Skip to content

gatlin/precursor-component

Repository files navigation

precursor-component

(c) 2021-, Gatlin Johnson.

what is all this (people ask this a lot)

PURPOSE: The purpose of this project is to show off the power and flexibility of a toy call-by-push-value programming language I wrote, Precursor.

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:

  1. 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 2: a generator pattern á la JavaScript or Python is built from scratch;
  • 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).
  1. The design and implementation of the REPL itself is intended to be instructive to anyone who wants to start building an interpreter or language.

The component UI and the virtual machine itself are completely separate; the latter could easily be adapted for embedding elsewhere.

Below is the documentation for building the code and afterward is more explanatory text.

run the example

Clone this repo, then

npm i
npm run example

Then visit http://localhost:8000/ to see it in action.

generate bundle

npm i
npm run bundle

Now 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>

quick overview of the language

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.

Positive terms:

  • literals
  • variables
  • suspended terms of any polarity
  • the result of built-in operations the language implementation provides.

Negative term : 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.

Negative terms:

  • Function literals (eg, (\ (a b) (op:multiply a b)));
  • Function application (eg, ((? square) 4.2))
  • Terms resumed with ?;
  • let expressions (not letrec);
  • any use of shift, reset, or if.

The letrec form allows you to define a number of expressions and assign them names.

(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 above letrec, even though expr-1 is a function and thus negative it is automatically suspended, which is why expr-2 resumes it first.