From f080b75c52e2ef3825c293fd8260acdc7f225ba8 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 21 Dec 2024 14:05:27 +0100 Subject: [PATCH] Parse variable declarations --- .gitignore | 2 + CMakeLists.txt | 3 + README | 37 +++ Rakefile | 4 + cli/main.cpp | 49 +++- include/elna/source/driver.hpp | 35 +++ include/elna/source/result.hpp | 50 +++++ include/elna/source/semantic.hpp | 94 ++++++++ include/elna/source/symbol_table.hpp | 161 +++++++++++++ source/driver.cpp | 38 ++++ source/lexer.ll | 10 +- source/parser.yy | 254 +++++++++++++++++---- source/result.cpp | 22 ++ source/semantic.cpp | 325 +++++++++++++++++++++++++++ source/symbol_table.cpp | 129 +++++++++++ 15 files changed, 1151 insertions(+), 62 deletions(-) create mode 100644 README create mode 100644 Rakefile create mode 100644 include/elna/source/driver.hpp create mode 100644 include/elna/source/semantic.hpp create mode 100644 include/elna/source/symbol_table.hpp create mode 100644 source/driver.cpp create mode 100644 source/semantic.cpp create mode 100644 source/symbol_table.cpp diff --git a/.gitignore b/.gitignore index c8ca994..a65d6bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /build/ .cache/ +CMakeFiles/ +CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a3126c..2ad72d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,10 @@ add_flex_bison_dependency(lexer parser) add_executable(elna cli/main.cpp source/ast.cpp include/elna/source/ast.hpp source/types.cpp include/elna/source/types.hpp + source/driver.cpp include/elna/source/driver.hpp + source/symbol_table.cpp include/elna/source/symbol_table.hpp source/result.cpp include/elna/source/result.hpp + source/semantic.cpp include/elna/source/semantic.hpp ${BISON_parser_OUTPUTS} ${FLEX_lexer_OUTPUTS} ) target_include_directories(elna PRIVATE ${CMAKE_CURRENT_BINARY_DIR} include) diff --git a/README b/README new file mode 100644 index 0000000..b6e03eb --- /dev/null +++ b/README @@ -0,0 +1,37 @@ +# Elna programming language + +Elna compiles simple mathematical operations to machine code. +The compiled program returns the result of the operation. + +## File extension + +.elna + +## Grammar PL/0 + +program = block "." ; + +block = [ "const" ident "=" number {"," ident "=" number} ";"] + [ "var" ident {"," ident} ";"] + { "procedure" ident ";" block ";" } statement ; + +statement = [ ident ":=" expression | "call" ident + | "?" ident | "!" expression + | "begin" statement {";" statement } "end" + | "if" condition "then" statement + | "while" condition "do" statement ]; + +condition = "odd" expression | + expression ("="|"#"|"<"|"<="|">"|">=") expression ; + +expression = [ "+"|"-"] term { ("+"|"-") term}; + +term = factor {("*"|"/") factor}; + +factor = ident | number | "(" expression ")"; + +## Operations + +"!" - Write a line. +"?" - Read user input. +"odd" - The only function, returns whether a number is odd. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..c5128fd --- /dev/null +++ b/Rakefile @@ -0,0 +1,4 @@ +task :default do + sh 'make -C build' + sh './build/bin/elna' +end diff --git a/cli/main.cpp b/cli/main.cpp index 0caf149..6da37e7 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -1,22 +1,49 @@ +#include #include "parser.hpp" #include int main() { - std::istringstream inp("const world = 5, hello = 7;"); + elna::source::driver driver{ "-" }; + std::istringstream inp(R"( + const world = 5, hello = 7; + var x: int, y: boolean; - std::unique_ptr program; + proc f(); + begin + x := 8 + end; - elna::syntax::FooLexer lexer(inp); - yy::parser parser(lexer, program); - auto result = parser(); + begin + while false do inc(5) + end. + )"); - for (auto& definition : program->definitions()) + elna::source::lexer lexer(inp); + yy::parser parser(lexer, driver); + + if (auto result = parser()) { - auto const_definition = dynamic_cast(definition.get()); - - std::cout << "const " << const_definition->identifier() << " = " - << const_definition->body().number() << std::endl; + for (const auto& error : driver.errors()) + { + std::cerr << error->path().string() << ':' + << error->line() << ':' << error->column() + << ": error: " << error->what() + << '.' << std::endl; + } + return result; } - return result; + for (auto& definition : driver.tree->definitions()) + { + if (auto const_definition = dynamic_cast(definition.get())) + { + std::cout << "const " << const_definition->identifier() << " = " + << const_definition->body().number() << std::endl; + } + else if (auto proc_definition = dynamic_cast(definition.get())) + { + std::cout << "const " << proc_definition->identifier() << "()" << std::endl; + } + } + return 0; } diff --git a/include/elna/source/driver.hpp b/include/elna/source/driver.hpp new file mode 100644 index 0000000..e91938f --- /dev/null +++ b/include/elna/source/driver.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include "elna/source/ast.hpp" +#include "location.hh" + +namespace elna::source +{ + position make_position(const yy::location& location); + + class syntax_error final : public error + { + std::string message; + + public: + syntax_error(const std::string& message, + const std::filesystem::path& input_file, const yy::location& location); + + virtual std::string what() const override; + }; + + class driver + { + std::list> m_errors; + const std::filesystem::path input_file; + + public: + std::unique_ptr tree; + + driver(const std::filesystem::path& input_file); + + void error(const yy::location& loc, const std::string& message); + const std::list>& errors() const noexcept; + }; +} diff --git a/include/elna/source/result.hpp b/include/elna/source/result.hpp index df79084..2dc9a69 100644 --- a/include/elna/source/result.hpp +++ b/include/elna/source/result.hpp @@ -2,6 +2,7 @@ #include #include +#include "elna/source/types.hpp" namespace elna::source { @@ -49,4 +50,53 @@ namespace elna::source /// Source file name. const std::filesystem::path& path() const noexcept; }; + + class name_collision final : public error + { + position previous; + std::string name; + + public: + /** + * \param name Symbol name. + * \param path Source file name. + * \param current Current symbol position. + * \param previous Position of the previously defined symbol. + */ + name_collision(const std::string& name, const std::filesystem::path& path, + const position current, const position previous); + + std::string what() const override; + }; + + struct type_mismatch final : public error + { + /** + * Kind of the operation on the type. + */ + enum class operation + { + dereference, + argument, + arithmetic, + comparison, + condition, + assignment + }; + + /** + * \param name Given type. + * \param kind Kind of the operation on the type. + * \param path Source file name. + * \param position Operation position. + */ + type_mismatch(std::shared_ptr got, operation kind, const std::filesystem::path& path, + const struct position position); + + std::string what() const override; + + private: + std::shared_ptr got; + operation kind; + }; } diff --git a/include/elna/source/semantic.hpp b/include/elna/source/semantic.hpp new file mode 100644 index 0000000..752c596 --- /dev/null +++ b/include/elna/source/semantic.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include "elna/source/ast.hpp" +#include "elna/source/symbol_table.hpp" + +namespace elna::source +{ + class name_analysis_visitor final : public empty_visitor + { + std::shared_ptr table; + const std::filesystem::path filename; + std::list> m_errors; + const std::size_t pointer_size; + + std::shared_ptr convert_declaration_type(const type_expression& ast_type) const; + + public: + /** + * \param table Symbol table. + * \param path Source filename. + * \param target_pointer_size Pointer size on the target platform. + */ + name_analysis_visitor(std::shared_ptr table, const std::filesystem::path& filename, + const std::size_t target_pointer_size); + + /** + * \return Collected errors. + */ + const std::list>& errors() const noexcept; + + void visit(constant_definition *definition) override; + void visit(declaration *declaration) override; + void visit(program *program) override; + void visit(procedure_definition *procedure) override; + }; + + /** + * Visitor which allocates registers and stack space for variables and + * parameters. + */ + class allocator_visitor final : public empty_visitor + { + std::ptrdiff_t local_offset; + std::ptrdiff_t argument_offset; + std::shared_ptr table; + + public: + allocator_visitor(std::shared_ptr table); + + void visit(declaration *declaration) override; + void visit(program *program) override; + void visit(procedure_definition *procedure) override; + void visit(call_statement *statement) override; + }; + + /** + * This visitor performs the type checking. + */ + class type_analysis_visitor final : public empty_visitor + { + std::shared_ptr table; + const std::filesystem::path filename; + const std::size_t pointer_size; + std::list> m_errors; + + public: + /** + * \param table Symbol table. + * \param path Source filename. + * \param target_pointer_size Pointer size on the target platform. + */ + type_analysis_visitor(std::shared_ptr table, const std::filesystem::path& filename, + const std::size_t target_pointer_size); + + /** + * \return Collected errors. + */ + const std::list>& errors() const noexcept; + + void visit(program *program) override; + void visit(procedure_definition *procedure) override; + void visit(integer_literal *literal) override; + void visit(boolean_literal *literal) override; + void visit(variable_expression *expression) override; + void visit(unary_expression *expression) override; + void visit(binary_expression *expression) override; + void visit(call_statement *statement) override; + void visit(constant_definition *definition) override; + void visit(while_statement *statement) override; + void visit(if_statement *statement) override; + void visit(assign_statement *statement) override; + }; +} diff --git a/include/elna/source/symbol_table.hpp b/include/elna/source/symbol_table.hpp new file mode 100644 index 0000000..f0eb9fa --- /dev/null +++ b/include/elna/source/symbol_table.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include + +namespace elna::source +{ + class symbol_table; + + /** + * Generic language entity information. + */ + class info + { + public: + virtual ~info() = 0; + + protected: + info(); + }; + + /** + * Type information. + */ + class type_info final : public info + { + std::shared_ptr m_type; + + public: + explicit type_info(const class type& type); + ~type_info() override; + std::shared_ptr type() const noexcept; + }; + + /** + * Information for a typed symbol. + */ + class typed_info : public info + { + std::shared_ptr m_type; + + protected: + typed_info(std::shared_ptr type); + + public: + ~typed_info() override; + + std::shared_ptr type() const noexcept; + }; + + /** + * Constant information. + */ + class constant_info final : public typed_info + { + std::int32_t m_value; + + public: + constant_info(std::shared_ptr type, const std::int32_t value); + + std::int32_t value() const noexcept; + }; + + /** + * Variable information. + */ + class variable_info final : public typed_info + { + public: + std::ptrdiff_t offset{ 0 }; + + explicit variable_info(std::shared_ptr type); + }; + + /** + * Procedure parameter information. + */ + class parameter_info final : public typed_info + { + public: + std::ptrdiff_t offset{ 0 }; + + explicit parameter_info(std::shared_ptr type); + }; + + /** + * Intrinsic and external procedure information. + */ + class intrinsic_info : public info + { + std::shared_ptr m_type; + + public: + explicit intrinsic_info(const class procedure_type& type); + ~intrinsic_info() override; + + std::shared_ptr type() const noexcept; + std::size_t parameter_stack_size() const noexcept; + }; + + /** + * Procedure information. + */ + class procedure_info final : public intrinsic_info + { + std::shared_ptr local_table; + + public: + std::size_t local_stack_size{ 0 }; + std::size_t argument_stack_size{ 0 }; + + procedure_info(const class procedure_type& type, std::shared_ptr outer_scope); + ~procedure_info() override; + + std::shared_ptr scope(); + std::size_t stack_size() const noexcept; + }; + + /** + * Symbol table. + */ + class symbol_table + { + std::unordered_map> entries; + std::shared_ptr outer_scope; + + public: + /** + * Constructs a new symbol with an optional outer scope. + * + * \param scope Outer scope. + */ + explicit symbol_table(std::shared_ptr scope = nullptr); + + /** + * Looks for symbol in the table by name. Returns nullptr if the symbol + * can not be found. + * + * \param name Symbol name. + * \return Symbol from the table if found. + */ + std::shared_ptr lookup(const std::string& name); + + /** + * Registers new symbol. + * + * \param name Symbol name. + * \param entry Symbol information. + */ + void enter(const std::string& name, std::shared_ptr entry); + + /** + * Returns the outer scope or nullptr if the this is the global scope. + * + * \return Outer scope. + */ + std::shared_ptr scope(); + }; +} diff --git a/source/driver.cpp b/source/driver.cpp new file mode 100644 index 0000000..a83ea8b --- /dev/null +++ b/source/driver.cpp @@ -0,0 +1,38 @@ +#include "elna/source/driver.hpp" + +namespace elna::source +{ + position make_position(const yy::location& location) + { + return position{ + static_cast(location.begin.line), + static_cast(location.begin.column) + }; + } + + syntax_error::syntax_error(const std::string& message, + const std::filesystem::path& input_file, const yy::location& location) + : error(input_file, make_position(location)), message(message) + { + } + + std::string syntax_error::what() const + { + return message; + } + + driver::driver(const std::filesystem::path& input_file) + : input_file(input_file) + { + } + + void driver::error(const yy::location& loc, const std::string& message) + { + m_errors.emplace_back(std::make_unique(message, input_file, loc)); + } + + const std::list>& driver::errors() const noexcept + { + return m_errors; + } +} diff --git a/source/lexer.ll b/source/lexer.ll index 9485abd..cb69e5e 100644 --- a/source/lexer.ll +++ b/source/lexer.ll @@ -6,12 +6,12 @@ #include "parser.hpp" #undef YY_DECL -#define YY_DECL yy::parser::symbol_type elna::syntax::FooLexer::lex() +#define YY_DECL yy::parser::symbol_type elna::source::lexer::lex() #define yyterminate() return yy::parser::make_YYEOF(this->location) %} %option c++ noyywrap never-interactive -%option yyclass="elna::syntax::FooLexer" +%option yyclass="elna::source::lexer" %% %{ @@ -53,10 +53,10 @@ const { var { return yy::parser::make_VAR(this->location); } -True { +true { return yy::parser::make_BOOLEAN(true, this->location); } -False { +false { return yy::parser::make_BOOLEAN(false, this->location); } [A-Za-z_][A-Za-z0-9_]* { @@ -125,7 +125,7 @@ False { . { std::stringstream ss; - ss << "Illegal character 0x" << std::hex << static_cast(yytext[0]); + ss << "Illegal character 0x" << std::hex << static_cast(yytext[0]); throw yy::parser::syntax_error(this->location, ss.str()); } %% diff --git a/source/parser.yy b/source/parser.yy index 3447ea9..c892237 100644 --- a/source/parser.yy +++ b/source/parser.yy @@ -4,29 +4,28 @@ %code requires { #include #include - #include "elna/source/ast.hpp" + #include "elna/source/driver.hpp" - - #if ! defined(yyFlexLexerOnce) + #if !defined(yyFlexLexerOnce) #include #endif - namespace elna::syntax + namespace elna::source { - class FooLexer; + class lexer; } } %code provides { - namespace elna::syntax + namespace elna::source { - class FooLexer : public yyFlexLexer + class lexer: public yyFlexLexer { public: yy::location location; - FooLexer(std::istream& arg_yyin) + lexer(std::istream& arg_yyin) : yyFlexLexer(&arg_yyin) { } @@ -42,8 +41,8 @@ %define api.value.type variant %define parse.assert -%parse-param {elna::syntax::FooLexer& lexer} -%parse-param {std::unique_ptr& program} +%parse-param {elna::source::lexer& lexer} +%parse-param {elna::source::driver& driver} %locations %header @@ -66,14 +65,46 @@ %token ASSIGNMENT COLON HAT AT %type > integer_literal; +%type > boolean_literal; %type > constant_definition; %type >> constant_definition_part constant_definitions; +%type > variable_declaration; +%type >> variable_declarations variable_declaration_part + formal_parameter_list; %type > type_expression; +%type > expression pointer summand factor address; +%type >> expressions actual_parameter_list; +%type > variable_expression; +%type > compound_statement; +%type > assign_statement; +%type > call_statement; +%type > while_statement; +%type > if_statement; +%type > statement; +%type >> statements optional_statements; +%type > procedure_definition; +%type >> procedure_definitions; +%type > block; %% -program: constant_definition_part +program: constant_definition_part variable_declaration_part procedure_definitions statement DOT + { + std::vector> definitions($1.size() + $3.size()); + std::vector>::iterator definition = definitions.begin(); + + for (auto& constant : $1) + { + *definition++ = std::move(constant); + } + for (auto& constant : $3) + { + *definition++ = std::move(constant); + } + driver.tree = std::make_unique(elna::source::position{}, + std::move(definitions), std::move($2), + std::move($4)); + } +block: constant_definition_part variable_declaration_part statement { - elna::source::position position; - std::vector> declarations; std::vector> definitions($1.size()); std::vector>::iterator definition = definitions.begin(); @@ -81,46 +112,171 @@ program: constant_definition_part { *definition++ = std::move(constant); } - program = std::make_unique(position, - std::move(definitions), std::move(declarations), - std::make_unique(position)); + $$ = std::make_unique(elna::source::position{}, + std::move(definitions), std::move($2), std::move($3)); + }; +procedure_definition: + PROCEDURE IDENTIFIER formal_parameter_list SEMICOLON block SEMICOLON + { + $$ = std::make_unique(elna::source::position{}, + std::move($2), std::move($5)); + std::swap($$->parameters(), $3); + }; +procedure_definitions: + procedure_definition procedure_definitions + { + std::swap($$, $2); + $$.emplace($$.cbegin(), std::move($1)); } + | procedure_definition { $$.emplace_back(std::move($1)); } integer_literal: NUMBER { - elna::source::position position{ - static_cast(@1.begin.line), - static_cast(@1.begin.column) - }; - $$ = std::make_unique(position, $1); + $$ = std::make_unique(elna::source::make_position(@1), $1); }; +boolean_literal: BOOLEAN + { + $$ = std::make_unique(elna::source::make_position(@1), $1); + }; +compound_statement: BEGIN_BLOCK optional_statements END_BLOCK + { + $$ = std::make_unique(elna::source::make_position(@1)); + std::swap($$->statements(), $2); + } +assign_statement: IDENTIFIER ASSIGNMENT expression + { + $$ = std::make_unique(elna::source::make_position(@1), $1, std::move($3)); + } +call_statement: IDENTIFIER actual_parameter_list + { + $$ = std::make_unique(elna::source::make_position(@1), $1); + std::swap($$->arguments(), $2); + } +while_statement: WHILE expression DO statement + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($2), std::move($4)); + } +if_statement: + IF expression THEN statement + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($2), std::move($4)); + } +pointer: + integer_literal { $$ = std::move($1); } + | boolean_literal { $$ = std::move($1); } + | variable_expression { $$ = std::move($1); } + | LEFT_PAREN expression RIGHT_PAREN { $$ = std::move($2); } +summand: + factor { $$ = std::move($1); } + | factor MULTIPLICATION factor + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '*'); + } + | factor DIVISION factor + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '/'); + } +address: + pointer HAT + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), '^'); + } + | pointer { $$ = std::move($1); } +factor: + AT address + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($2), '@'); + } + | address { $$ = std::move($1); } +expression: + summand EQUALS summand + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '='); + } + | summand NOT_EQUAL summand + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), 'n'); + } + | summand LESS_THAN summand + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '<'); + } + | summand GREATER_THAN summand + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '>'); + } + | summand LESS_EQUAL summand + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '<'); + } + | summand GREATER_EQUAL summand + { + $$ = std::make_unique(elna::source::make_position(@1), + std::move($1), std::move($3), '>'); + } + | summand { $$ = std::move($1); } +expressions: + expression COMMA expressions + { + std::swap($$, $3); + $$.emplace($$.cbegin(), std::move($1)); + } + | expression { $$.emplace_back(std::move($1)); } +variable_expression: IDENTIFIER + { $$ = std::make_unique(elna::source::make_position(@1), $1); } +statement: + compound_statement { $$ = std::move($1); } + | assign_statement { $$ = std::move($1); } + | call_statement { $$ = std::move($1); } + | while_statement { $$ = std::move($1); } + | if_statement { $$ = std::move($1); } +statements: + statement SEMICOLON statements + { + std::swap($$, $3); + $$.emplace($$.cbegin(), std::move($1)); + } + | statement { $$.emplace_back(std::move($1)); } +optional_statements: + statements { std::swap($$, $1); } + | /* no statements */ {} type_expression: - HAT IDENTIFIER - { - elna::source::position position{ - static_cast(@1.begin.line), - static_cast(@1.begin.column) - }; - $$ = std::make_unique(position, $2, true); - } - | IDENTIFIER - { - elna::source::position position{ - static_cast(@1.begin.line), - static_cast(@1.begin.column) - }; - $$ = std::make_unique(position, $1, false); - } + HAT IDENTIFIER + { + $$ = std::make_unique(elna::source::make_position(@1), $2, true); + } + | IDENTIFIER + { + $$ = std::make_unique(elna::source::make_position(@1), $1, false); + } variable_declaration: IDENTIFIER COLON type_expression + { + $$ = std::make_unique(elna::source::make_position(@1), + $1, std::move($3)); + }; variable_declarations: - variable_declaration COMMA variable_declarations - | variable_declaration + variable_declaration COMMA variable_declarations + { + std::swap($$, $3); + $$.emplace($$.cbegin(), std::move($1)); + } + | variable_declaration { $$.emplace_back(std::move($1)); } +variable_declaration_part: + /* no variable declarations */ {} + | VAR variable_declarations SEMICOLON { std::swap($$, $2); } constant_definition: IDENTIFIER EQUALS integer_literal { - elna::source::position position{ - static_cast(@1.begin.line), - static_cast(@1.begin.column) - }; - $$ = std::make_unique(position, + $$ = std::make_unique(elna::source::make_position(@1), $1, std::move($3)); }; constant_definitions: @@ -133,9 +289,15 @@ constant_definitions: constant_definition_part: /* no constant definitions */ {} | CONST constant_definitions SEMICOLON { std::swap($$, $2); }; +formal_parameter_list: + LEFT_PAREN RIGHT_PAREN {} + | LEFT_PAREN variable_declarations RIGHT_PAREN { std::swap($$, $2); } +actual_parameter_list: + LEFT_PAREN RIGHT_PAREN {} + | LEFT_PAREN expressions RIGHT_PAREN { std::swap($$, $2); } %% -void yy::parser::error(const location_type& loc, const std::string &message) +void yy::parser::error(const location_type& loc, const std::string& message) { - std::cerr << "Error: " << message << std::endl; + driver.error(loc, message); } diff --git a/source/result.cpp b/source/result.cpp index d50ae87..f79e85f 100644 --- a/source/result.cpp +++ b/source/result.cpp @@ -21,4 +21,26 @@ namespace elna::source { return this->m_path; } + + name_collision::name_collision(const std::string& name, const std::filesystem::path& path, + const position current, const position previous) + : error(path, current), name(name), previous(previous) + { + } + + std::string name_collision::what() const + { + return "Name '" + name + "' was already defined"; + } + + type_mismatch::type_mismatch(std::shared_ptr got, operation kind, const std::filesystem::path& path, + const struct position position) + : error(path, position), kind(kind), got(got) + { + } + + std::string type_mismatch::what() const + { + return "Type cannot be used here."; + } } diff --git a/source/semantic.cpp b/source/semantic.cpp new file mode 100644 index 0000000..789fd48 --- /dev/null +++ b/source/semantic.cpp @@ -0,0 +1,325 @@ +#include "elna/source/semantic.hpp" +#include "elna/source/result.hpp" +#include + +namespace elna::source +{ + name_analysis_visitor::name_analysis_visitor(std::shared_ptr table, + const std::filesystem::path& filename, const std::size_t target_pointer_size) + : table(table), filename(filename), pointer_size(target_pointer_size) + { + } + + void name_analysis_visitor::visit(constant_definition *definition) + { + auto constant_type = std::make_shared(int_type); + this->table->enter(definition->identifier(), + std::make_shared(constant_type, definition->body().number())); + } + + std::shared_ptr name_analysis_visitor::convert_declaration_type(const type_expression& ast_type) const + { + auto variable_type = std::dynamic_pointer_cast(table->lookup(ast_type.base())) + ->type(); + std::shared_ptr declaration_type; + + if (ast_type.is_pointer()) + { + return std::make_shared(variable_type, 4); + } + else + { + return variable_type; + } + } + + void name_analysis_visitor::visit(declaration *declaration) + { + std::shared_ptr declaration_type = convert_declaration_type(declaration->type()); + + this->table->enter(declaration->identifier(), + std::make_shared(declaration_type)); + } + + void name_analysis_visitor::visit(program *program) + { + class procedure_type main_type{ std::vector>(), this->pointer_size }; + this->table->enter("_start", std::make_shared(main_type, this->table)); + empty_visitor::visit(program); + } + + void name_analysis_visitor::visit(procedure_definition *procedure) + { + std::vector> arguments; + + for (auto& parameter : procedure->parameters()) + { + auto declaration_type = convert_declaration_type(parameter->type()); + arguments.push_back(declaration_type); + } + procedure_type definition_type{ std::move(arguments), this->pointer_size }; + auto info = std::make_shared(definition_type, this->table); + + this->table->enter(procedure->identifier(), info); + this->table = info->scope(); + + for (std::size_t i = 0; i < procedure->parameters().size(); ++i) + { + this->table->enter(procedure->parameters().at(i)->identifier(), + std::make_shared(definition_type.arguments.at(i))); + } + procedure->body().accept(this); + + this->table = info->scope()->scope(); + } + + const std::list>& name_analysis_visitor::errors() const noexcept + { + return m_errors; + } + + allocator_visitor::allocator_visitor(std::shared_ptr table) + : table(table) + { + } + + void allocator_visitor::visit(declaration *declaration) + { + auto declaration_info = this->table->lookup(declaration->identifier()); + + if (auto variable = std::dynamic_pointer_cast(declaration_info)) + { + this->local_offset -= sizeof(std::int32_t); + variable->offset = this->local_offset; + } + else if (auto parameter = std::dynamic_pointer_cast(declaration_info)) + { + parameter->offset = this->argument_offset; + this->argument_offset += sizeof(std::int32_t); + } + } + + void allocator_visitor::visit(program *program) + { + this->local_offset = 0; + this->argument_offset = 0; + + empty_visitor::visit(program); + std::dynamic_pointer_cast(table->lookup("_start"))->local_stack_size = + std::abs(this->local_offset); + } + + void allocator_visitor::visit(procedure_definition *procedure) + { + this->local_offset = 0; + this->argument_offset = 0; + auto info = std::dynamic_pointer_cast(this->table->lookup(procedure->identifier())); + this->table = info->scope(); + + empty_visitor::visit(procedure); + + this->table = info->scope()->scope(); + info->local_stack_size = std::abs(this->local_offset); + info->argument_stack_size = this->argument_offset; + } + + void allocator_visitor::visit(call_statement *statement) + { + auto call_info = std::dynamic_pointer_cast(this->table->lookup(statement->name())); + + this->argument_offset = std::max(static_cast(this->argument_offset), + call_info->parameter_stack_size()); + } + + type_analysis_visitor::type_analysis_visitor(std::shared_ptr table, + const std::filesystem::path& filename, const std::size_t target_pointer_size) + : table(table), filename(filename), pointer_size(target_pointer_size) + { + } + + void type_analysis_visitor::visit(program *program) + { + for (auto& definition : program->definitions()) + { + if (dynamic_cast(definition.get()) != nullptr) + { + definition->accept(this); + } + } + program->body().accept(this); + } + + void type_analysis_visitor::visit(procedure_definition *procedure) + { + auto info = std::dynamic_pointer_cast(this->table->lookup(procedure->identifier())); + this->table = info->scope(); + + procedure->body().accept(this); + + this->table = info->scope()->scope(); + } + + void type_analysis_visitor::visit(integer_literal *literal) + { + literal->data_type = std::dynamic_pointer_cast(table->lookup("Int"))->type(); + } + + void type_analysis_visitor::visit(boolean_literal *literal) + { + literal->data_type = std::dynamic_pointer_cast(table->lookup("Boolean"))->type(); + } + + void type_analysis_visitor::visit(variable_expression *expression) + { + expression->data_type = std::dynamic_pointer_cast(table->lookup(expression->name()))->type(); + } + + void type_analysis_visitor::visit(unary_expression *expression) + { + empty_visitor::visit(expression); + + switch (expression->operation()) + { + case unary_operator::reference: + expression->data_type = std::make_shared(expression->operand().data_type, + this->pointer_size); + break; + case unary_operator::dereference: + auto operand_type = expression->operand().data_type; + + if (auto referenced_type = std::dynamic_pointer_cast(operand_type)) + { + expression->data_type = referenced_type; + } + else if (operand_type != nullptr) + { + auto new_error = std::make_unique(operand_type, + type_mismatch::operation::dereference, this->filename, expression->position()); + m_errors.push_back(std::move(new_error)); + } + break; + } + } + + void type_analysis_visitor::visit(binary_expression *expression) + { + empty_visitor::visit(expression); + + switch (expression->operation()) + { + case binary_operator::sum: + case binary_operator::subtraction: + case binary_operator::multiplication: + case binary_operator::division: + case binary_operator::less: + case binary_operator::greater: + case binary_operator::less_equal: + case binary_operator::greater_equal: + if (expression->lhs().data_type != nullptr && expression->rhs().data_type != nullptr) + { + auto lhs_type = std::dynamic_pointer_cast(expression->lhs().data_type); + auto rhs_type = std::dynamic_pointer_cast(expression->rhs().data_type); + + std::unique_ptr new_error; + if (lhs_type == nullptr || *lhs_type != int_type) + { + new_error = std::make_unique(lhs_type, + type_mismatch::operation::arithmetic, this->filename, expression->lhs().position()); + } + if (rhs_type == nullptr || *rhs_type != int_type) + { + new_error = std::make_unique(rhs_type, + type_mismatch::operation::arithmetic, this->filename, expression->rhs().position()); + } + if (new_error != nullptr) + { + m_errors.push_back(std::move(new_error)); + } + } + break; + case binary_operator::equals: + case binary_operator::not_equals: + if (expression->lhs().data_type != nullptr && expression->rhs().data_type != nullptr) + { + if (expression->lhs().data_type != expression->rhs().data_type) + { + auto new_error = std::make_unique(expression->rhs().data_type, + type_mismatch::operation::comparison, this->filename, expression->rhs().position()); + } + } + break; + } + } + + void type_analysis_visitor::visit(call_statement *statement) + { + auto call_info = std::dynamic_pointer_cast(this->table->lookup(statement->name())); + + std::size_t i{ 0 }; + for (const auto& argument : statement->arguments()) + { + argument->accept(this); + + if (argument->data_type != nullptr && i < call_info->type()->arguments.size() + && call_info->type()->arguments[i] != argument->data_type) + { + auto new_error = std::make_unique(argument->data_type, + type_mismatch::operation::argument, this->filename, argument->position()); + m_errors.push_back(std::move(new_error)); + } + + ++i; + } + } + + void type_analysis_visitor::visit(constant_definition *definition) + { + definition->body().accept(this); + } + + void type_analysis_visitor::visit(while_statement *statement) + { + statement->prerequisite().accept(this); + auto condition_type = std::dynamic_pointer_cast(statement->prerequisite().data_type); + + if (condition_type != nullptr && *condition_type != boolean_type) + { + auto new_error = std::make_unique(condition_type, + type_mismatch::operation::condition, this->filename, statement->prerequisite().position()); + m_errors.push_back(std::move(new_error)); + } + statement->body().accept(this); + } + + void type_analysis_visitor::visit(if_statement *statement) + { + statement->prerequisite().accept(this); + auto condition_type = std::dynamic_pointer_cast(statement->prerequisite().data_type); + + if (condition_type != nullptr && *condition_type != boolean_type) + { + auto new_error = std::make_unique(condition_type, + type_mismatch::operation::condition, this->filename, statement->prerequisite().position()); + m_errors.push_back(std::move(new_error)); + } + statement->body().accept(this); + } + + void type_analysis_visitor::visit(assign_statement *statement) + { + statement->rvalue().accept(this); + auto lvalue_info = std::dynamic_pointer_cast(this->table->lookup(statement->lvalue())); + + if (statement->rvalue().data_type != nullptr && lvalue_info->type() == statement->rvalue().data_type) + { + auto new_error = std::make_unique(statement->rvalue().data_type, + type_mismatch::operation::assignment, this->filename, statement->position()); + m_errors.push_back(std::move(new_error)); + } + } + + const std::list>& type_analysis_visitor::errors() const noexcept + { + return m_errors; + } +} diff --git a/source/symbol_table.cpp b/source/symbol_table.cpp new file mode 100644 index 0000000..8801fa7 --- /dev/null +++ b/source/symbol_table.cpp @@ -0,0 +1,129 @@ +#include "elna/source/types.hpp" +#include "elna/source/symbol_table.hpp" + +namespace elna::source +{ + symbol_table::symbol_table(std::shared_ptr scope) + : outer_scope(scope) + { + } + + std::shared_ptr symbol_table::lookup(const std::string& name) + { + auto entry = entries.find(name); + + if (entry != entries.cend()) + { + return entry->second; + } + if (this->outer_scope != nullptr) + { + return this->outer_scope->lookup(name); + } + return nullptr; + } + + void symbol_table::enter(const std::string& name, std::shared_ptr entry) + { + entries.insert_or_assign(name, entry); + } + + std::shared_ptr symbol_table::scope() + { + return this->outer_scope; + } + + info::~info() + { + } + + info::info() + { + } + + type_info::type_info(const class type& type) + : info(), m_type(std::make_shared(type)) + { + } + + type_info::~type_info() + { + } + + std::shared_ptr type_info::type() const noexcept + { + return m_type; + } + + typed_info::typed_info(std::shared_ptr type) + : m_type(type) + { + } + + typed_info::~typed_info() + { + } + + std::shared_ptr typed_info::type() const noexcept + { + return m_type; + } + + constant_info::constant_info(const std::shared_ptr type, const std::int32_t value) + : typed_info(type), m_value(value) + { + } + + std::int32_t constant_info::value() const noexcept + { + return m_value; + } + + variable_info::variable_info(std::shared_ptr type) + : typed_info(type) + { + } + + parameter_info::parameter_info(std::shared_ptr type) + : typed_info(type) + { + } + + intrinsic_info::intrinsic_info(const class procedure_type& type) + : m_type(std::make_shared(type)) + { + } + + intrinsic_info::~intrinsic_info() + { + } + + std::shared_ptr intrinsic_info::type() const noexcept + { + return m_type; + } + + std::size_t intrinsic_info::parameter_stack_size() const noexcept + { + return type()->arguments.size() * sizeof(std::int32_t); + } + + procedure_info::procedure_info(const class procedure_type& type, std::shared_ptr outer_scope) + : intrinsic_info(type), local_table(std::make_shared(outer_scope)) + { + } + + procedure_info::~procedure_info() + { + } + + std::shared_ptr procedure_info::scope() + { + return local_table; + } + + std::size_t procedure_info::stack_size() const noexcept + { + return local_stack_size + argument_stack_size; + } +}