abacus: add parse library

Would need some improvements, such as by using the C++ Flex scanner
interface.

It will also need to start using the BigNum once it has been
implemented.
This commit is contained in:
Bruno BELANYI 2021-08-20 02:05:53 +02:00
parent 42100fe98d
commit ff35faa705
6 changed files with 270 additions and 0 deletions

View file

@ -2,6 +2,8 @@ abacus_sources = files(
'abacus.cc',
)
subdir('parse')
abacus = executable(
'abacus',
sources: abacus_sources,

34
src/parse/meson.build Normal file
View file

@ -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'),
)

View file

@ -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

View file

@ -0,0 +1,30 @@
#pragma once
#include <string>
#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

100
src/parse/parser.yy Normal file
View file

@ -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 <int> 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 <int> 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';
}

73
src/parse/scanner.ll Normal file
View file

@ -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);
}
<<EOF>> 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