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 * Compile AST into ECMAScript
* *
* Every block is mapped 1:1 to a function in ECMAScript. So, we just * Every function is mapped 1:1 to a function in ECMAScript. So, we
* map all root children (which are expected to be block definitions) to * just map all root children (which are expected to be Scheme-style
* functions. * 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 ) 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 * @param {Object} t token
* *
* @return {string} compiled block definition * @return {string} compiled function definition
*/ */
_cdfn( t ) _cdfn( t )
{ {
this.assertApply( t, 'define-block' ); this.assertApply( t, 'define' );
// e.g. (define-block <foo> doc ((input ...)) body) // e.g. (define (foo ...) body)
const [ , { value: name }, doc, desc, ...body ] = t; const [ , [ { value: name }, ...params ], ...body ] = t;
const id = this._idFromName( name ); const id = this._idFromName( name );
const docstr = this._docstring( doc ); const paramjs = this._paramsToEs( params );
const bodyjs = this._bodyToEs( body ); const bodyjs = this._bodyToEs( body );
// this is the final format---each block becomes its own function // this is the final format---each function becomes its own function
// definition // definition in ES
return `${docstr}\nfunction ${id}()\n{\n${bodyjs}\n};`; 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 * This simply takes the value of the symbol and outputs it (formatted),
* generated---it is output verbatim. If the docstring is the empty * delimited by commas.
* string, then the empty string is returned.
* *
* @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' ) { return args.map( ({ value: name }) => this._idFromName( name ) )
throw TypeError( `Expected string docblock, but found ${t.type}` ); .join( ", " );
}
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 */";
} }
@ -382,7 +370,7 @@ class Compiler
* This produces a 1:1 mapping of BODY s-expressions to ES statements, * This produces a 1:1 mapping of BODY s-expressions to ES statements,
* recursively. The heavy lifting is done by `#_sexpToEs'. * 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 * @return {string} compiled BODY
*/ */
@ -431,71 +419,35 @@ class Compiler
case 'string': case 'string':
return `"${t.value}"`; 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 // identifiers generated need to be ES-friendly
case 'symbol': case 'symbol':
return this._idFromName( t.value ); return this._idFromName( t.value );
default: 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 // simple function application (fn ...args)
// string value (in the future, this doesn't have to be the const [ { value: fn }, ...args ] = t;
// 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`)
}
// 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; we're not going to worry about mapping them for // scalar values
// now; they will be compiled in the order in which they appear
const idfn = this._idFromName( fn ); const idfn = this._idFromName( fn );
const args = argmap.map( ([ , v ]) => this._sexpToEs( v ) ); const argstr = args.map( arg => this._sexpToEs( arg ) ).join( ", " );
const argstr = args.join( ", " );
// make the dangerous assumption that arguments are ordered // final function application
// for now
return `${idfn}(${argstr})`; 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 * Determine whether T is an application of a symbol NAME, or error
* *
* @param {*} t hopefully a token or token list * @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 ) assertApply( t, name )
{ {
@ -554,10 +506,8 @@ class Compiler
* *
* Here is an example Hello, World!: * Here is an example Hello, World!:
* *
* (define-block <hello-world> * (define (hello x)
* "A simple 'Hello, World!' program." * (js:console "Hello," x, "!"))
* ()
* (<js:console> ((message "Hello, world!"))))
* *
* *
* ¹ This term should invoke visuals of an abstract being entering existence * ¹ This term should invoke visuals of an abstract being entering existence