prebirth: Abandon `define-block' in favor of `define'

Turns out, I'll kill myself before writing a Prebirth compiler in a
block-define-based Prebirth Lisp.  So, let's degrade even further into a
primitive Scheme.  This is going down a dangerous path to simply
implementing Scheme...

Nonetheless, here I remove `define-block' in favor of a simple shorthand
function definition `define', as is custom in Scheme.  We will worry
about block definitions later as metadata mapping to normal functions.
master
Mike Gerwitz 2017-08-28 00:45:34 -04:00
parent c0fb8297a6
commit f23396de2e
Signed by: mikegerwitz
GPG Key ID: 8C917B7F5DC51BA2
1 changed files with 35 additions and 85 deletions

View File

@ -293,11 +293,11 @@ class Compiler
/**
* Compile AST into ECMAScript
*
* Every block is mapped 1:1 to a function in ECMAScript. So, we just
* map all root children (which are expected to be block definitions) to
* functions.
* Every function is mapped 1:1 to a function in ECMAScript. So, we
* just map all root children (which are expected to be Scheme-style
* shorthand function definitions) to functions.
*
* @param {Array} tree root of tree containing top-level block definitions
* @param {Array} tree root containing top-level function definitions
*/
compile( tree )
{
@ -309,57 +309,45 @@ class Compiler
/**
* Compile block definition into a ES function definition
* Compile function definition into a ES function definition
*
* This will fail if the given token is not a `define-block'.
* This will fail if the given token is not a `define'.
*
* @param {Object} t token
*
* @return {string} compiled block definition
* @return {string} compiled function definition
*/
_cdfn( t )
{
this.assertApply( t, 'define-block' );
this.assertApply( t, 'define' );
// e.g. (define-block <foo> doc ((input ...)) body)
const [ , { value: name }, doc, desc, ...body ] = t;
// e.g. (define (foo ...) body)
const [ , [ { value: name }, ...params ], ...body ] = t;
const id = this._idFromName( name );
const docstr = this._docstring( doc );
const bodyjs = this._bodyToEs( body );
const id = this._idFromName( name );
const paramjs = this._paramsToEs( params );
const bodyjs = this._bodyToEs( body );
// this is the final format---each block becomes its own function
// definition
return `${docstr}\nfunction ${id}()\n{\n${bodyjs}\n};`;
// this is the final format---each function becomes its own function
// definition in ES
return `function ${id}(${paramjs})\n{\n${bodyjs}\n};`;
}
/**
* Compile docblock string
* Compile parameter list
*
* This converts to the docstring T into a docblock. No annotations are
* generated---it is output verbatim. If the docstring is the empty
* string, then the empty string is returned.
* This simply takes the value of the symbol and outputs it (formatted),
* delimited by commas.
*
* @param {string} t docstring token
* @param {Array} args token parameter list
*
* @return {string} generated docblock or the empty string
* @return {string} compiled parameter list
*/
_docstring( t )
_paramsToEs( args )
{
if ( t.type !== 'string' ) {
throw TypeError( `Expected string docblock, but found ${t.type}` );
}
const doc = t.value;
// don't bother with the docblock generation if we have nothing useful
if ( !doc ) {
return "";
}
// enclose in multi-line comment delimiters and prefix each line
return "/**\n" + doc.replace( /^/g, " * " ) + "\n */";
return args.map( ({ value: name }) => this._idFromName( name ) )
.join( ", " );
}
@ -382,7 +370,7 @@ class Compiler
* This produces a 1:1 mapping of BODY s-expressions to ES statements,
* recursively. The heavy lifting is done by `#_sexpToEs'.
*
* @param {Array} body s-expressions representing block body
* @param {Array} body s-expressions representing function body
*
* @return {string} compiled BODY
*/
@ -431,71 +419,35 @@ class Compiler
case 'string':
return `"${t.value}"`;
// symbols have the same concerns as block definitions: the
// symbols have the same concerns as function definitions: the
// identifiers generated need to be ES-friendly
case 'symbol':
return this._idFromName( t.value );
default:
throw Error( "Cannot compile unknown token `${t.type}'" );
throw Error( `Cannot compile unknown token \`${t.type}'` );
}
}
// only support block form for now, and assume that `fn' is a
// string value (in the future, this doesn't have to be the
// case---fn should be able to be an arbitrary sexp)
const [ { value: fn }, argmap ] = t;
if ( !this._isBlockForm( t ) ) {
throw Error( `\`${fn}' application is not in block form`)
}
// simple function application (fn ...args)
const [ { value: fn }, ...args ] = t;
// convert all remaining symbols (after the symbol representing the
// function application) into arguments by parsing their sexps or
// scalar values; we're not going to worry about mapping them for
// now; they will be compiled in the order in which they appear
// scalar values
const idfn = this._idFromName( fn );
const args = argmap.map( ([ , v ]) => this._sexpToEs( v ) );
const argstr = args.join( ", " );
const argstr = args.map( arg => this._sexpToEs( arg ) ).join( ", " );
// make the dangerous assumption that arguments are ordered
// for now
// final function application
return `${idfn}(${argstr})`;
}
/**
* Determine whether T represents a block form
*
* Block form is an application of a block, which has a certain
* syntax. Specifically: `(<block> ((key value) ...))'.
*
* @param {*} t hopefully a token list
*
* @return {boolean} whether T represents a block form
*/
_isBlockForm( t )
{
// the first symbol is the function name, second is an sexp
// containing each of the key/value argument mappings
const [ fn, argmap ] = t;
// enforce block id convention (at least for now)
const isblockid = /^<[^>]+>$/.test( fn.value );
return (
Array.isArray( t )
&& isblockid
&& Array.isArray( argmap )
);
}
/**
* Determine whether T is an application of a symbol NAME, or error
*
* @param {*} t hopefully a token or token list
* @param {string} name block name to assert against
* @param {string} name function name to assert against
*/
assertApply( t, name )
{
@ -554,10 +506,8 @@ class Compiler
*
* Here is an example Hello, World!:
*
* (define-block <hello-world>
* "A simple 'Hello, World!' program."
* ()
* (<js:console> ((message "Hello, world!"))))
* (define (hello x)
* (js:console "Hello," x, "!"))
*
*
* ¹ This term should invoke visuals of an abstract being entering existence