diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a3126c..135ff85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ 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/result.cpp include/elna/source/result.hpp ${BISON_parser_OUTPUTS} ${FLEX_lexer_OUTPUTS} ) 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/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..4c0cdd0 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,44 @@ %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; +%type >> expressions actual_parameter_list; +%type > compound_statement; +%type > assign_statement; +%type > call_statement; +%type > while_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 +110,102 @@ 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)); + } +expression: + integer_literal { $$ = std::move($1); } + | boolean_literal { $$ = std::move($1); } +expressions: + expression COMMA expressions + { + std::swap($$, $3); + $$.emplace($$.cbegin(), std::move($1)); + } + | expression { $$.emplace_back(std::move($1)); } +statement: + compound_statement { $$ = std::move($1); } + | assign_statement { $$ = std::move($1); } + | call_statement { $$ = std::move($1); } + | while_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 +218,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); }