diff --git a/bootstrap/emit.cc b/bootstrap/emit.cc index 1e6e3c1..e121da8 100644 --- a/bootstrap/emit.cc +++ b/bootstrap/emit.cc @@ -136,7 +136,7 @@ std::any EmitVisitor::visitFactor(xlangParser::FactorContext *ctx) { if (auto expr_list = ctx->exprList()) { for (auto expr : expr_list->expr()) { visitExpr(expr); - args.push_back(last); + args.emplace_back(last); } } output << " " << tmp() << " = w call $" diff --git a/bootstrap/error.cc b/bootstrap/error.cc index 0ccc5d2..a594bbc 100644 --- a/bootstrap/error.cc +++ b/bootstrap/error.cc @@ -15,8 +15,15 @@ void ErrorListener::syntaxError([[maybe_unused]] antlr4::Recognizer *recognizer, size_t line, size_t charPositionInLine, const std::string &msg, [[maybe_unused]] std::exception_ptr e) { - std::cerr << file << ":" << line << ":" << charPositionInLine << ": " << msg - << std::endl; + std::cerr << file << ":" << line << ":" << charPositionInLine + 1 << ": " + << msg << std::endl; + has_error = true; +} + +void ErrorListener::typeError(size_t line, size_t charPositionInLine, + std::string_view msg) { + std::cerr << file << ":" << line << ":" << charPositionInLine + 1 << ": " + << msg << std::endl; has_error = true; } diff --git a/bootstrap/error.hh b/bootstrap/error.hh index 7772106..6208ef4 100644 --- a/bootstrap/error.hh +++ b/bootstrap/error.hh @@ -11,11 +11,13 @@ class ErrorListener : public antlr4::BaseErrorListener { public: ErrorListener(std::string_view inputfile); + bool hasError(); void syntaxError(antlr4::Recognizer *recognizer, antlr4::Token *offendingSymbol, size_t line, size_t charPositionInLine, const std::string &msg, std::exception_ptr e) override; + void typeError(size_t line, size_t charPositionInLine, std::string_view msg); }; } // namespace xlang diff --git a/bootstrap/main.cc b/bootstrap/main.cc index 0ea2670..3103813 100644 --- a/bootstrap/main.cc +++ b/bootstrap/main.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,12 @@ int main(int argc, char **argv) { exit(1); } + xlang::TypeCheckVisitor typecheck{errorlistener}; + typecheck.visitFile(tree); + if (errorlistener.hasError()) { + exit(1); + } + xlang::EmitVisitor emit{outputfile}; emit.visitFile(tree); return 0; diff --git a/bootstrap/meson.build b/bootstrap/meson.build index 6fa7c8e..c941f72 100644 --- a/bootstrap/meson.build +++ b/bootstrap/meson.build @@ -3,6 +3,7 @@ xc_exe = executable('xc', 'main.cc', 'emit.cc', 'error.cc', + 'typecheck.cc', antlr4.process('xlang.g4'), ], dependencies : [ diff --git a/bootstrap/typecheck.cc b/bootstrap/typecheck.cc new file mode 100644 index 0000000..a4f7175 --- /dev/null +++ b/bootstrap/typecheck.cc @@ -0,0 +1,109 @@ +#include + +namespace xlang { + +bool TypeCheckVisitor::inScope(const std::string &name) { + for (auto it = scope.end() - 1;; it--) { + if (it->find(name) != it->end()) { + return true; + } + if (it == scope.begin()) { + break; + } + } + return false; +} + +TypeCheckVisitor::TypeCheckVisitor(ErrorListener &errorlistener) + : errorlistener{errorlistener} {} + +std::any TypeCheckVisitor::visitFile(xlangParser::FileContext *ctx) { + for (auto function : ctx->function()) { + auto token = function->Identifier()->getSymbol(); + auto name = token->getText(); + if (function_arity.find(name) != function_arity.end()) { + errorlistener.typeError(token->getLine(), token->getCharPositionInLine(), + "duplicate function '" + name + "'"); + continue; + } + if (auto arg_list = function->argumentList()) { + function_arity[name] = arg_list->Identifier().size(); + } else { + function_arity[name] = 0; + } + } + visitChildren(ctx); + return {}; +} + +std::any TypeCheckVisitor::visitFunction(xlangParser::FunctionContext *ctx) { + scope.emplace_back(); + if (auto arg_list = ctx->argumentList()) { + for (const auto arg : arg_list->Identifier()) { + scope.back().emplace(arg->getSymbol()->getText()); + } + } + visitBlock(ctx->block()); + scope.pop_back(); + return {}; +} + +std::any TypeCheckVisitor::visitBlock(xlangParser::BlockContext *ctx) { + scope.emplace_back(); + visitChildren(ctx); + scope.pop_back(); + return {}; +} + +std::any TypeCheckVisitor::visitStatement(xlangParser::StatementContext *ctx) { + if (auto identifier = ctx->Identifier()) { + visitExpr(ctx->expr()); + scope.back().emplace(identifier->getSymbol()->getText()); + return {}; + } + visitChildren(ctx); + return {}; +} + +std::any TypeCheckVisitor::visitFactor(xlangParser::FactorContext *ctx) { + if (auto identifier = ctx->Identifier()) { + auto token = identifier->getSymbol(); + auto name = token->getText(); + if (ctx->LeftParen()) { + if (function_arity.find(name) == function_arity.end()) { + errorlistener.typeError(token->getLine(), + token->getCharPositionInLine(), + "unknown function '" + name + "'"); + } + if (auto expr_list = ctx->exprList()) { + auto arity = function_arity[name]; + auto arg_num = expr_list->expr().size(); + if (arity != arg_num) { + errorlistener.typeError( + token->getLine(), token->getCharPositionInLine(), + "function '" + name + "' expects " + std::to_string(arity) + + " arguments, but got " + std::to_string(arg_num)); + } + } else { + if (auto arity = function_arity[name]) { + errorlistener.typeError( + token->getLine(), token->getCharPositionInLine(), + "function '" + name + "' expects " + std::to_string(arity) + + " arguments, but got none"); + } + } + visitChildren(ctx); + } else { + if (!inScope(name)) { + errorlistener.typeError(token->getLine(), + token->getCharPositionInLine(), + "variable '" + name + "' is not in scope"); + } + } + return {}; + } + visitChildren(ctx); + return {}; +} + +} // namespace xlang diff --git a/bootstrap/typecheck.hh b/bootstrap/typecheck.hh new file mode 100644 index 0000000..b60290f --- /dev/null +++ b/bootstrap/typecheck.hh @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace xlang { + +class TypeCheckVisitor : public xlangBaseVisitor { + ErrorListener &errorlistener; + std::unordered_map function_arity; + std::vector> scope; + + bool inScope(const std::string &name); + + public: + TypeCheckVisitor(ErrorListener &errorlistener); + + std::any visitFile(xlangParser::FileContext *ctx) override; + std::any visitFunction(xlangParser::FunctionContext *ctx) override; + std::any visitBlock(xlangParser::BlockContext *ctx) override; + std::any visitStatement(xlangParser::StatementContext *ctx) override; + std::any visitFactor(xlangParser::FactorContext *ctx) override; +}; + +} // namespace xlang