bootstrap: Formalize (both command-line and browser)
This allows bootstrapping in either a development environment (Node.js) or simply using the intended runtime environment: the user's browser. * bootstrap.html: Add file (browser). * bootstrap.js: Add file (command-line). * bootstrap/Bootstrap.js: Add class. Formalize bootstrap process. * bootstrap/libprebirth.js (fsdata): Add variable to serve as filesystem stub. (fs): Always throw error when `fs' module is unavailable. ($$js$file$_$$g$string): Consider `fsdata'. * bootstrap/prebirth.js: Export as CommonJS module if in proper environment. Abort automatic processing via stdin if root CommonJS module.master
parent
2e5b536a5d
commit
6138731304
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (C) 2017 Mike Gerwitz
|
||||
|
||||
This file is part of Gibble.
|
||||
|
||||
Gibble is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Gibble Lisp Browserstrap</title>
|
||||
<style type="text/css">
|
||||
* {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
button#start {
|
||||
display: inline-block;
|
||||
border: 0px solid black;
|
||||
background-color: transparent;
|
||||
margin-left: 10ex;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Gibble Lisp Bootstrap</h1>
|
||||
|
||||
<pre id="log">
|
||||
This is the in-browser version of the bootstrap process; it operates
|
||||
no differently than when invoked from the command-line, and has the
|
||||
benefit of being able to be run without any further dependencies
|
||||
(CLI requires Node.js, which is a hefty dependency.)
|
||||
|
||||
You do need a browser that supports ECMAScript 2015 or later, though.
|
||||
|
||||
Seeing as how it is rather rude to begin hammering your CPU when you
|
||||
first load a page (as virtually every site you visit does now-a-days),
|
||||
I'll wait for you to click the button below to get started.
|
||||
|
||||
</pre>
|
||||
|
||||
<button id="start">| ... just a sec ... |</button>
|
||||
|
||||
<noscript>
|
||||
<p>
|
||||
<strong>Bootstrapping requires JavaScript.</strong>
|
||||
All of Gibble requires JavaScript, actually.
|
||||
</p>
|
||||
</noscript>
|
||||
|
||||
<script type="text/javascript" src="bootstrap/prebirth.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/Bootstrap.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
const loge = document.getElementById( 'log' );
|
||||
const logf = ( str, e ) =>
|
||||
{
|
||||
loge.innerText += str + "\n";
|
||||
console.log( str );
|
||||
|
||||
if ( e instanceof Error ) {
|
||||
console.error( e );
|
||||
}
|
||||
};
|
||||
|
||||
const getf = path => new Promise( ( resolve, reject ) =>
|
||||
{
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.responseType = 'text';
|
||||
xhr.open( 'GET', `bootstrap/${path}`, true );
|
||||
|
||||
xhr.onload = result => resolve( result.target.responseText );
|
||||
xhr.onerror = e => reject( e );
|
||||
|
||||
xhr.send();
|
||||
} );
|
||||
|
||||
const strap = new Bootstrap( getf, logf, new Prebirth() );
|
||||
|
||||
const start = document.getElementById( 'start' );
|
||||
|
||||
start.addEventListener( 'click', ev =>
|
||||
{
|
||||
ev.target.parentElement.removeChild( ev.target );
|
||||
strap.bootstrap();
|
||||
} );
|
||||
|
||||
start.innerText = '| Start Bootstrapping! |';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Command-line bootstrap procedure for Gibble Lisp
|
||||
*
|
||||
* Copyright (C) 2017 Mike Gerwitz
|
||||
*
|
||||
* This file is part of Gibble.
|
||||
*
|
||||
* Gibble is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This script is intended to be run from the command line and requires
|
||||
* Node.js; it is more suitable to development and automated processes.
|
||||
* If you are a user or non-developer without such an environment set up,
|
||||
* then you might rather bootstrap using your web browser instead; you can
|
||||
* do so by directing your web browser to `bootstrap.html', located in the
|
||||
* same directory as this file.
|
||||
*
|
||||
* To perform the entire bootstrapping process, simply invoke this script:
|
||||
* $ node bootstrap.js
|
||||
*
|
||||
* For more information on each aspect of the process, see the individual
|
||||
* files in `./bootstrap/'.
|
||||
*/
|
||||
|
||||
const Bootstrap = require( './bootstrap/Bootstrap' );
|
||||
const Prebirth = require( './bootstrap/prebirth' );
|
||||
|
||||
const logf = ( str, e ) =>
|
||||
{
|
||||
console.log( str );
|
||||
|
||||
if ( e instanceof Error ) {
|
||||
console.error( e );
|
||||
}
|
||||
};
|
||||
|
||||
const getf = path => Promise.resolve(
|
||||
require( 'fs' ).readFileSync( `./bootstrap/${path}` ).toString()
|
||||
);
|
||||
|
||||
const strap = new Bootstrap( getf, logf, new Prebirth() );
|
||||
|
||||
strap.bootstrap();
|
|
@ -0,0 +1,361 @@
|
|||
/**
|
||||
* Bootstrap procedure for Gibble Lisp
|
||||
*
|
||||
* Copyright (C) 2017 Mike Gerwitz
|
||||
*
|
||||
* This file is part of Gibble.
|
||||
*
|
||||
* Gibble is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Ideally, the user should be able to bootstrap Gibble Lisp with nothing
|
||||
* more than what they already have installed on their computer, in the
|
||||
* environment that Gibble was designed to run in---the web
|
||||
* browser. Node.js was used during official development, but that is a
|
||||
* large system that should not be a necessary dependency---it should be
|
||||
* needed only for convenience.
|
||||
*
|
||||
* To run this process on a local development environment using Node.js, see
|
||||
* `../bootstrap.js'. To run in your web browser, see `../bootstrap.html'.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Bootstrap procedure for Gibble Lisp
|
||||
*
|
||||
* This abstracts the bootstrap process in such a way that it can be run in
|
||||
* any JavaScript environment. Notably, we need to support not only Node.js
|
||||
* (which is convenient for development and automation), but also a web
|
||||
* browser, which allows users to bootstrap using only their runtime
|
||||
* environment and no additional tools.
|
||||
*
|
||||
* Prebirth and every compiler thereafter are designed to be able to be run
|
||||
* from the command line, accepting source code on standard input. Such a
|
||||
* concept does not exist in a browser environment, and therefore cannot
|
||||
* exist here; there is an awkward abstraction to work around that.
|
||||
*/
|
||||
class Bootstrap
|
||||
{
|
||||
/**
|
||||
* Initialize bootstrap process
|
||||
*
|
||||
* The file loader `getf' must accept a path to a file to load and
|
||||
* return a Promise representing the contents of that file. The logger
|
||||
* function `logf' must accept a string message and, as an optional
|
||||
* argument an Error. `prebirth' should be `Prebirth' from
|
||||
* `prebirth.js'.
|
||||
*
|
||||
* @param {function(string):Promise} getf file loader
|
||||
* @param {function(string,Error=}} logf logger
|
||||
* @param {Prebirth} prebirth Prebirth
|
||||
*/
|
||||
constructor( getf, logf, prebirth )
|
||||
{
|
||||
this._getf = getf;
|
||||
this._logf = logf;
|
||||
this._prebirth = prebirth;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform bootstrapping process
|
||||
*
|
||||
* This compiles each of the phases of Gibble beginning with
|
||||
* Prebirth. This will evolve in complexity as we continue to move
|
||||
* forward. Each step of the process is self-verifying.
|
||||
*
|
||||
* There is currently no final result from this method other than
|
||||
* log output and an indication of success or failure; that'll change as
|
||||
* we get further along and will produce the final compiler.
|
||||
*
|
||||
* @return {undefined} nothing yet.
|
||||
*/
|
||||
bootstrap()
|
||||
{
|
||||
this._strout( 'header' );
|
||||
|
||||
this._loadPaths( [
|
||||
[ "birth.scm", "Birth" ],
|
||||
[ "libprebirth.js", "libprebirth" ],
|
||||
] )
|
||||
.then( ( [ scm, lib ] ) =>
|
||||
{
|
||||
this._strout( 'prebirthDesc' );
|
||||
|
||||
return [
|
||||
this._prebirth.compile( scm, lib ),
|
||||
scm,
|
||||
lib
|
||||
];
|
||||
} )
|
||||
.then( ( [ birthjs, scm, lib ] ) =>
|
||||
{
|
||||
this._strout( 'prebirthComplete', birthjs.length );
|
||||
this._strout( 'birthCompiled' );
|
||||
this._strout( 'birthSelfCompiling' );
|
||||
|
||||
const birth = new Function(
|
||||
'let __fsinit = this.__fsinit;' +
|
||||
'let birthout = "";\n' +
|
||||
'const console = { log: str => birthout = str + "\\n" };\n' +
|
||||
birthjs +
|
||||
"return birthout;"
|
||||
);
|
||||
|
||||
const birthout = birth.call( {
|
||||
__fsinit: { // stub fs
|
||||
"/dev/stdin": scm,
|
||||
"libprebirth.js": lib,
|
||||
},
|
||||
} );
|
||||
|
||||
if ( birthout === '' ) {
|
||||
throw Error( "Self-compilation yielded no output" );
|
||||
}
|
||||
|
||||
this._strout( 'birthVerify' );
|
||||
|
||||
if ( birthout !== birthjs ) {
|
||||
this._strout( 'birthVerifyFail' );
|
||||
|
||||
throw Error(
|
||||
"Birth self-compilation output does not match Prebirth!"
|
||||
);
|
||||
}
|
||||
|
||||
this._strout( 'birthVerifyOk' );
|
||||
|
||||
return birthout;
|
||||
} )
|
||||
.catch( e => this._error( e ) )
|
||||
.then( status =>
|
||||
this._log( "=> " + this._doneMessage( status ) )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Produce a promise for the file contents of each of `path'
|
||||
*
|
||||
* See also `#_loadPath'.
|
||||
*
|
||||
* @param {Array<string>} paths file paths
|
||||
*
|
||||
* @return {Promise} resolved with file contents or failure
|
||||
*/
|
||||
_loadPaths( paths )
|
||||
{
|
||||
return Promise.all(
|
||||
paths.map( ( [ path, desc ] ) =>
|
||||
this._loadPath( path, desc )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Produce a promise for the file contents of `path'
|
||||
*
|
||||
* This action is logged with the description `desc' and the length of
|
||||
* the result.
|
||||
*
|
||||
* This uses the loader function provided via the constructor, which
|
||||
* must return a Promise.
|
||||
*
|
||||
* @param {string} path file path
|
||||
* @param {string} desc file description for logging
|
||||
*
|
||||
* @return {Promise} promise of string file contents
|
||||
*/
|
||||
_loadPath( path, desc )
|
||||
{
|
||||
this._strout( 'loadingf', desc, path );
|
||||
|
||||
return this._getf( path )
|
||||
.then( data =>
|
||||
{
|
||||
this._strout( 'loadedf', path, data.length );
|
||||
|
||||
return data;
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Promise to log a string identified by `id'
|
||||
*
|
||||
* All given arguments in `args' will be passed to the function handling
|
||||
* that identifier.
|
||||
*
|
||||
* @param {string} id string identifier (see `_strmap')
|
||||
* @param {Array} args string arguments
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
_strout( id, ...args )
|
||||
{
|
||||
return Promise.resolve(
|
||||
this._log( this._str.apply( this, arguments ) )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a string identified by `id'
|
||||
*
|
||||
* All given arguments in `args' will be passed to the function handling
|
||||
* that identifier.
|
||||
*
|
||||
* @param {string} id string identifier (see `_strmap')
|
||||
* @param {Array} args string arguments
|
||||
*
|
||||
* @return {string} generated string
|
||||
*/
|
||||
_str( id, ...args )
|
||||
{
|
||||
const strf = Bootstrap._strmap[ id ];
|
||||
|
||||
if ( strf === undefined ) {
|
||||
throw Error( `Unknown strmap '${id}'` );
|
||||
}
|
||||
|
||||
return strf.apply( null, args );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log string using logger function
|
||||
*
|
||||
* @param {string} str string to log
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
_log( str )
|
||||
{
|
||||
this._logf( str );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log error using logger function
|
||||
*
|
||||
* `e.message' will be used as the log string, with `e' itself being
|
||||
* passed as the second argument to the logger function.
|
||||
*
|
||||
* @param {Error} e error
|
||||
*
|
||||
* @return {boolean} false
|
||||
*/
|
||||
_error( e )
|
||||
{
|
||||
const str = this._str( 'fatal', e );
|
||||
|
||||
this._logf( str, e );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return either success or failure message given `status'
|
||||
*
|
||||
* @param {boolean} status success/failure indicator
|
||||
*
|
||||
* @return {string} success/failure message
|
||||
*/
|
||||
_doneMessage( status )
|
||||
{
|
||||
return ( status === false )
|
||||
? this._str( 'fail' )
|
||||
: this._str( 'ok' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output strings in an easily accessible map
|
||||
*
|
||||
* This both keeps the code a bit more easily comprehensible by removing
|
||||
* large strings from procedural logic, and allows for future localization.
|
||||
*
|
||||
* We can do better once we get to a localization stage---Error messages
|
||||
* aren't part of this map, for example.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
Bootstrap._strmap = {
|
||||
header: () =>
|
||||
" _ _ \n" +
|
||||
" ,--0---0--. \n" +
|
||||
"| |\n" +
|
||||
"| |\n" +
|
||||
" `---------' \n",
|
||||
|
||||
loadingf: ( desc, path ) =>
|
||||
`Loading ${desc} from ${path}...`,
|
||||
|
||||
loadedf: ( path, len ) =>
|
||||
`Loaded ${path} (len=${len}).`,
|
||||
|
||||
prebirthDesc: () =>
|
||||
"\n" +
|
||||
"Prebirth is a very basic Lisp dialect with a compiler\n" +
|
||||
"implemented in ECMAScript. Birth is the same\n" +
|
||||
"compiler, but re-implemented in Prebirth Lisp.\n",
|
||||
|
||||
prebirthComplete: ( len ) =>
|
||||
`[prebirth] Compilation complete (len=${len}).`,
|
||||
|
||||
birthCompiled: () =>
|
||||
"\n" +
|
||||
"Birth has been compiled with Prebirth. Since Birth is\n" +
|
||||
"a re-implementation of Prebirth, it can now be used\n" +
|
||||
"to compile itself.\n",
|
||||
|
||||
birthSelfCompiling: () =>
|
||||
"[birth] Self-compiling Birth...",
|
||||
|
||||
birthVerify: () =>
|
||||
"[birth] Verifying self-compilation output...",
|
||||
|
||||
birthVerifyFail: () =>
|
||||
"\n" +
|
||||
"The self-compilation of Birth yielded output\n" +
|
||||
"that differs from Prebirth's compilation of Birth.\n" +
|
||||
"This verification step is a self-test to ensure\n" +
|
||||
"consistency between the two implementations.\n\n" +
|
||||
"Unfortunately, to fix this, you need to hack\n" +
|
||||
"Prebirth and/or Birth. Please report a bug!",
|
||||
|
||||
birthVerifyOk: () =>
|
||||
"[birth] Birth output matches that of Prebirth.",
|
||||
|
||||
fatal: ( e ) =>
|
||||
"\n\n!!! " + e.toString() + "\n\n" +
|
||||
"Something has gone terribly wrong!\n" +
|
||||
"See the console for a stack trace.\n\n",
|
||||
|
||||
ok: () =>
|
||||
"Bootstrap successful!",
|
||||
|
||||
fail: () =>
|
||||
"Bootstrap failed.",
|
||||
};
|
||||
|
||||
|
||||
// for use in a CommonJS (e.g. Node.js) environment
|
||||
if ( typeof module !== 'undefined' ) {
|
||||
module.exports = Bootstrap;
|
||||
}
|
|
@ -144,13 +144,27 @@ const $$js$match = ( r, s ) => s.match( r ) || false;
|
|||
const $$js$replace = ( r, repl, s ) => s.replace( r, repl );
|
||||
|
||||
|
||||
// the variable __fsinit, if defined, can be used to stub the filesystem
|
||||
const fsdata = ( typeof __fsinit !== 'undefined' ) ? __fsinit : {};
|
||||
|
||||
const fs = ( typeof require !== 'undefined' )
|
||||
? require( 'fs' )
|
||||
: { readFileSync: path => window.fsdata[ path ] };
|
||||
: {
|
||||
readFileSync( path )
|
||||
{
|
||||
throw Error( `Cannot load ${path} (no fs module)` );
|
||||
},
|
||||
}
|
||||
|
||||
// stdin->string
|
||||
// file->string
|
||||
const $$js$file$_$$g$string = ( path ) =>
|
||||
fs.readFileSync( path ).toString();
|
||||
{
|
||||
if ( fsdata[ path ] !== undefined ) {
|
||||
return fsdata[ path ];
|
||||
}
|
||||
|
||||
return fsdata[ path ] = fs.readFileSync( path ).toString();
|
||||
};
|
||||
|
||||
|
||||
/** =============== end of libprebirth =============== **/
|
||||
|
|
|
@ -688,6 +688,13 @@ class Prebirth
|
|||
}
|
||||
|
||||
|
||||
/* If we're running in a CommonJS environment (e.g. Node.js), export our
|
||||
/* entrypoint.
|
||||
*/
|
||||
if ( typeof module !== 'undefined' ) {
|
||||
module.exports = Prebirth;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prebirth was originally intended to be run via the command line using
|
||||
|
@ -696,9 +703,9 @@ class Prebirth
|
|||
*/
|
||||
( function ()
|
||||
{
|
||||
if ( typeof process === 'undefined' ) {
|
||||
return;
|
||||
}
|
||||
if ( ( typeof process === 'undefined' )
|
||||
|| ( ( typeof module !== 'undefined' ) && ( module.id !== '.' ) ) )
|
||||
return;
|
||||
|
||||
const fs = require( 'fs' );
|
||||
const src = fs.readFileSync( '/dev/stdin' ).toString();
|
||||
|
|
Loading…
Reference in New Issue