A small sandbox to play around with some C++ parsing libraries and learn. It parses lambda calculus expressions to an AST and prints it.
Very much inspired by the build toolchain that Sorbet uses.
./bazel run //main:sandbox -- $'let f = \\x -> x in f f'
# input:  let x = \x -> x in x x
# output: Let { bind = x, what = Lam { param = x, body = Var { var = x } }, inWhere = App { f = Var { var = x }, arg = Var { var = x } } }
# if you want to generate a compile_commands.json file:
tools/scripts/build_compilation_db.shSome things I cared about
- I wrote a lot of comments
- This was a learning exercise. I don't really intend to pursue this project further (I find it way more fun to hack on existing projects than to duplicate existing efforts into my own greenfield projects). But still, I had never worked with the lexer+parser much in Sorbet, so if I had needed to build a project from scratch, I knew this part would have taken me some getting used to. So the comments are there to remind me how the whole thing works.
 
 - Bazel for the build tool
- It's what Sorbet uses
 - I'm mostly convinced all build tools are terrible; Bazel just happens to be the one that I know the most, and the one for which I know how to set up to work with libraries and my editor.
 
 - Ragel for lexing, Bison for parsing
- They're what Sorbet uses, but I wanted something simpler.
 - Both the generated Ragel lexer and Bison parser are fully self-contained C++ classes (no global variables tracking state).
 
 std::unique_ptrfor the Bison parse result- I couldn't find any examples online of a simple Bison parser that used
unique_ptr, so I wasn't sure if it would work or not (e.g., it might have been the case that it copy constructed something under the hood, but that's not the case; looks like you can use Bison with unique_ptr just fine). 
- I couldn't find any examples online of a simple Bison parser that used
 - The Ragel lexer iterates over a 
string_view::const_iteratorinstead of achar *.- This is actually pretty cool; Ragel doesn't even know that it's not
operating on a 
char *. The generated Ragel lexer just manipulates whatever variables calledp/pe/ts/teare in scope, and as long as they support the operations that it needs them to, it just compiles. Turns out that thestring_view::const_iteratorinterface supports everything Ragel needs it to. 
 - This is actually pretty cool; Ragel doesn't even know that it's not
operating on a 
 - Move semantics for strings allocated by the lexer.
- I'm told that one of the big inefficiencies in Sorbet's parser is that it
does a lot of string copying. I made the 
sandbox::parser::Nodeconstructors always takestd::string&&'s to require that they'remove'd when constructing an AST node, instead of copied. 
 - I'm told that one of the big inefficiencies in Sorbet's parser is that it
does a lot of string copying. I made the 
 - There are a lot of TODO's
- I tried to write TODOs for most of the things I like about Sorbet that I'd want to cargo cult if I ever used this as a starting point for something bigger.
 
 
Anyways, if you read this far, you might also want to look at some other things I work on:
- Sorbet
- The internals are documented and the codebase is using pretty modern C++. It's also relatively small compared to it's peer projects.
 
 - rust-lc-interp
- I used a similar project (lambda calculus interpretter) to learn about Rust. That one not only parses but also interprets the output.
 
 - stlc-infer
- At one point, I worked through an exercise from TAPL to write (naive) type inference for the simply-typed lambda calculus. It lexes, parses, type checks/infers, and interprets. It's written in Haskell.
 
 
The Rust project and the Haskell project use basically the same surface syntax, so you can get a more or less equal comparison of Ragel/Bison, Alex/Happy, and lalrpop.
- core::GlobalState / core::Context threaded through whole program.
- error reporting