This domain-specific language (DSL) allows writing and evaluating basic arithmetic expressions in prefix-notation, i.e. Polish notation.
The equation 1+2*3
would be expressed as: (+ 1 (* 2 3))
.
Note that the parethesis make the order of execution explicit, so there is no order-of-precedence amongst functions.
This project was built using Langium, a language engineering tool. The grammar for the language is defined in src/language/calculator.langium. Langium uses this grammar definition to generate a tokenizer, parser, and VS code extension with syntax highlighting and auto-completion for the language. After everything has been generated, langium is able to parse a document written in the 'calculator language' and generate an abstract syntax tree for it.
An example document in the calculator language:
(+ 1 2)
The abstract syntax tree (AST), exported to JSON:
{
"$type": "Model",
"expressions": [
{
"$type": "Application",
"operator": {
"$type": "Binary",
"value": "+"
},
"arguments": [
{
"$type": "Integer",
"value": 1
},
{
"$type": "Integer",
"value": 2
}
]
},
]
}
To get executable code, the AST must be translated into a language which we can execute. The hand-written code generator can be found in src/cli/generator.ts. It takes any 'calculator AST' and converts (transpiles) it to the corresponding C++ code.
The generated C++ code:
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <functional>
int main()
{
std::cout << std::to_string(std::plus<>{}(1, 2)) << std::endl;
return EXIT_SUCCESS;
}
The C++ code can then be compiled and executed to evaluate the calculator expressions.
C++ was chosen as the target language because it's fast and provides useful abstractions.
This makes it easier to develop and debug the code generator, than if LLVM IR or WebAssembly was chosen as the target language.
Function objects from the functional header were used, instead of the inbuild operator+
, for example.
This maintains a more consistent syntax even with functions for which there are no inbuilt operators in C++.
Generators could be written for other languages as well. JavaScript is a very attractive target language because an IDE and the entire toolchain, including execution of the code, could be run inside the browser. The langauge could then be used by simply visiting the right website. The IDE and execution environment would run locally on the client (in the browser) and not on the server, which ensures the IDE is very responsive and hosting costs stay reasonable. For the time being, that approach was not chosen here though.
- Setup Langium on your machine: https://www.npmjs.com/package/langium
- Clone this repo
- Build using the task in vscode (or execute
npm run langium:generate && npm run build
in a terminal) - Run the resulting program in vscode, to start a new vscode window which has the extension for 'Calculator' loaded
- In this new vscode window, open a .calc file in the example/ directory to try out syntax highlighting and auto-completion
Ensure you have langium and gcc (or another C++ compiler) installed on your Unix machine and run the following commands:
npm run langium:generate && \
npm run build && \
node bin/cli.js toJSON examples/example.calc out/generated.json && \
node bin/cli.js toCPP examples/example.calc out/generated.cpp && \
g++ -o out/generated out/generated.cpp && \
./out/generated