Create your own programming language transpiled to JS with FParsec
The goal will be to create your own little programming language and to implement the parser.
The transpilation code will be provided and will allow you to debug your language in Chrome.
It is obviously impossible to finish the exercise in one evening, but fun is guaranteed.
You will need FSharp programming tools:
-
Dojo inspired by Phillip Trelford
Create a small HTML page and debug your code in a browser.
You can also create a VS code plugin for your language.
Or an Electron app
Open VS code in the root directory.
You should see .vscode/tasks.json
in the top of the explorer.
samples
folder contains some examples of possible final results.
Source code of the transplier is in src/EverybodyCode/EverybodyCode
.
You can find 3 .fsx
:
-
Compile.fsx
: You can use it to understand how AST is compiled to JS. -
Parse.fsx
: You must use it to test your parsers while your are creating them. -
Parse_and_compile.fsx
: Contains minimum of need code to transpile your code. You will need to change some directories. (Not really necessary for the DOJO)
And also .fs
files:
-
PositionTacking.fs
: Contains parsing position utils -
AST.fs
: Contains Abstract syntax tree of your language. -
JsCompiler.fs
: Contains JS tranpiler. -
SourceMapper.fs
: JS sources maps generator (used by JS debuggers) -
Parser.fs
: You parsing implementation !
src/EverybodyCode/tests/EverybodyCode.Tests
contains tests of the transpiler.
(That's why it is in src/EverybodyCode
).
Clone the repo.
Navigate to src/EverybodyCode
and run:
-
npm i
: Because I use source-map -
dotnet restore
-
dotnet build
As you can see, Parser.fs
is not empty.
I put small parsers like litterals, identifier, variables names, mathematics expressions, etc ...
You will need to implement forwarded to ref parsers like:
-
pstatement
: parsing of statements. (conditions, loop, functions declarations, classes, etc ...) -
pCallExp
: parsing of function or methods calls. -
expOrOpe
: parsing operation or expression. -
pApply
: parsing of function call on an expression value.
pstatementimpl
will receive your implementation of pstatement
.
This is the most important parser.
It should be something like:
pstatementimpl :=
attempt pVarDeclare <|>
attempt pFor <|>
attempt pIf
// ...
At first, write most simple parsers like pVarDeclare
.
If we want to parse declare age = 33
.
We want to match declare[ ](extracted name "age")[ ]=[ ](extracted value 33)
Open Parse.fsx and eval following parser:
let pVarDeclare =
pstring "declare" >>. ws >>. pidentifier .>> ws .>> pchar '=' .>> ws .>>. pexpr
test pVarDeclare "declare age = 33"
Result should be:
Success: ("age", Litteral (Decimal 33.0))
-
Operator
>>.
: combine 2 parsers and keep result of the left. -
Operator
.>>.
: combine 2 parsers and keep both results in a tuple. -
Operator
|>>
: send a parser result in a function.
So we can build our AST like:
let pVarDeclare =
pstring "declare" >>. ws >>. (position .>>. pidentifier) .>> ws .>> pstring "=" .>> ws .>>. pexpr
|>> fun ((p,name),e) -> p,SetVar (true, name, AssignValue e)
Should give:
Success: ({Line = 1;
Column = 9;}, SetVar (true,"age",AssignValue (Litteral (Decimal 33.0))))
position
is a parser returning position of the result return by the combined parser.
In this project, putting positions in the AST was the most simple solution I found.
Now, the game is to implement your parsing in Parser.fs
and be able to launch debugging of your own language.
If you are in VS code, use ctrl+maj+b
and choose build compiler
.
Then you can edit .vscode/tasks.json
to add your transpilation tasks.