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:
parent
42100fe98d
commit
ff35faa705
|
@ -2,6 +2,8 @@ abacus_sources = files(
|
|||
'abacus.cc',
|
||||
)
|
||||
|
||||
subdir('parse')
|
||||
|
||||
abacus = executable(
|
||||
'abacus',
|
||||
sources: abacus_sources,
|
||||
|
|
34
src/parse/meson.build
Normal file
34
src/parse/meson.build
Normal 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'),
|
||||
)
|
31
src/parse/parser-driver.cc
Normal file
31
src/parse/parser-driver.cc
Normal 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
|
30
src/parse/parser-driver.hh
Normal file
30
src/parse/parser-driver.hh
Normal 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
100
src/parse/parser.yy
Normal 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
73
src/parse/scanner.ll
Normal 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
|
Loading…
Reference in a new issue