(c) 2021-, Gatlin Johnson.
Precursor is written specifically to be embedded in other applications. By itself though it is not a complete interpreter; thus I have built a web component containing a REPL interpreter for the language which you may visit and play with!
Building a REPL accomplishes my purpose in two basic ways:
- The examples demonstrate how features enjoyed in other programming languages can be written straightforwardly with Precursor's operators (even though the grammar is small):
- sample 1: a haphazard overview of some basic language features;
- sample 3: an Actor is created that safely encapsulates state mutation;
- sample 4: a list data type is defined and then a list of numbers is reduced to a sum;
- sample 5: I define an extensible effect system to write asynchronous I/O applications, and then do so!
- and of course you can modify them (or write your own).
- The design and implementation of the REPL itself is intended to be instructive to anyone who wants to start building an interpreter or language.
Below is the documentation for building the code and afterward is more explanatory text.
Clone this repo, then
npm i npm run example
Then visit http://localhost:8000/ to see it in action.
npm i npm run bundle
precursor.component.bundled.js will exist in the project root.
You can include this in HTML documents to use the component.
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <script type="module" src="precursor.component.bundled.js"> </script> <body> <precursor-component> (op:writeln "hello there") </precursor-component> </body> </html>
Precursor's grammar is unusual; the examples should cover just about everything but it may be helpful to read these notes (WIP, sadly).
There is no substitute for the Call-By-Push-Value paper but I can summarize the main ideas.
In CBPV terms are polarized either positive or negative.
Positive term : Static data with no behavior. Positive terms are fully evaluated, passed as arguments, and bound to variable symbols.
- suspended terms of any polarity
- the result of built-in operations the language implementation provides.
: Dynamic behavior; a term in need of further evaluation. Must be suspended
into a positive term with
! to be manipulated as an argument or variable;
it may be resumed with
? later to continue execution.
- Function literals (eg,
(\ (a b) (op:multiply a b)));
- Function application (eg,
((? square) 4.2))
- Terms resumed with
- any use of
letrec form allows you to define a number of expressions and assign them
(letrec ( ; definitions begin (expr-1 (\ (foo bar) ...)) (expr-2 ((? expr-1) "foo-value" 4)) ... ; definitions end ) ; body begin (let baz ( (? expr-2) ...) ... ) ; body end )
Every term defined in a given
letrec is visible to all the others.
Only terms defined at the top of a
letrec may be recursive, and they may
reference symbols defined in scopes outside the
letrec as well as any sibling
terms defined along with it.
Finally, all variables in Precursor must contain positive terms; in the
letrec, even though
expr-1 is a function and thus negative it is
automatically suspended, which is why
expr-2 resumes it first.