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
|
* 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
|
||||||
|
|
Loading…
Reference in New Issue