diff --git a/src/meson.build b/src/meson.build index 5b8b9c6..da90f6c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,6 +2,8 @@ abacus_sources = files( 'abacus.cc', ) +subdir('parse') + abacus = executable( 'abacus', sources: abacus_sources, diff --git a/src/parse/meson.build b/src/parse/meson.build new file mode 100644 index 0000000..eed8403 --- /dev/null +++ b/src/parse/meson.build @@ -0,0 +1,34 @@ +flex_binary = find_program('flex', required: true) +flex = generator( + flex_binary, + output: '@BASENAME@.cc', + arguments: [ + '-o', + '@OUTPUT@', + '@INPUT@', + ], +) + +bison_binary = find_program('bison', required: true) +bison = generator( + bison_binary, + output: [ + '@BASENAME@.cc', + '@BASENAME@.hh', + ], + arguments: [ + '@INPUT@', + '--output=@OUTPUT0@', + '--defines=@OUTPUT1@', + '--graph', + # FIXME: html output in bison 3.7.90 + ], +) + +parser = library( + 'parser', + 'parser-driver.cc', + 'parser-driver.hh', + flex.process('scanner.ll'), + bison.process('parser.yy'), +) diff --git a/src/parse/parser-driver.cc b/src/parse/parser-driver.cc new file mode 100644 index 0000000..c6df35e --- /dev/null +++ b/src/parse/parser-driver.cc @@ -0,0 +1,31 @@ +#include "parser-driver.hh" + +namespace abacus::parser { + +ParserDriver::ParserDriver() = default; + +int ParserDriver::parse(std::string filename) { + filename_ = std::move(filename); + + current_location_.initialize(&filename_); + + scan_open(); + + yy::parser parser(*this); + parser.set_debug_level(parse_trace_p_); + int res = parser.parse(); + + scan_close(); + + return res; +} + +yy::location& ParserDriver::location() { + return current_location_; +} + +yy::location const& ParserDriver::location() const { + return current_location_; +} + +} // namespace abacus::parser diff --git a/src/parse/parser-driver.hh b/src/parse/parser-driver.hh new file mode 100644 index 0000000..076ea91 --- /dev/null +++ b/src/parse/parser-driver.hh @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "parser.hh" + +namespace abacus::parser { + +class ParserDriver { +public: + ParserDriver(); + + int parse(std::string filename); + + void scan_open(); + void scan_close(); + + yy::location& location(); + yy::location const& location() const; + +private: + // FIXME: will become BigNum + int result_ = 0; + std::string filename_{}; + yy::location current_location_{}; + bool parse_trace_p_ = false; + bool scan_trace_p_ = false; +}; + +} // namespace abacus::parser diff --git a/src/parse/parser.yy b/src/parse/parser.yy new file mode 100644 index 0000000..4b5ce1b --- /dev/null +++ b/src/parse/parser.yy @@ -0,0 +1,100 @@ +// At least 3.2 to get rid of `stack.hh` +%require "3.2" +// Let's be modern +%language "C++" + +// LALR is fine for our purposes +%skeleton "lalr1.cc" + +// Write a header with our parser-related declarations +%defines + +// Safer than unions... +%define api.value.type variant +// Define constructors for each token +%define api.token.constructor + +// Ambiguity is forbidden +%expect 0 + +// Prefix all the tokens with TOK_ to avoid colisions. +%define api.token.prefix {TOK_} +// Track locations +%locations + +// Enable parse tracing +%define parse.trace +// Give detailled error messages +%define parse.error detailed +// Correct look-ahead to avoid erroneous error messages +%define parse.lac full + +// Code that should be in the header +%code requires { +// Forward declare our driver +namespace abacus::parser { +class ParserDriver; +} // namespace abacus::parser +} + +%code provides { + // Forward ParserDriver to scanner + #define YY_DECL \ + yy::parser::symbol_type yylex(::abacus::parser::ParserDriver& drv) + YY_DECL; +} + +// Only include the actual ParserDriver class declaration in source code +%code { +#include "parser-driver.hh" +} + +// Use the driver to carry context back-and-forth +%param { abacus::parser::ParserDriver& drv } + +%token EOF 0 "end-of-file" + +// FIXME:will become BigNum +%token NUM "number" + +// Use `<<` to print everything +%printer { yyo << $$; } <*>; + +%token + PLUS "+" + MINUS "-" + TIMES "*" + DIVIDE "/" + LPAREN "(" + RPAREN ")" + +// Let's define the usual PEMDAS rules +%left PLUS MINUS +%left TIMES DIVIDE +%precedence UNARY + +// FIXME: will become BigNum +%type input exp + +%% + +input: + exp EOF + ; + +exp: + NUM { $$ = $1; } + | exp PLUS exp { $$ = $1 + $3; } + | exp MINUS exp { $$ = $1 - $3; } + | exp TIMES exp { $$ = $1 * $3; } + | exp DIVIDE exp { $$ = $1 / $3; } + | PLUS exp %prec UNARY { $$ = $2; } + | MINUS exp %prec UNARY { $$ = -$2; } + | LPAREN exp RPAREN { $$ = $2; } + ; + +%% + +void yy::parser::error(location_type const& l, std::string const& m) { + std::cerr << l << ": " << m << '\n'; +} diff --git a/src/parse/scanner.ll b/src/parse/scanner.ll new file mode 100644 index 0000000..e85f9b1 --- /dev/null +++ b/src/parse/scanner.ll @@ -0,0 +1,73 @@ +%{ +#include "parser-driver.hh" +#include "parser.hh" +%} +/* Avoid warnings because of unused functions */ +%option noinput +%option nounput + +/* Enable scan tracing */ +%option debug + +/* Assume single file, do no implement `yywrap` */ +%option noyywrap + +/* Let Flex track the line numbers */ +%option yylineno + +%{ + // Run at each match + #define YY_USER_ACTION loc.columns(yyleng); +%} + +blank [ \t\r] +int [0-9]+ + +%% + +%{ + // Run each time `yylex` is called + auto& loc = drv.location(); + loc.step(); +%} + +{blank}+ loc.step(); +\n+ loc.lines(yyleng); loc.step(); + +"+" return yy::parser::make_PLUS(loc); +"-" return yy::parser::make_MINUS(loc); +"*" return yy::parser::make_TIMES(loc); +"/" return yy::parser::make_DIVIDE(loc); +"(" return yy::parser::make_LPAREN(loc); +")" return yy::parser::make_RPAREN(loc); + +{int} return yy::parser::make_NUM(std::stoi(yytext), loc); + +. { + using namespace yy; + using namespace std::string_literals; + throw parser::syntax_error(loc, "invalid character: "s + yytext); +} + +<> return yy::parser::make_EOF(loc); + +%% + +namespace abacus::parser { + +void ParserDriver::scan_open() { + yy_flex_debug = scan_trace_p_; + + if (filename_.empty() || filename_ == "-") { + yyin = stdin; + } else if ((yyin = fopen(filename_.c_str(), "r")) == nullptr) { + std::cerr << "cannot open " << filename_ << ": " << strerror(errno) << '\n'; + exit(EXIT_FAILURE); + } +} + +void ParserDriver::scan_close() { + fclose(yyin); +} + +} // namespace abacus::parser