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.
master
Mike Gerwitz 2017-08-29 01:42:15 -04:00
parent 61f6ba1470
commit ef4f70bf96
Signed by: mikegerwitz
GPG Key ID: 8C917B7F5DC51BA2
1 changed files with 77 additions and 2 deletions

View File

@ -300,6 +300,21 @@ class Parser
*/ */
class Compiler 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 * Compile AST into ECMAScript
* *
@ -484,10 +499,22 @@ class Compiler
// simple function application (fn ...args) // simple function application (fn ...args)
const [ { value: fn }, ...args ] = t; 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 // convert all remaining symbols (after the symbol representing the
// function application) into arguments by parsing their sexps or // function application) into arguments by parsing their sexps or
// scalar values // scalar values
const idfn = this._idFromName( fn, true ); const idfn = mapentry || this._idFromName( fn, true );
const argstr = args.map( arg => this._sexpToEs( arg ) ).join( ", " ); const argstr = args.map( arg => this._sexpToEs( arg ) ).join( ", " );
// final function application // 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 * Prebirth was originally intended to be run via the command line using
@ -510,7 +585,7 @@ class Compiler
} }
const p = new Parser(); const p = new Parser();
const c = new Compiler(); const c = new Compiler( fnmap );
const fs = require( 'fs' ); const fs = require( 'fs' );
const src = fs.readFileSync( '/dev/stdin' ).toString(); const src = fs.readFileSync( '/dev/stdin' ).toString();