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
parent
c0fb8297a6
commit
f23396de2e
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue