/** This module contains classes and
* [mix-ins](https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/)
* to compile various stages of a little language
* using builder actions to represent programs as trees
* and visitors to interpret, check types, and compile.
* The details are discussed in [chapter 11]{@tutorial 11-trees} and [appendix D]{@tutorial d-kit}.
*
* A tree consists of *nodes* represented as nested `Array` objects,
* each consisting of a string *tag* selecting a method of a *visitor*,
* followed by zero or more nodes or other argument values.
* `visit` cannot be a tag.
*
* A tree node can have `.lineno` and `.type` properties referring to the
* source line number and the type (`bool`, `number`, or `string`) delivered
* by the node. {@linkcode module:Eleven~Visit#_dump visitor._dump(node)} displays
* the properties if they exist.
*
* The module assumes that there are `globalThis` definitions
* of `puts()` for output and `prompt()` for num which are available to `eval()`.
*
* @module Eleven
* @author © 2023 Axel T. Schreiner <axel@schreiner-family.net>
* @version 2024-08-04
*/
import * as EBNF from './ebnf.js';
import * as BNF from './bnf.js';
import * as Six from './06.js';
// --- 11/01
/** Base class for tree building. Private method names start with an underscore.
@class
@property {module:Base~Parser} parser - access to source position.
*/
class Build {
/** Sets the property */
constructor (parser) { this.parser = parser; }
/** Tags node with source position as `.lineno` if available. */
_lineno (node) {
if (this.parser.current && this.parser.current.lineno)
node.lineno = this.parser.current.lineno;
return node;
}
}
/** Class actions to represent an arithmetic expression as a tree using recursive descent.
@mixin
@see [example 6/07](../?eg=06/07)
*/
const Build_RD = superclass => class extends superclass {
/** `list: sum [{ ',' sum }];` returns `[ 'list' sum ... ]`
@memberof module:Eleven~Build_RD
@instance */
list (sum, many) {
return [ 'list', sum ].concat(many ? many[0].map(alt => alt[1]) : []);
}
/** `sum: product [{ add | subtract }];` returns tree
@memberof module:Eleven~Build_RD
@instance */
sum (product, many) { return (many ? many[0] : []).
reduce((sum, alt) => (alt[0][1] = sum, alt[0]), product);
}
/** `add: '+' product;` returns `[ 'add' null product ]`
@memberof module:Eleven~Build_RD
@instance */
add (x, right) { return [ 'add', null, right ]; }
/** `subtract: '-' product;` returns `[ 'subtract' null product ]`
@memberof module:Eleven~Build_RD
@instance */
subtract (x, right) { return [ 'subtract', null, right ]; }
/** `product: signed [{ multiply | divide }];` returns tree
@memberof module:Eleven~Build_RD
@instance */
product (signed, many) { return (many ? many[0] : []).
reduce((product, alt) => (alt[0][1] = product, alt[0]), signed);
}
/** `multiply: '*' signed;` returns `[ 'multiply' null signed ]`
@memberof module:Eleven~Build_RD
@instance */
multiply (x, right) { return [ 'multiply', null, right ]; }
/** `divide: '/' signed;` returns `[ 'divide' null signed ]`
@memberof module:Eleven~Build_RD
@instance */
divide (x, right) { return [ 'divide', null, right ]; }
/** `signed: [ '-' ] term;` returns `term` or `[ 'minus' term ]`
@memberof module:Eleven~Build_RD
@instance */
signed (minus, term) { return minus ? [ 'minus', term ] : term; }
/** `term: number | name | '(' sum ')';` returns tree
@memberof module:Eleven~Build_RD
@instance */
term (...val) { return val.length == 1 ? val[0] : val[1] }
/** `number: Number;` returns `[ 'number' number ]`
@memberof module:Eleven~Build_RD
@instance */
number (number) { return [ 'number', parseInt(number, 10) ]; }
};
// --- 11/02
/** Class actions to represent an arithmetic expression as a tree using a precedence table.
@mixin
*/
const Build_Number = superclass => class extends superclass {
/** `expr: add | ... | '(' expr ')' | number;` returns tree
@memberof module:Eleven~Build_Number
@instance */
expr (...values) { return values.length > 1 ? values[1] : values[0]; }
/** `add: expr '+' expr;` returns `[ 'add' a b ]`
@memberof module:Eleven~Build_Number
@instance */
add (a, x, b) { return this._lineno([ 'add', a, b ]); }
/** `subtract: expr '-' expr;` returns `[ 'subtract' a b ]`
@memberof module:Eleven~Build_Number
@instance */
subtract (a, x, b) { return this._lineno([ 'subtract', a, b ]); }
/** `multiply: expr '*' expr;` returns `[ 'multiply' a b ]`
@memberof module:Eleven~Build_Number
@instance */
multiply (a, x, b) { return this._lineno([ 'multiply', a, b ]); }
/** `divide: expr '/' expr;` returns `[ 'divide' a b ]`
@memberof module:Eleven~Build_Number
@instance */
divide (a, x, b) { return this._lineno([ 'divide', a, b ]); }
/** `power: expr '**' expr;` returns `[ 'power' a b ]`
@memberof module:Eleven~Build_Number
@instance */
power (a, x, b) { return this._lineno([ 'power', a, b ]); }
/** `minus: '-' expr;` returns `[ 'minus' b ]`
@memberof module:Eleven~Build_Number
@instance */
minus (x, b) { return this._lineno([ 'minus', b ]); }
/** `number: Number;` returns `[ 'number' number ]`
@memberof module:Eleven~Build_Number
@instance */
number (number) { return this._lineno([ 'number', parseInt(number, 10) ]); }
};
// --- 11/03
/** Base class with methods to validate and visit a tree.
Private method names start with an underscore.
@class
@property {RegExp} trace - selects matching tags to trace visits.
@property {number} errors - count of calls to `_error()`.
*/
class Visit {
trace = false; // RegExp selects tags to display
errors = 0; // counts calls to _error()
/** Displays and counts an error message. */
_error (lno, ... s) {
if (typeof lno == 'number' && lno > 0) lno = `line ${lno}:`;
else lno = s.splice(0, 1)[0];
puts(`error ${++ this.errors}: ${lno}`, ... s);
}
/** Recursively checks tree tags, throws an error if there is a problem.
@param {Array} node - to validate. */
_tree (node) { // recursively validates a tree
if (!(node instanceof Array)) return;
if (!node.length) throw 'empty node';
if (typeof node[0] != 'string') throw 'node tag is not a string';
if (!node[0].length) throw 'empty node tag';
if (node[0] == 'visit') throw "'visit' cannot be a tag";
if (typeof this.constructor.prototype[node[0]] != 'function')
throw node[0] + ': unknown node tag';
node.slice(1).forEach(node => this._tree(node));
}
/** Creates a deep display of a node.
@param {Array} node - to recursively traverse.
@param {number} [shallow] - limits depth if non-negative, by default unlimited. */
_dump (node, shallow = -1) { // recursively dumps a tree
if (!(node instanceof Array))
switch (typeof node) {
case 'boolean':
case 'number': return node;
case 'string': return "'" + node.replace(/(['\\\n])/g, "\\$1") + "'";
default: return typeof node;
}
let result = '[ ' + (!shallow ? this._dump(node[0]) :
node.map(item => this._dump(item, shallow - 1)).join(' ')) + ' ]';
if ('lineno' in node) result += '.' + node.lineno;
if ('type' in node) result += ':' + node.type;
return result;
}
/** Visits a (valid!) tree node, returns either `node` itself or the result of calling tag as method for an array.
@param {Array} node - to visit, using the tag as a method name and the node as argument.
@param {RegExp} [trace] - sets `.trace` if specified. */
visit (node, trace) {
if (trace instanceof RegExp) this.trace = trace;
// not a list: return it
if (!(node instanceof Array)) return node;
// visit
let result;
const show = this.trace instanceof RegExp &&
this.trace.test(node[0]) ? this._dump(node, 0) : false;
try {
return result = this.constructor.prototype[node[0]].call(this, node);
} finally {
if (show) puts(show, 'returns', this._dump(result, 1));
}
}
}
/** Methods to evaluate arithmetic expressions.
All of these expect `Number` values and return a `Number` result.
@mixin
*/
const Eval_Number = superclass => class extends superclass {
/** `[ 'add' a b ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
add (node) { return this.visit(node[1]) + this.visit(node[2]); }
/** `[ 'subtract' a b ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
subtract (node) { return this.visit(node[1]) - this.visit(node[2]); }
/** `[ 'multiply' a b ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
multiply (node) { return this.visit(node[1]) * this.visit(node[2]); }
/** `[ 'divide' a b ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
divide (node) { return this.visit(node[1]) / this.visit(node[2]); }
/** `[ 'power' a b ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
power (node) { return this.visit(node[1]) ** this.visit(node[2]); }
/** `[ 'minus' a ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
minus (node) { return - this.visit(node[1]); }
/** `[ 'number' a ]`; returns `Number`
@memberof module:Eleven~Eval_Number
@instance */
number (node) { return this.visit(node[1]); }
};
/** Class actions for top-level rules to display and visit a tree.
* For the `main` rule:
** Add optional arguments with visitor classes.
** Add an optional `RegExp` argument to display the tree between visits and trace each visit.
* @mixin
*/
const Main = (superclass, ...args) => class extends superclass {
/** Create and apply all but the last visitor, check the last.
@return {Array} checked last visitor, last tree, trace if any.
@throws {string} error message, e.g., error count or `_tree` issue.
@memberof module:Eleven~Main
@instance */
_doVisits (tree, args) {
let trace; // (first) trace pattern, if any
const visitors = args.filter(arg => { // remove patterns
if (!(arg instanceof RegExp)) return true;
if (!trace) trace = arg;
return false;
}),
tail = visitors.splice(-1, 1); // last visitor, others
if (!tail.length) throw 'main: no visitors';
let caller; // each visitor is constructed with caller
[tree, caller] = visitors.reduce(([tree, caller], Visitor) => {
const visitor = new Visitor (caller); // create next visitor
visitor._tree(tree); // validate tree
tree = visitor.visit(tree, trace); // visit
if (trace) { puts(visitor._dump(tree)); } // trace, if any
if (visitor.errors) throw `${visitor.errors} error(s)`;
return [tree, visitor]; // done; next visit if any
}, [tree, this]); // first caller is the builder
const lastVisitor = new tail[0](caller); // last visitor object
lastVisitor._tree(tree); // validate tree
return [ lastVisitor, tree, trace ];
}
/** `main: tree;` return the checked last visit as a function.
@throws {string} error message, e.g., error count or `_tree` issue.
@memberof module:Eleven~Main
@instance */
main (tree) {
let [lastVisitor, lastTree, trace] = this._doVisits(tree, args);
return () => lastVisitor.visit(lastTree, trace);
}
/** `dump: tree;` Display and return the tree.
@memberof module:Eleven~Main
@instance */
dump (tree) {
puts(new Visit()._dump(tree));
return tree;
}
/** `run: funct;` Execute the function and return the result.
@memberof module:Eleven~Main
@instance */
run (funct) { return funct(); }
};
// --- 11/04
/** Class actions to represent a list of statements as a tree.
@mixin
*/
const Build_Stmts = superclass => class extends superclass {
/** `stmts: stmt [{ ';' stmt }];` returns `stmt` or `[ 'stmts' stmt ... ]`
@memberof module:Eleven~Build_Stmts
@instance */
stmts (stmt, many) {
return many == null ? stmt :
this._lineno([ 'stmts',
...many[0].reduce(
(stmts, alt) => { stmts.push(alt[1]); return stmts; },
[ stmt ])
]);
}
/** `stmt: print | ...;` returns tree
@memberof module:Eleven~Build_Stmts
@instance */
stmt (stmt) { return stmt; }
/** `print: 'print' expr [{ ',' expr }];` returns `[ 'print' expr ... ]`
@memberof module:Eleven~Build_Stmts
@instance */
print (x, expr, many) {
return this._lineno([ 'print',
...(many ? many[0] : [ ]).reduce(
(exprs, alt) => { exprs.push(alt[1]); return exprs; },
[ expr ])
]);
}
/** `loop: 'while' expr 'do' stmts 'od';` returns `[ 'loop' expr stmts ]`
@memberof module:Eleven~Build_Stmts
@instance */
loop (w, expr, d, stmts, o) {
return this._lineno([ 'loop', expr, stmts ]);
}
/** `select: 'if' expr 'then' stmts [ 'else' stmts ] 'fi';` returns `[ 'select' expr left right? ]`
@memberof module:Eleven~Build_Stmts
@instance */
select (i, expr, t, left, opt, f) {
const result = this._lineno([ 'select', expr, left ]);
if (opt) result.push(opt[1]); return result;
}
};
/** Class actions to represent names in a tree.
@mixin
*/
const Build_Names = superclass => class extends superclass {
/** `assign: Name '=' expr;` returns `[ 'assign' name expr ]`
@memberof module:Eleven~Build_Names
@instance */
assign (name, x, expr) {
return this._lineno([ 'assign', name, expr ]);
}
/** `name: Name;` returns `[ 'name' name ]`
@memberof module:Eleven~Build_Names
@instance */
name (name) { return this._lineno([ 'name', name ]); }
};
/** Class actions to represent comparisons as trees.
@mixin
*/
const Build_Cmps = superclass => class extends superclass {
/** `eq: expr '=' expr;` returns `[ 'eq' a b ]`
@memberof module:Eleven~Build_Cmps
@instance */
eq (a, x, b) { return this._lineno([ 'eq', a, b ]); }
/** `ne: expr '<>' expr;` returns `[ 'ne' a b ]`
@memberof module:Eleven~Build_Cmps
@instance */
ne (a, x, b) { return this._lineno([ 'ne', a, b ]); }
/** `gt: expr '>' expr;` returns `[ 'gt' a b ]`
@memberof module:Eleven~Build_Cmps
@instance */
gt (a, x, b) { return this._lineno([ 'gt', a, b ]); }
/** `ge: expr '>=' expr;` returns `[ 'ge' a b ]`
@memberof module:Eleven~Build_Cmps
@instance */
ge (a, x, b) { return this._lineno([ 'ge', a, b ]); }
/** `lt: expr '<' expr;` returns `[ 'lt' a b ]`
@memberof module:Eleven~Build_Cmps
@instance */
lt (a, x, b) { return this._lineno([ 'lt', a, b ]); }
/** `le: expr '<=' expr;` returns `[ 'le' a b ]`
@memberof module:Eleven~Build_Cmps
@instance */
le (a, x, b) { return this._lineno([ 'le', a, b ]); }
};
// --- 11/05
/** Methods to interpret comparisons.
All of these expect `Number` values and return a `Boolean` result.
@mixin
*/
const Eval_Cmps = superclass => class extends superclass {
/** `[ 'eq' a b ]`; returns `Boolean`.
@memberof module:Eleven~Eval_Cmps
@instance */
eq (node) { return this.visit(node[1]) == this.visit(node[2]); }
/** `[ 'ne' a b ]`; returns `Boolean`.
@memberof module:Eleven~Eval_Cmps
@instance */
ne (node) { return this.visit(node[1]) != this.visit(node[2]); }
/** `[ 'gt' a b ]`; returns `Boolean`.
@memberof module:Eleven~Eval_Cmps
@instance */
gt (node) { return this.visit(node[1]) > this.visit(node[2]); }
/** `[ 'ge' a b ]`; returns `Boolean`.
@memberof module:Eleven~Eval_Cmps
@instance */
ge (node) { return this.visit(node[1]) >= this.visit(node[2]); }
/** `[ 'lt' a b ]`; returns `Boolean`.
@memberof module:Eleven~Eval_Cmps
@instance */
lt (node) { return this.visit(node[1]) < this.visit(node[2]); }
/** `[ 'le' a b ]`; returns `Boolean`.
@memberof module:Eleven~Eval_Cmps
@instance */
le (node) { return this.visit(node[1]) <= this.visit(node[2]); }
};
/** Methods to interpret a list of statements.
@mixin
*/
const Eval_Stmts = superclass => class extends superclass {
/** `[ 'stmts' stmt ... ]`
@memberof module:Eleven~Eval_Stmts
@instance */
stmts (node) { node.slice(1).forEach(stmt => this.visit(stmt)); }
/** `[ 'print' value ... ]`
@memberof module:Eleven~Eval_Stmts
@instance */
print (node) { puts(...node.slice(1).map(value => this.visit(value))); }
/** `[ 'loop' cond stmt ]`
@memberof module:Eleven~Eval_Stmts
@instance */
loop (node) { while (this.visit(node[1])) this.visit(node[2]); }
/** `[ 'select' cond then else? ]`
@memberof module:Eleven~Eval_Stmts
@instance */
select (node) {
if (this.visit(node[1])) this.visit(node[2]);
else if (node.length > 3) this.visit(node[3]);
}
};
/** Mixin with a symbol table.
Private method names start with an underscore.
@property {Map} symbols - symbol table, maps names to descriptions;
imported from previous visitor, if any.
@mixin
*/
const Symbols = superclass => class extends superclass {
/** Creates the `Map` for symbol descriptions or gets it from the previous processor.
@name constructor
@param {Object} prev - previous visitor or builder.
@memberof module:Eleven~Symbols
@instance */
constructor (prev, ... more) {
super(prev, ... more);
this.symbols = prev?.symbols ?? new Map ();
}
/** Returns a name's description, if necessary creates it.
@param {string} name - to allocate.
@returns {ord:number} description which indicates creation order, starting at 1.
@memberof module:Eleven~Symbols
@instance */
_alloc (name) {
let symbol = this.symbols.get(name); // check if exists
if (!symbol) // create with ordinal
this.symbols.set(name,
symbol = { ord: this.symbols.size + 1 });
return symbol;
}
};
/** Method to interpret names, requires {@linkcode module:Eleven~Symbols Symbols}.
@mixin
*/
const Eval_Names = superclass => class extends superclass {
/** `[ 'name' name ]` returns the stored value.
@memberof module:Eleven~Eval_Names
@instance */
name (node) {
const symbol = this._alloc(node[1]);
if (!('value' in symbol)) symbol.value = 0;
return symbol.value;
}
/** `[ 'assign' name value ]`
@memberof module:Eleven~Eval_Names
@instance */
assign (node) { this._alloc(node[1]).value = this.visit(node[2]); }
};
// --- 11/06
/** Class actions to represent Boolean expressions as trees.
@mixin
*/
const Build_Bool = superclass => class extends superclass {
/** `or: expr 'or' expr;` returns `[ 'or' a b ]`
@memberof module:Eleven~Build_Bool
@instance */
or (a, x, b) { return this._lineno([ 'or', a, b ]); }
/** `and: expr 'and' expr;` returns `[ 'and' a b ]`
@memberof module:Eleven~Build_Bool
@instance */
and (a, x, b) { return this._lineno([ 'and', a, b ]); }
/** `not: 'not' expr;` returns `[ 'not' b ]`
@memberof module:Eleven~Build_Bool
@instance */
not (x, b) { return this._lineno([ 'not', b ]); }
/** `bool: 'true' | 'false';` returns `[ 'bool' bool ]`
@memberof module:Eleven~Build_Bool
@instance */
bool (bool) { return this._lineno([ 'bool', bool == 'true' ]); }
};
/** Class actions to represent string expressions as trees.
@mixin
*/
const Build_String = superclass => class extends superclass {
/** `input: 'input' [ String String ];` returns `[ 'input' unescaped-string unescaped-string ]`
@memberof module:Eleven~Build_String
@instance */
input (i, opt) {
return (opt ? opt : [ ]).
reduce((r, s) =>
(r.push(s.slice(1, -1).replace(/\\(.)/g, '$1')), r),
[ 'input' ]);
}
/** `len: 'len' expr;` returns `[ 'len' b ]`
@memberof module:Eleven~Build_String
@instance */
len (x, b) { return this._lineno([ 'len', b ]); }
/** `string: String;` returns `[ 'string' unescaped-string ]`
@memberof module:Eleven~Build_String
@instance */
string (string) {
return this._lineno([ 'string',
string.slice(1, -1).replace(/\\(.)/g, '$1') ]);
}
};
/** Class action to represent type casts as trees.
@mixin
*/
const Build_Cast = superclass => class extends superclass {
/** `type: 'bool' | 'number' | 'string';` returns `type`
@memberof module:Eleven~Build_Cast
@instance */
type (type) { return type; }
/** `cast: '(' type ')' expr;` returns `[ 'cast' type b ]`
@memberof module:Eleven~Build_Cast
@instance */
cast (l, type, r, b) { return this._lineno([ 'cast', type, b ]); }
};
/** Methods to interpret Boolean expressions.
@mixin
*/
const Eval_Bool = superclass => class extends superclass {
/** `[ 'or' a b ]` returns `Boolean`.
@memberof module:Eleven~Eval_Bool
@instance */
or (node) {
return node.slice(1).reduce((result, tree) => {
if (result) return result; // short-circuit
result = this.visit(tree);
if (typeof result != 'boolean')
this._error(node.lineno, "'or' non-boolean");
return result;
}, false);
}
/** `[ 'and' a b ]` returns `Boolean`.
@memberof module:Eleven~Eval_Bool
@instance */
and (node) {
return node.slice(1).reduce((result, tree) => {
if (!result) return result; // short-circuit
result = this.visit(tree);
if (typeof result != 'boolean')
this._error(node.lineno, "'and' non-boolean");
return result;
}, true);
}
/** `[ 'not' b ]` returns `Boolean`.
@memberof module:Eleven~Eval_Bool
@instance */
not (node) {
const result = this.visit(node[1]);
if (typeof result != 'boolean')
this._error(node.lineno, "'not' non-boolean");
return !result;
}
/** `[ 'bool' value ]` returns `Boolean`.
@memberof module:Eleven~Eval_Bool
@instance */
bool (node) {
if (typeof node[1] != 'boolean')
this._error(node.lineno, "'bool' non-boolean");
return node[1];
}
};
/** Methods to interpret string expressions.
@mixin
*/
const Eval_String = superclass => class extends superclass {
/** `[ 'concat' a b ]` returns `String`.
@memberof module:Eleven~Eval_String
@instance */
concat (node) {
const vals = node.slice(1).map(this.visit.bind(this));
if (vals.some(val => typeof val != 'string'))
this._error(node.lineno, "'concat' non-string");
return vals[0] + vals[1];
}
/** `[ 'input' prompt? default? ]` returns `String`.
@memberof module:Eleven~Eval_String
@instance */
input (node) {
return prompt(node[1] ?? '', node[2] ?? '');
}
/** `[ 'len' b ]` returns `Number`.
@memberof module:Eleven~Eval_String
@instance */
len (node) {
const val = this.visit(node[1]);
if (typeof val != 'string')
this._error(node.lineno, "'len' non-string");
return val.length; // undefined if not string
}
/** `[ 'string' value ]` returns `String`.
@memberof module:Eleven~Eval_String
@instance */
string (node) {
if (typeof node[1] != 'string')
this._error(node.lineno, "'string' non-string");
return node[1];
}
};
/** Method to interpret explicit typing.
@mixin
*/
const Eval_Cast = superclass => class extends superclass {
/** `[ 'cast' type b ]` returns type-cast value.
@memberof module:Eleven~Eval_Cast
@instance */
cast (node) {
switch (node[1]) {
case 'bool': return !! this.visit(node[2]);
case 'number': return Number(this.visit(node[2]));
case 'string': return String(this.visit(node[2]));
default: throw node[1] + ': not expected for cast';
}
}
};
// --- 11/07
/** Base class for type checking.
@class
*/
class Check extends Visit {
/** Utility: accepts `[ type value ]`, sets `.type` from tag, returns node.
@param {Array} node - to check. */
_literal (node) {
if (!(typeof node[1]).startsWith(node[0]))
this._error(node.lineno, `expected ${node[0]} literal`);
node.type = node[0]; return node;
}
/** Utility: visits and casts `node[index]` to `type` if needed, returns node.
@param {string} type - expected.
@param {Array} node - parent of subtree to check.
@param {number} index - of subtree in `node`. */
_toType (type, node, index) {
if (this.visit(node[index]).type != type)
(node[index] = [ 'cast', type, node[index] ]).type = type;
return node;
}
/** Utility: casts all operands to type if needed, sets `.type`, returns node.
@param {string} type - expected.
@param {Array} node - tree to check. */
_require (type, node) {
node.slice(1).forEach((_, n) => this._toType(type, node, n+1));
node.type = type;
return node;
}
}
/** Methods to check arithmetic expressions, cast to `number` if needed,
return `number` node.
@mixin
*/
const Check_Number = superclass => class extends superclass {
/** `[ 'add' a b ]` casts to `number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
add (node) { return this._require('number', node); }
/** `[ 'subtract' a b ]` casts to `number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
subtract (node) { return this._require('number', node); }
/** `[ 'multiply' a b ]` casts to `number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
multiply (node) { return this._require('number', node); }
/** `[ 'divide' a b ]` casts to `number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
divide (node) { return this._require('number', node); }
/** `[ 'power' a b ]` casts to `number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
power (node) { return this._require('number', node); }
/** `[ 'minus' b ]` casts to `number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
minus (node) { return this._require('number', node); }
/** `[ 'number' value ]` expects `number` value.
@param {Array} node - to check.
@memberof module:Eleven~Check_Number
@instance */
number (node) { return this._literal(node); }
};
/** Methods to check comparisons, cast to left operand's type if needed,
return `bool` node.
@mixin
*/
const Check_Cmps = superclass => class extends superclass {
/** Casts right operand to left operand's type if needed.
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
_cmp (node) {
const type = this.visit(node[1]).type;
this._toType(type, node, 2);
node.type = 'bool';
return node;
}
/** `[ 'eq' a b ]`
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
eq (node) { return this._cmp(node); }
/** `[ 'ne' a b ]`
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
ne (node) { return this._cmp(node); }
/** `[ 'gt' a b ]`
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
gt (node) { return this._cmp(node); }
/** `[ 'ge' a b ]`
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
ge (node) { return this._cmp(node); }
/** `[ 'lt' a b ]`
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
lt (node) { return this._cmp(node); }
/** `[ 'le' a b ]`
@param {Array} node - to check.
@memberof module:Eleven~Check_Cmps
@instance */
le (node) { return this._cmp(node); }
};
/** Methods to check Boolean expressions, cast to `bool` if needed,
return `bool` node.
@mixin
*/
const Check_Bool = superclass => class extends superclass {
/** `[ 'or' a b ]` casts to `bool`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Bool
@instance */
or (node) { return this._require('bool', node); }
/** `[ 'and' a b ]` casts to `bool`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Bool
@instance */
and (node) { return this._require('bool', node); }
/** `[ 'not' b ]` casts to `bool`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Bool
@instance */
not (node) { return this._require('bool', node); }
/** `[ 'bool' value ]` expects `boolean` value.
@param {Array} node - to check.
@memberof module:Eleven~Check_Bool
@instance */
bool (node) { return this._literal(node); }
};
/** Methods to check string expressions, cast to `string` if needed;
requires {@linkcode module:Eleven~Check_Number06 Check_Number06} as superclass
to defer to `add`. Return `string` node.
@mixin
*/
const Check_String = superclass => class extends superclass {
/** `[ 'input' prompt? default? ]` returns `node.string`.
@param {Array} node - to check.
@memberof module:Eleven~Check_String
@instance */
input (node) {
node.type = 'string'; return node;
}
/** `[ 'add' a b ]` for at least one `string` casts to `string` and returns `node` as `concat.string`.
@param {Array} node - to check.
@memberof module:Eleven~Check_String
@instance */
add (node) {
const a = this.visit(node[1]), b = this.visit(node[2]);
if (a.type != 'string') {
if (b.type != 'string') return super.add(node); // any any
this._toType('string', node, 1); // any string
} else if (b.type != 'string')
this._toType('string', node, 2); // string any
node[0] = 'concat'; // string string
node.type = 'string'; return node;
}
/** `[ 'len' b ]` casts to `string` and returns `node.number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_String
@instance */
len (node) {
this._require('string', node);
node.type = 'number'; return node;
}
/** `[ 'string' value ]` expects `string` value, returns `node.string`.
@param {Array} node - to check.
@memberof module:Eleven~Check_String
@instance */
string (node) { return this._literal(node); }
};
/** Method to check explicit cast, sets `.type` from cast, returns typed node.
@mixin
*/
const Check_Cast = superclass => class extends superclass {
/** `[ 'cast' type b ]` returns `node.type`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Cast
@instance */
cast (node) {
this.visit(node[2]);
node.type = node[1]; return node;
}
};
// --- 11/08
/** Class actions to represent a block with declarations and other items as a tree.
@mixin
*/
const Build_Dcl = superclass => class extends superclass {
/** `block: item [{ ';' item }]; item: dcl | stmt;` returns `[ 'block' dcl... stmt... ]`
@memberof module:Eleven~Build_Dcl
@instance */
block (item, many) {
const items = (many ? many[0] : []).reduce(
(items, alt) => { items.push(alt[1][0]); return items; }, [ item[0] ]);
return this._lineno([ 'block' ].concat(
items.filter(item => item[0] == 'dcl'),
items.filter(item => item[0] != 'dcl')));
}
/** `dcl: type Name [{ ',' Name }];` returns `[ 'dcl' type name ... ]`
@memberof module:Eleven~Build_Dcl
@instance */
dcl (type, name, many) {
return this._lineno([ 'dcl', type, name ].
concat(many ? many[0].map(alt => alt[1]) : []));
}
};
/** Methods to check typed statements, return node.
@mixin
*/
const Check_Stmts = superclass => class extends superclass {
/** `[ 'stmts' stmt ... ]` checks each `stmt`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Stmts
@instance */
stmts (node) {
node.slice(1).forEach(stmt => this.visit(stmt)); return node;
}
/** `[ 'print' value ... ]` values cast to `string`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Stmts
@instance */
print (node) { return this._require('string', node); }
/** `[ 'loop' cond stmt ]` condition cast to `bool`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Stmts
@instance */
loop (node) {
this.visit(node[2]);
return this._toType('bool', node, 1);
}
/** `[ 'select' cond then else? ]` condition cast to `bool`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Stmts
@instance */
select (node) {
node.slice(2).forEach(node => this.visit(node));
return this._toType('bool', node, 1);
}
};
/** Methods to check names, return (typed) node.
Requires {@linkcode module:Eleven~Symbols Symbols}.
@mixin
*/
const Check_Names = superclass => class extends superclass {
/** Returns `node.type(name)`, if undefined `node.number`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Names
@instance */
name (node) {
node.type = this._type(node.lineno, node[1]);
return node;
}
/** Utility: returns type of name, defaulted to `number`.
@param {number} lineno - for error message, if any.
@param {string} name - to find.
@memberof module:Eleven~Check_Names
@instance */
_type (lineno, name) {
const symbol = this._alloc(name);
if (!('type' in symbol)) {
this._error(lineno, name + ': undefined');
symbol.type = 'number';
}
return symbol.type;
}
/** `[ 'assign' name value ]` casts `value` to type of `name`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Names
@instance */
assign (node) {
return this._toType(this._type(node.lineno, node[1]), node, 2);
}
};
/** Methods to check a list of statements and declarations, return node.
@mixin
*/
const Check_Dcl = superclass => class extends superclass {
/** `[ 'block' dcl ... other ... ]` replaces each node by the result of the visit, returns `node`.
@param {Array} node - to check.
@memberof module:Eleven~Check_Dcl
@instance */
block (node) {
node.slice(1).forEach((item, n) => node[n + 1] = this.visit(item));
return node;
}
/** `[ 'dcl' type Name ...]` allocates and types each `Name`, returns `node`.
@memberof module:Eleven~Check_Dcl
@instance */
dcl (node) {
node.slice(2).forEach(name => {
if (this.symbols.has(name))
this._error(node.lineno, name + ': duplicate');
this._alloc(name).type = node[1];
});
return node;
}
};
// --- 11/09
/** Methods to interpret a block with declarations and other items;
requires {@linkcode module:Eleven~Symbols Symbols}.
@mixin
*/
const Eval_Dcl = superclass => class extends superclass {
/** `[ 'block' dcl ... other ... ]` visits all.
@memberof module:Eleven~Eval_Dcl
@instance */
block (node) { node.slice(1).forEach(item => this.visit(item)); }
/** `[ 'dcl' type name ...]` defines as `false`, `0`, or empty strings.
@memberof module:Eleven~Eval_Dcl
@instance */
dcl (node) {
node.slice(2).forEach(name => {
if (this.symbols.has(name) && 'value' in this.symbols.get(name))
this._error(node.lineno, name + ': duplicate');
switch (node[1]) {
case 'bool': this._alloc(name).value = false; break;
case 'number': this._alloc(name).value = 0; break;
case 'string': this._alloc(name).value = ''; break;
default: this._error(node.lineno, node[1] + ": not in 'dcl'");
}
});
}
};
// --- 11/10
/** Class action for a top-level rule to visit a tree and return a stack machine;
requires {@linkcode module:Eleven~Main} and {@linkcode module:Eleven~Code} as last visitor.
@mixin
*/
const Compile = (superclass, ...args) => class extends superclass {
/** Create and apply all visitors, return `executable`.
@throws {string} error message, e.g., error count or `_tree` issue.
@memberof module:Eleven~Compile
@instance */
compile (tree) {
const [lastVisitor, lastTree, trace] = this._doVisits(tree, args);
lastVisitor.visit(lastTree, trace);
if (trace) puts(lastVisitor.machine.toString());
return lastVisitor.executable;
}
};
/** Base class for code generation.
@property {Object} Machine - stack machine generator class.
@property {module:Six~Machine10} machine - stack machine to generate code for.
@class
@extends module:Eleven~Visit
*/
class Code extends Visit {
/** Stack machine generator class. */
get Machine () { return this.#Machine ??= Six.Machine10; }
#Machine;
/** Extended stack machine instructions. */
get Instructions () {
return this.#Instructions ??= superclass => superclass;
}
#Instructions;
/** Stack machine generator. */
get machine () {
return this.#machine ??= new (this.Instructions(this.Machine)) ();
}
#machine;
/** The executable. */
get executable () { return this.machine.run(0); }
/** Visits the subtrees and generates an instruction.
@param {string} op - instruction name.
@returns {number} end of code memory. */
_postfix (node, op) {
node.slice(1).forEach(node => this.visit(node));
return this.machine.gen(op);
}
}
/** Methods to generate code for arithmetic expressions;
all return the next code address.
@mixin
*/
const Code_Number = superclass => class extends superclass {
/** `Power` instruction. */
get Instructions () {
return this.#Instructions ??=
superclass => class extends super.Instructions(superclass) {
/** `stack: ... a b -> ... a**b` */
Power (memory) {
memory.splice(-2, 2, memory.at(-2) ** memory.at(-1));
}
};
}
#Instructions;
/** `[ 'add' a b ]`
@memberof module:Eleven~Code_Number
@instance */
add (node) { return this._postfix(node, 'Add'); }
/** `[ 'subtract' a b ]`
@memberof module:Eleven~Code_Number
@instance */
subtract (node) { return this._postfix(node, 'Subtract'); }
/** `[ 'multiply' a b ]`
@memberof module:Eleven~Code_Number
@instance */
multiply (node) { return this._postfix(node, 'Multiply'); }
/** `[ 'divide' a b ]`
@memberof module:Eleven~Code_Number
@instance */
divide (node) { return this._postfix(node, 'Divide'); }
/** `[ 'power' a b ]`
@memberof module:Eleven~Code_Number
@instance */
power (node) { return this._postfix(node, 'Power'); }
/** `[ 'minus' a ]`
@memberof module:Eleven~Code_Number
@instance */
minus (node) { return this._postfix(node, 'Minus'); }
/** `[ 'number' a ]`
@memberof module:Eleven~Code_Number
@instance */
number (node) {
if (typeof node[1] != 'number')
this._error(node.lineno, "'number' non-number");
return this.machine.gen('Push', node[1]);
}
};
// --- 11/11
/** Methods to generate code for comparisons;
all return the next code address.
Uses {@linkcode module:Six~Machine11 Machine11}.
Requires {@linkcode module:Eleven~Symbols Symbols} for frame size and tracing.
@mixin
*/
const Code_Cmps = superclass => class extends superclass {
/** [Override] Use `Six.Machine11`.
@memberof module:Eleven~Code_Cmps
@instance */
get Machine () { return this.#Machine ??= Six.Machine11; }
#Machine;
/** [Override] The executable, checks for `trace` variable.
@memberof module:Eleven~Code_Cmps
@instance */
get executable () {
const trace = this.symbols.get('trace');
return this.machine.run(this.symbols.size, 0,
trace ? trace.ord - 1 : false);
}
/** `[ 'eq' a b ]`
@memberof module:Eleven~Code_Cmps
@instance */
eq (node) { return this._postfix(node, 'Eq'); }
/** `[ 'ne' a b ]`
@memberof module:Eleven~Code_Cmps
@instance */
ne (node) { return this._postfix(node, 'Ne'); }
/** `[ 'gt' a b ]`
@memberof module:Eleven~Code_Cmps
@instance */
gt (node) { return this._postfix(node, 'Gt'); }
/** `[ 'ge' a b ]`
@memberof module:Eleven~Code_Cmps
@instance */
ge (node) { return this._postfix(node, 'Ge'); }
/** `[ 'lt' a b ]`
@memberof module:Eleven~Code_Cmps
@instance */
lt (node) { return this._postfix(node, 'Lt'); }
/** `[ 'le' a b ]`
@memberof module:Eleven~Code_Cmps
@instance */
le (node) { return this._postfix(node, 'Le'); }
};
/** Methods to generate code for names;
all return the next code address.
Requires {@linkcode module:Eleven~Symbols Symbols}.
@mixin
*/
const Code_Names = superclass => class extends superclass {
/** `[ 'name' name ]`
@memberof module:Eleven~Code_Names
@instance */
name (node) {
return this.machine.gen('Load', this._alloc(node[1]).ord - 1);
}
/** `[ 'assign' name value ]` returns next code address.
@memberof module:Eleven~Code_Names
@instance */
assign (node) {
this.visit(node[2]);
this.machine.gen('Store', this._alloc(node[1]).ord - 1);
return this.machine.gen('Pop');
}
};
/** Methods to generate code for statements;
all return the next code address.
Requires {@linkcode module:Eleven~Code_Cmps Code_Cmps}.
Adds `Bnzero` to `super.Instructions`.
@mixin
*/
const Code_Stmts = superclass => class extends superclass {
/** [Override] Add `Bnzero`.
@memberof module:Eleven~Code_Stmts
@instance */
get Instructions () {
return this.#Instructions ??=
superclass => class extends super.Instructions(superclass) {
/** `stack: ... bool -> ... | pc: bool? a` */
Bnzero (a) {
return memory => { if (memory.pop()) memory.pc = a; }
}
};
}
#Instructions;
/** `[ 'stmts' stmt ... ]`
@memberof module:Eleven~Code_Stmts
@instance */
stmts (node) {
return node.slice(1).reduce((end, stmt) => this.visit(stmt), 0);
}
/** `[ 'print' value ... ]`
@memberof module:Eleven~Code_Stmts
@instance */
print (node) {
node.slice(1).forEach(value => this.visit(value));
return this.machine.gen('Print', node.length - 1);
}
/** `[ 'loop' cond stmt ]`
@memberof module:Eleven~Code_Stmts
@instance */
loop (node) {
const a = this.machine.code.push(null) - 1, // a: Branch b
b = this.visit(node[2]); // a+1: stmt
this.visit(node[1]); // b: cond
this.machine.code[a] = this.machine.ins('Branch', b); // fixup
return this.machine.gen('Bnzero', a + 1); // Bnzero a+1
}
/** `[ 'select' cond then else? ]`
@memberof module:Eleven~Code_Stmts
@instance */
select (node) {
const a = this.visit(node[1]); // cond
this.machine.code.push(null); // a: Bzero b
let b = this.visit(node[2]), end = b; // then
if (node.length > 3) { // b:end:
this.machine.code.push(null); // Branch end
end = this.visit(node[3]); // b: else
// end:
this.machine.code[b ++] = this.machine.ins('Branch', end);
}
this.machine.code[a] = this.machine.ins('Bzero', b); // fixup
return end;
}
};
// --- 11/12
/** Methods to generate code for Boolean expressions;
all return the next code address.
Adds `IfTrue`, `IfFalse`, and `Not` to `super.Instructions`.
@mixin
*/
const Code_Bool = superclass => class extends superclass {
/** [Override] Add `IfTrue`, `IfFalse`, and `Not`.
@memberof module:Eleven~Code_Bool
@instance */
get Instructions () {
return this.#Instructions ??=
superclass => class extends super.Instructions(superclass) {
/** `stack: ... bool -> ... bool | pc: bool? a` */
IfTrue (a) {
return memory => { if (memory.at(-1)) memory.pc = a; };
}
/** `stack: ... bool -> ... bool | pc: !bool? a` */
IfFalse (a) {
return memory => { if (!memory.at(-1)) memory.pc = a; };
}
/** `stack: ... a -> ... !a` */
Not (memory) { memory.splice(-1, 1, !memory.at(-1)); }
};
}
#Instructions;
/** `[ 'or' a b ]`
@memberof module:Eleven~Code_Bool
@instance */
or (node) {
this.visit(node[1]); // push a
const x = this.machine.code.push(null) - 1; // x: IfTrue y
this.machine.gen('Pop'); // pop a
const y = this.visit(node[2]); // push b
// y:
this.machine.code[x] = this.machine.ins('IfTrue', y); // fixup
return y;
}
/** `[ 'and' a b ]`
@memberof module:Eleven~Code_Bool
@instance */
and (node) {
this.visit(node[1]); // push a
const x = this.machine.code.push(null) - 1; // x: IfFalse y
this.machine.gen('Pop'); // pop a
const y = this.visit(node[2]); // push b
// y:
this.machine.code[x] = this.machine.ins('IfFalse', y); // fixup
return y;
}
/** `[ 'not' b ]`
@memberof module:Eleven~Code_Bool
@instance */
not (node) { return this._postfix(node, 'Not'); }
/** `[ 'bool' value ]`
@memberof module:Eleven~Code_Bool
@instance */
bool (node) {
if (typeof node[1] != 'boolean')
throw `[ 'bool' ${node[1]} ]: not boolean`;
return this.machine.gen('Push', node[1]);
}
};
/** Methods to generate code for string expressions;
all return the next code address.
Adds `Concat`, `Len`, and `InputString` to {@linkcode module:Six~Machine11 Machine11}.
@mixin
*/
const Code_String = superclass => class extends superclass {
/** Convert raw to literal string. Only escapes single quote, newline, backslash; see {@link module:Base~Tuple#escape}.
@memberof module:Eleven~Code_String
@instance */
_escape (s) { return `'${s.replace(/['\n\\]/g, '\\$&')}'`; }
/** [Override] Show strings in memory.
@memberof module:Eleven~Code_String
@instance */
get Machine () {
const escape = this._escape.bind(this);
return this.#Machine ??= class extends super.Machine {
/** Show strings in memory. */
get Memory () {
return this.#Memory ??= class extends super.Memory {
toString () {
return '[ ' + this.map(
v => typeof v == 'string' ? escape(v) : v
).join(' ') + ' ]';
}
};
}
#Memory;
};
}
#Machine;
/** [Override] Add `InputString`, `Concat`, and `Len`.
@memberof module:Eleven~Code_String
@instance */
get Instructions () {
return this.#Instructions ??=
superclass => class extends super.Instructions(superclass) {
/** `stack: ... a b -> ... a+b` */
Concat (memory) {
memory.splice(-2, 2, memory.at(-2) + memory.at(-1));
}
/** `stack: ... a -> ... a.length` */
Len (memory) {
memory.splice(-1, 1, memory.at(-1).length);
}
/** `stack: ... -> ... val` */
InputString (prmpt, dflt) {
return memory => memory.push(prompt(prmpt, dflt));
}
};
}
#Instructions;
/** `[ 'input' prompt? default? ]` returns next code address.
@memberof module:Eleven~Code_String
@instance */
input (node) {
return this.machine.gen('InputString',
this._escape(node[1] ?? "''"), this._escape(node[2] ?? "''"));
}
/** `[ 'concat' a b ]`
@memberof module:Eleven~Code_String
@instance */
concat (node) { return this._postfix(node, 'Concat'); }
/** `[ 'len' b ]` returns `number`.
@memberof module:Eleven~Code_String
@instance */
len (node) { return this._postfix(node, 'Len'); }
/** `[ 'string' value ]` returns `string`.
@memberof module:Eleven~Code_String
@instance */
string (node) {
if (typeof node[1] != 'string')
throw `[ 'string' ${node[1]} ]: not string`;
return this.machine.gen('Push', this._escape(node[1]));
}
};
/** Method to generate code for `cast`; returns the next code address.
Adds `Cast` to {@linkcode module:Six~Machine11 Machine11}.
@mixin
*/
const Code_Cast = superclass => class extends superclass {
/** [Override] Add `Cast`.
@memberof module:Eleven~Code_Cast
@instance */
get Instructions () {
return this.#Instructions ??=
superclass => class extends super.Instructions(superclass) {
/** `stack: ... a -> ... cast a` */
Cast (to, from) {
let cast;
switch (to + '-' + from) {
case 'bool-number': cast = x => !!x; break;
case 'bool-string': cast = x => /^\s*true\s*$/i.test(x); break;
case 'number-bool':
case 'number-string': cast = Number; break;
case 'string-bool':
case 'string-number': cast = String; break;
default: throw `Cast ${to} ${from}: illegal cast`;
}
return memory =>
memory.splice(-1, 1, cast(memory.at(-1)));
}
};
}
#Instructions;
/** `[ 'cast' type b ]`
@memberof module:Eleven~Code_Cast
@instance */
cast (node) {
this.visit(node[2]);
return this.machine.gen('Cast', `'${node[1]}'`, `'${node[2].type}'`);
}
};
/** Methods to generate code for `block` and `dcl`;
all return the next code address.
Requires {@linkcode module:Eleven~Symbols Symbols}.
@mixin
*/
const Code_Dcl = superclass => class extends superclass {
/** `[ 'block' dcl ... stmt ... ]` visits all.
@memberof module:Eleven~Code_Dcl
@instance */
block (node) {
return node.slice(1).reduce((end, node) => this.visit(node), 0);
}
/** `[ 'dcl' type name ...]` allocate, initializes `bool` and `string`.
@memberof module:Eleven~Code_Dcl
@instance */
dcl (node) {
return node.slice(2).reduce((end, name) => {
const addr = this._alloc(name).ord - 1;
switch (node[1]) {
case 'number': return this.machine.code.length;
case 'bool': this.machine.gen('Push', false); break;
case 'string': this.machine.gen('Push', "''"); break;
}
this.machine.gen('Store', addr);
return this.machine.gen('Pop');
}, 0);
}
};
export {
Build, Build_RD // 11/01
,Build_Number // 11/02
,Visit, Eval_Number, Main // 11/03
,Build_Stmts, Build_Names, Build_Cmps // 11/04
,Eval_Cmps, Eval_Stmts, Symbols, Eval_Names // 11/05
,Build_String, Build_Bool, Build_Cast, Eval_String,Eval_Bool, Eval_Cast // 11/06
,Check, Check_Number, Check_Cmps, Check_Bool, Check_String, Check_Cast // 11/07
,Build_Dcl, Check_Stmts, Check_Names, Check_Dcl // 11/08
,Eval_Dcl // 11/09
,Compile, Code, Code_Number // 11/10
,Code_Cmps, Code_Names, Code_Stmts // 11/11
,Code_Bool, Code_String, Code_Cast, Code_Dcl // 11/12
};