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',
|
'abacus.cc',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subdir('parse')
|
||||||
|
|
||||||
abacus = executable(
|
abacus = executable(
|
||||||
'abacus',
|
'abacus',
|
||||||
sources: abacus_sources,
|
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