Skip to content

A call-by-push-value language and interpreter you can embed in typescript / js apps.

Notifications You must be signed in to change notification settings

gatlin/precursor-ts

Repository files navigation

precursor-ts

Precursor is a small, experimental programming language implemented as a pure TypeScript (and hence, JavaScript) library.

You can read more details below in the synopsis, you can see a working example (with I/O!) live in your browser!.

Licensed under the GPL-3 where it can be, and the WTFPL elsewhere.

documentation

API documentation & examples may be found here.

build and install from source

install dependencies and run tests

nvm use # the author recommends nvm!
npm i   # install dependencies
npm t   # run tests
npm run coverage # run tests with coverage

build the javascript distribution

npm run build

This produces two versions of the library, one each for CommonJS and ES modules:

ls dist/cjs # or dist/esm, same file names
ceskm.d.ts  ceskm.js  grammar.d.ts  grammar.js  index.d.ts  index.js  parser.d.ts  parser.js

build the documentation site

npm run docs

synopsis

import { strict as assert } from "assert";
import { CESKM, parse_cbpv, scalar } from "precursor-ts";
import type { Value } from "precursor-ts";

type Base = boolean | null | string | number;
class VM extends CESKM<Base> {
  public run(program: string): Value<Base> {
    let result = this.step(this.inject(parse_cbpv(program)));
    while (!result.done) {
      result = this.step(result.value);
    }
    return result.value;
  }
  protected literal(v: Base): Value<Base> {
    if ("number" === typeof v
     || "boolean" === typeof v
     || "string" === typeof v
     || null === v)
      { return scalar(v); }
    throw new Error(`${v} not a primitive value`);
  }
  protected op(op_sym: string, args: Value<Base>[]): Value<Base> {
    switch (op_sym) {
      case "op:add": {
        if (! ("v" in args[0]) || ! ("v" in args[1]))
          { throw new Error(`arguments must be values`); }
        if ("number" !== typeof args[0].v || "number" !== typeof args[1].v)
          { throw new Error(`arguments must be numbers`); }
        const result: unknown = args[0].v + args[1].v;
        return scalar(result as Base);
      }
      // ...
      default: return super.op(op_sym,args);
    }
  }
}

const vm = new VM();
try {
  const three = vm.run(`
    (op:add 1 2)
  `);
  assert.deepEqual(three, {
    v: 3
  });
}
catch (e) { console.error(e); }

an attempt with words

Precursor is a small programming language which you may grow and build upon (a precursor, if you like).

The default distribution consists of 3 components which work together "out of the box":

  • a small call-by-push-value language, Cbpv, defined as a data type that you can manipulate in code (grammar.ts);
  • a CESK-based evaluator which operates on Cbpv objects (ceskm.ts) ;
  • a parser for an s-expression syntax, which parses source code strings into Cbpv values(parser.ts).

You can see examples of the syntax parsed by the default parser, and more generally how to get started quickly with this library, please consult the tests.

questions / comments

You can submit bugs through the Issues feature at https://github.com/gatlin/precursor-ts .

As well you may email me at [email protected]. I reserve the right to be terrible at replying; you should absolutely not take it personally.