A simple demo language that builds upon FE.
Usage: let [options] <file>
A simple demo language that builds upon FE.
Arguments:
<file> Input file.
Options:
-h, --help Display this help and exit.
-v, --version Display version info and exit.
-d, --dump Dumps the let program again.
-e, --eval Evaluate the let program.
Diagnostics:
--loc-style <style> How a diagnostic spells out a source location: `full`
(`path:row:col-row:col`), `rowcol` (`path:row:col`),
`row` (`path:row`), or msvc (`path(row,col)`).
--no-snippet Does not render the offending source line and caret
underneath a diagnostic.
--gutter <width> Width of a diagnostic's line-number column. [default:
`5`]
--max-rows <num> Maximum number of rows a diagnostic's snippet renders
before eliding its middle; `0` elides nothing.
[default: `8`]
--max-errors <num> Maximum number of errors to report before dropping
the rest; `0` reports all of them. [default: `0`]
--werror Treats warnings as errors.
Use "-" as `<file>` to output to stdout.
Diagnostics are collected in an fe::Error that the lexer and the parser share; main hands it to fe::Error::ack, which throws it once parsing is done.
Each message prints the offending source row and underlines the columns the fe::Loc covers, renders its `code` citations in color, and may carry notes pointing at a related location:
test/error/unclosed_paren.let:1:13: error: expected `)`, got `;` while parsing parenthesized expression
1 | print (1 + 2;
| ^
test/error/unclosed_paren.let:1:7: note: unmatched `(` opened here
1 | print (1 + 2;
| ^
--no-snippet sets fe::Driver::diag.no_snippet, which drops the rows underneath and leaves just the header lines.
If you have a GitHub account setup with SSH, just do this:
git clone --recurse-submodules git@github.com:leissa/let.gitOtherwise, clone via HTTPS:
git clone --recurse-submodules https://github.com/leissa/let.gitThen, build with:
cd let
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j $(nproc)For a Release build simply use -DCMAKE_BUILD_TYPE=Release.
Invoke the interpreter like so:
./build/bin/let test/test.let -eRun the test suite via CTest:
ctest --test-dir buildInstall with:
cmake --install build --prefix /usr/localp = s ... s EOF (* program *)
;
s = ';' (* empty statement *)
| 'let' ID '=' e ';' (* let statement *)
| 'print' e ';' (* print statement *)
;
e = LIT (* literal expression *)
| ID (* identifier expression *)
| '(' e ')' (* parenthesized expression *)
| OP1 e (* unary expression *)
| e OP2 e (* binary expression *)
;where
LIT= [0-9]+ID= [a-zA-Z][a-zA-Z0-9]*OP1is one of:+,-OP2is one of:*,+,-,/
In addition, Let supports
/* C-style */and// C++-sytlecomments.
For the sake of the demo, Let is case-insensitive:
the lexer folds every identifier and keyword to lower case (so Foo, FOO, and foo denote the same name, and LET/Print are recognized as keywords).
This deliberately exercises FE's case-normalizing lexer path (fe::Lexer::accept<Append::Lower>).
Ambiguities in the expression productions are resolved according to the operator precedence that is summarized in the following table (strongest binding first):
| Operator | Description |
|---|---|
+e, -e |
unary plus, unary minus |
*, / |
multiplication, division |
+, - |
addition, subtraction |
All binary operators are left associative.
All calculations use 64-bit unsigned integer wrap-around arithmetic.
Division by zero yields zero.
Reading an identifier that was never bound by a let statement is not an error: it evaluates to zero (the name is implicitly bound to 0 on first use).