From ef4f70bf96977d841528188777b59373d035663c Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 29 Aug 2017 01:42:15 -0400 Subject: [PATCH] prebirth: Initial function map The Prebirth JS compiler is getting much more sophisticated than I had hoped, but I need something to work with here. This starts to get us in a good spot. There are other conveniences I will want. More to come. * build-aux/bootstrap/prebirth.js (Compiler#constructor): Add method, accept fnmap. (Compiler#_sexpToEs): Use fnmap as needed. (fnmap): Add initial map. Instantiate `Compiler' with fnmap. --- build-aux/bootstrap/prebirth.js | 79 ++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/build-aux/bootstrap/prebirth.js b/build-aux/bootstrap/prebirth.js index 83728b2..3ddf8a6 100644 --- a/build-aux/bootstrap/prebirth.js +++ b/build-aux/bootstrap/prebirth.js @@ -300,6 +300,21 @@ class Parser */ class Compiler { + /** + * Initialize with function map + * + * The function map will be used to map certain functions into other + * names or forms. For example, `js:console' may map to `console.log' + * and `if' to an `if' statement+expression. + * + * @param {Object} fnmap function map + */ + constructor( fnmap ) + { + this._fnmap = fnmap; + } + + /** * Compile AST into ECMAScript * @@ -484,10 +499,22 @@ class Compiler // simple function application (fn ...args) const [ { value: fn }, ...args ] = t; + const mapentry = this._fnmap[ fn ]; + + // if the fnmap contains a function entry, then it will handle the + // remaining processing + if ( mapentry && ( typeof mapentry === 'function' ) ) { + return mapentry( + args, + this._sexpToEs.bind( this ), + this._bodyToEs.bind( this ) + ); + } + // convert all remaining symbols (after the symbol representing the // function application) into arguments by parsing their sexps or // scalar values - const idfn = this._idFromName( fn, true ); + const idfn = mapentry || this._idFromName( fn, true ); const argstr = args.map( arg => this._sexpToEs( arg ) ).join( ", " ); // final function application @@ -496,6 +523,54 @@ class Compiler } +/** + * Function aliases and special forms + * + * This map allows for a steady transition---items can be removed as they + * are written in Prebirth Lisp. This should give us a sane (but still + * simple) environment with which we can start to self-host. + * + * String values are simple function aliases. Function values take over the + * compilation of that function and allow for defining special forms. The + * first argument to the function is the list of raw arguments (not yet + * compiled); the second argument is `Compiler#_sexpToEs'; and the third is + * `Compiler#bodyToEs'. + * + * These are by no means meant to be solid implementations. + * + * @type {Object} + */ +const fnmap = { + 'js:console': 'console.log', + + // simple if statement with optional else + 'if': ( [ pred, t, f ], stoes ) => + `if (${stoes(pred)}) {${stoes(t)};} ` + + ( ( f === undefined ) ? '' : `else {${stoes(f)};}` ), + + // (let ((binding val) ...) ...body), compiled as a self-executing + // function which allows us to easily represent the return value of the + // entire expression while maintaining local scope + 'let*': ( [ bindings, ...body ], stoes, btoes ) => + "(function(){\n" + + bindings + .map( ([ x, val ]) => ` const ${stoes(x)} = ${stoes(val)};\n` ) + .join( '' ) + + btoes( body ) + "\n" + + " })()", + + // similar to the above, but variables cannot reference one-another + 'let': ( [ bindings, ...body ], stoes, btoes ) => + "(function(" + + bindings.map( ([ x ]) => stoes( x ) ).join( ", " ) + + "){\n" + + btoes( body ) + "\n" + + "})(" + + bindings.map( ([ , val ]) => stoes( val ) ).join( ", " ) + + ")", +}; + + /* * Prebirth was originally intended to be run via the command line using @@ -510,7 +585,7 @@ class Compiler } const p = new Parser(); - const c = new Compiler(); + const c = new Compiler( fnmap ); const fs = require( 'fs' ); const src = fs.readFileSync( '/dev/stdin' ).toString();