/** This module contains the classes for all examples in chapter seven.
@module Seven
@author © 2024 Axel T. Schreiner <axel@schreiner-family.net>
@version 2024-04-05
*/
import * as Six from './06.js';
/** [Example 7/01](../?eg=07/01): actions to compile string values and conversions into functions.
@extends module:Six~Functions12 */
class TCheck01 extends Six.Functions12 {
/** Removes quotes and backslash */
_unq (s) {
return s.slice(1,-1).replace(/\\([\\'])/g, "$1");
}
// prog: stmts;
// stmts: stmt [{ ';' stmt }];
// stmt: assign | print | loop | select;
// assign: Name '=' stringSum;
// print: 'print' sums;
// sums: stringSum [{ ',' stringSum }];
// loop: 'while' cmp 'do' stmts 'od';
// select: 'if' cmp 'then' stmts [ 'else' stmts ] 'fi';
// cmp: sum rel | stringSum stringRel;
// sum: product [{ add | subtract }];
// add: '+' product;
// subtract: '-' product;
// product: signed [{ multiply | divide }];
// multiply: '*' signed;
// divide: '/' signed;
// signed:[ '-' ] term;
/** `term: number | '(' sum ')' | 'number' stringTerm;` returns `fct:term` */
// term: number | '(' sum ')' | 'number' stringTerm;
// [0] [1] [1]
term (...val) {
switch (val.length) {
case 1: return val[0];
case 3: return val[1];
case 2: return memory => parseInt(val[1](memory), 10);
}
}
// number: Number;
// rel: eq | ne | gt | ge | lt | le;
// eq: '=' sum;
// ne: '<>' sum;
// gt: '>' sum;
// ge: '>=' sum;
// lt: '<' sum;
// le: '<=' sum;
/** `stringSum: stringTerm [{ stringTerm }];` returns fct */
stringSum (term, many) {
const c = (a, b) => memory => a(memory) + b(memory);
return (many ? many[0] : []).
reduce((sum, list) => c(sum, list[0]), term);
}
/** `stringTerm: string | name | input | 'string' term;` */
// [0] [0] [0] [1]
stringTerm (...val) {
return val.length == 1 ? val[0] :
memory => String(val[1](memory));
}
/** `string: String;` returns fct */
string (s) { return () => this._unq(s); }
// name: Name;
/** `input: 'input' String String;` [replace] returns fct */
input (i, prmpt, dflt) {
return () => prompt(this._unq(prmpt), this._unq(dflt));
}
// stringRel: stringEq | stringNe | stringGt | stringGe | stringLt | stringLe;
/** `stringEq: '=' stringSum;` returns fct for composition */
stringEq (_, right) { return this.parser.call(this, super.eq, _, right); }
/** `stringNe: '<>' stringSum;` returns fct for composition */
stringNe (_, right) { return this.parser.call(this, super.ne, _, right); }
/** `stringGt: '>' stringSum;` returns fct for composition */
stringGt (_, right) { return this.parser.call(this, super.gt, _, right); }
/** `stringGe: '>=' stringSum;` returns fct for composition */
stringGe (_, right) { return this.parser.call(this, super.ge, _, right); }
/** `stringLt: '<' stringSum;` returns fct for composition */
stringLt (_, right) { return this.parser.call(this, super.lt, _, right); }
/** `stringLe: '<=' stringSum;` returns fct for composition */
stringLe (_, right) { return this.parser.call(this, super.le, _, right); }
}
/** [Example 7/02](../?eg=07/02): actions to type-check and compile int|float|string into functions.
@extends module:Seven~TCheck01 */
class TCheck02 extends TCheck01 {
/** For error messages */
get parser () { return this.#parser; }
#parser;
/** Symbol table, maps names to types */
get symbols () { return this.#symbols; }
#symbols;
/** For symbolic computing with types */
get stack () { return this.#stack; }
#stack = [ ];
constructor (parser, symbols = new Map()) {
super();
this.#parser = parser;
this.#symbols = symbols;
}
/** Returns type of name, message if undefined */
_type (name) {
const type = this.symbols.get(name);
if (type) return type;
this.parser.error(name + ': undeclared'); // return undefined
}
/** Converts `fct:from` into `fct:to` if needed */
_cast (fct, from, to) {
switch (`${to} <- ${from}`) {
default:
this.parser.error('impossible cast from', from, 'to', to);
case 'int <- int': case 'float <- float':
case 'string <- string': case 'float <- int':
return fct;
case 'int <- float':
return memory => Math.round(fct(memory));
case 'int <- string':
return memory => parseInt(fct(memory), 10);
case 'float <- string':
return memory => parseFloat(fct(memory));
case 'string <- int': case 'string <- float':
return memory => String(fct(memory));
}
}
/** `prog: [{ decl ';' }] stmts;` returns executable */
prog (many, stmts) { return this.parser.call(this, super.prog, stmts); }
/** `decl: type Name [{ ',' Name }];` */
decl (type, name, many) {
[ name ].concat(many ? many[0].map(list => list[1]) : []).
forEach(name => {
if (this.symbols.has(name))
this.parser.error(`${name}: duplicate`);
this.symbols.set(name, type[0]);
});
}
// type: 'int' | 'float' | 'string';
// stmts: stmt [{ ';' stmt }];
// stmt: assign | print | loop | select;
/** `assign: Name '=' sum;` returns fct */
assign (name, _, sum) {
const type = this._type(name),
r = this.stack.pop();
if (type != r)
this.parser.error(`assigning ${r} to ${type} ${name}`);
return this.parser.call(this, super.assign, name, _, sum);
}
/** `print: 'print' sums;` returns fct, string arguments only */
print (p, sums) {
if (! this.stack.splice(- sums.length, sums.length).
every(type => type == 'string'))
this.parser.error('can only print strings');
return this.parser.call(this, super.print, p, sums);
}
/** `printAny: 'print' sums;` returns fct */
printAny (p, sums) { // implicitly casts non-string arguments
sums.reverse().map((sum, n) => { // check each argument
const type = this.stack.pop(); // requires reverse order
if (type != 'string') {
sum = this._cast(sum, type, 'string'); // apply conversion
puts(`print argument ${sums.length - n} was ${type}`);
}
return sum; // returns fct:string
}).reverse();
return this.parser.call(this, super.print, p, sums);
}
// sums: sum [{ ',' sum }];
/** `cmp: sum rel;` returns fct */
cmp (sum, rel) {
const [ l, r ] = this.stack.splice(-2, 2);
if ((l == 'string' || r == 'string') && l != r)
this.parser.error('must compare strings to strings');
return this.parser.call(this, super.cmp, sum, rel);
}
// rel: eq | ne | gt | ge | lt | le;
// eq: '=' sum;
// ne: '<>' sum;
// gt: '>' sum;
// ge: '>=' sum;
// lt: '<' sum;
// le: '<=' sum;
// sum: product [{ add | subtract }];
/** `add: '+' product;` returns `fct:string|int|float` */
add (_, right) {
const [ l, r ] = this.stack.splice(-2, 2);
this.stack.push(l == 'string' || r == 'string' ? 'string' :
l == 'int' && r == 'int' ? 'int' : 'float');
return this.parser.call(this, super.add, _, right);
}
/** `subtract: '-' product;` returns `fct:int|float` */
subtract (_, right) {
const [ l, r ] = this.stack.splice(-2, 2);
if (l == 'string' || r == 'string')
this.parser.error("cannot apply '-' to string");
this.stack.push(l == 'int' && r == 'int' ? 'int' : 'float');
return this.parser.call(this, super.subtract, _, right);
}
// product: signed [{ multiply | divide }];
/** `multiply: '*' signed;` returns `fct:int|float` */
multiply (_, right) {
const [ l, r ] = this.stack.splice(-2, 2);
if (l == 'string' || r == 'string')
this.parser.error("cannot apply '*' to string");
this.stack.push(l == 'int' && r == 'int' ? 'int' : 'float');
return this.parser.call(this, super.multiply, _, right);
}
/** `divide: '/' signed;` returns `fct:float` */
divide (_, right) {
const [ l, r ] = this.stack.splice(-2, 2);
if (l == 'string' || r == 'string')
this.parser.error("cannot apply '/' to string");
this.stack.push('float');
return this.parser.call(this, super.divide, _, right);
}
/** `signed: [ '-' ] term;` returns `fct:term` */
signed (minus, term) {
if (minus && this.stack.at(-1) == 'string')
this.parser.error("cannot apply '-' to string");
return this.parser.call(this, super.signed, minus, term);
}
/** `term: int | float | string | name | input
| 'int' term | 'float' term | 'string' term
| '(' sum ')';` returns `fct:term` */
// term: int | float | string | name | input
// [0]
// | 'int' term | 'float' term | 'string' term
// [0] [1]
// | '(' sum ')';
// [1]
term (...val) {
switch (val.length) {
case 1: return val[0];
case 3: return val[1];
}
const to = val[0], from = this.stack.pop();
this.stack.push(to);
return this._cast(val[1], from, to);
}
/** `input: 'input' String String;` returns `fct.string` */
input (i, prmt, dflt) {
this.stack.push('string'); return this.parser.call(this, super.input, i, prmt, dflt);
}
/** `int: Int;` returns `fct:int` */
int (int) { this.stack.push('int'); return this.parser.call(this, super.number, int); }
/** `float: Float;` returns `fct:float` */
float (float) {
this.stack.push('float'); return () => parseFloat(float);
}
/** `string: String;` returns `fct:string` */
string (string) {
this.stack.push('string'); return this.parser.call(this, super.string, string);
}
/** `name: Name;` returns `fct:_type(name)` */
name (name) {
this.stack.push(this._type(name));
return this.parser.call(this, super.name, name);
}
}
/** [Example 7/04](../?eg=07/04): function calls.
@extends module:Six~Machine11 */
class Machine04 extends Six.Machine11 {
/** `stack: ... -> ... old-pc | pc: addr` */
Call (addr) {
return memory => (memory.push(memory.pc), memory.pc = addr);
}
/** `stack: ... old-pc -> ,,, 0 old-pc` */
Entry (memory) {
memory.splice(-1, 0, 0);
}
/** `stack: ... old-pc -> ... | pc: old-pc` */
Return (memory) {
memory.pc = memory.pop();
}
/** `stack: ... x old-pc result -> ... result old-pc result` */
ReturnValue (memory) {
memory.splice(-3, 1, memory.at(-1));
}
}
/** [Example 7/04](../?eg=07/04): compile parameter-less functions into stack machine code.
@extends module:Six~Control11 */
class Functions04 extends Six.Control11 {
/** Manages next (global) variable address */
get size () { return this.#size; }
set size (size) { this.#size = size; }
#size = 0;
/** Describes current function */
get funct () { return this.#funct; }
set funct (sym) { this.#funct = sym; }
#funct;
/** (Inner) base class for symbol descriptions.
@class
@property {module:Seven~Functions04} owner - outer class.
@property {string} name - variable or function name. */
get Symbol () { return this.#Symbol ??= class {
owner; // surrounding class
name; // variable/function name
constructor (owner, name) {
this.owner = owner; this.name = name;
}
};
}
#Symbol;
/** Describes a variable.
@class @extends Symbol
@property {number} addr - memory address.
@property {function} load() - generates load instruction.
@property {function} storeOk() - always true.
@property {function} store() - generates store instruction.
@property {function} toString() - represents as text. */
get Var () { return this.#Var ??= class extends this.Symbol {
addr; // memory address
constructor (owner, name, addr) {
super(owner, name); this.addr = addr;
}
load () { // generate load instruction
this.owner.machine.gen('Load', this.addr);
}
storeOk () { return true; } // always permit assignment
store () { // generate store instruction
this.owner.machine.gen('Store', this.addr);
}
toString () { return `${this.name} at ${this.addr}`; }
};
}
#Var;
/** Describes a function.
@class @extends Symbol
@property {boolean|number} start - code address.
@property {number[]} calls - slots to insert `Call`.
@property {number[]} returns - slots to insert branch to exit.
@property {function} entry() - generates preamble code.
@property {function} undo() - undoes `entry()`.
@property {function} call() - generates `Call` instruction.
@property {function} return() - generates branch to exit.
@property {function} storeOk() - true if allowed to store.
@property {function} store() - generates store instruction.
@property {function} end() - fixes `calls`/`returns`, `exit()`.
@property {function} exit() - generates postamble code.
@property {function} toString() - represents as text. */
get Fun () { return this.#Fun ??= class extends this.Symbol {
start = false; // start address, not yet set
calls = []; // forward references to entry
returns = []; // forward references to exit
entry () { // defines start address, arranges slot for result
this.start = this.owner.machine.gen('Entry') - 1;
}
undo () { // ends a declaration, undoes entry()
this.owner.machine.code.length = this.start;
this.start = false;
}
call () { // create Call or save address for slot for Call
if (typeof this.start == 'number')
this.owner.machine.gen('Call', this.start);
else
this.calls.push(this.owner.machine.code.push(null) - 1);
}
return () { // create slot for Branch, save address
this.returns.push(this.owner.machine.code.push(null) - 1);
}
storeOk () { // ok to store result value?
if (this == this.owner.funct) return true;
this.owner.parser.error(`${this.name}: ` +
`assigned to outside function`);
return false;
}
store () { // store top of stack as result value
this.owner.machine.gen('ReturnValue');
}
end () { // resolve calls and returns if any, exit()
const call = this.owner.machine.ins('Call', this.start);
this.calls.forEach(c => this.owner.machine.code[c] = call);
this.calls.length = 0;
const br = this.owner.machine.ins('Branch',
this.owner.machine.code.length);
this.returns.forEach(c => this.owner.machine.code[c] = br);
this.returns.length = 0;
this.exit();
}
exit () { // generates code to return from function call
this.owner.machine.gen('Return');
}
toString () {
return `function ${this.name} start ${this.start}`;
}
};
}
#Fun;
constructor (parser, machine = new Machine04()) {
super(parser, machine);
}
/** Returns symbol description for name, if any */
_find (name, report) {
const sym = this.symbols.get(name);
if (report && !sym) this.parser.error(`${name}: undefined`);
return sym;
}
/** (Re-)defines and returns `sym`, cannot be undefined */
_dcl (sym, report) {
if (report && this.symbols.get(sym.name))
this.parser.error(`${sym.name}: duplicate`);
this.symbols.set(sym.name, sym);
return sym;
}
/** Returns new `Var` at next global address. */
_alloc (name) { return new this.Var(this, name, this.size ++); }
/** Flags undefined functions, returns main if defined */
_check_defs (map) {
let main = undefined;
map.forEach(sym => {
if (sym instanceof this.Fun)
if (typeof sym.start != 'number')
this.parser.error(`${sym.name}: undefined function`);
else if (sym.name == 'main')
main = sym;
});
return main;
}
/** Generates `Call` to `main.start` and `Print` result
@param {Fun} main - describes `main()`. */
_startup (main) {
this.machine.gen('Call', main.start); // call main function
this.machine.gen('Print', 1); // print and pop
}
/** `prog: [ vars ] funs;` returns executable */
prog (v, f) {
const main = this._check_defs(this.symbols), // flag undefined
startAddr = this.machine.code.length, // at startup code
trace = this._find('trace'), // does variable 'trace' exist?
traceAddr = trace instanceof this.Var ? trace.addr : false;
if (main) this._startup(main); // generate call to main
else this.parser.error('main: undefined');
if (traceAddr !== false) { // if 'trace' exists
puts(this.machine.toString()); // show code,
this.symbols.forEach(s => puts(s.toString())); // symbols,
puts('stack starts at', this.size); // variable memory size
if (main) puts('execution starts at', startAddr);
}
return this.machine.run(this.size, startAddr, traceAddr);
}
// vars: 'var' names ';';
/** `names: Name [{ ',' Name }];` defines new variables,
returns number of names */
names (name, some) {
const dcl = name => this._dcl(this._alloc(name), true);
dcl(name);
if (some) some[0].forEach(list => dcl(list[1]));
return 1 + (some ? some[0].length : 0);
}
// funs: { fun };
/** `fun: head [ 'begin' stmts 'end' ] ';';` */
fun (head, opt, _) {
if (opt) head.end(); // function definition: wrap up
else head.undo(); // function declaration: discard entry code
this.funct = null; // not in function
}
/** `head: 'function' Name;` returns function symbol */
head (f, name) {
let sym = this._find(name);
if (! (sym instanceof this.Fun)) {
if (sym instanceof this.Var)
this.parser.error(`${name}: used as variable and function`);
sym = this._dcl(new this.Fun(this, name));
}
if (typeof sym.start == 'number') {
this.parser.error(`${name}: duplicate`);
sym = this._dcl(new this.Fun(this, name)); // patch
}
sym.entry(); // generate code for function entry
return this.funct = sym; // in function
}
// stmts: stmt [{ ';' stmt }];
// stmt: assign | print | return | loop | select;
/** `assign: Name [ '=' sum ];` */
assign (name, sum) {
const sym = this._find(name, true);
if (sym) {
if (sym instanceof this.Var)
if (sum && sym.storeOk()) sym.store(); // variable = sum
else this.parser.error(`${name}: cannot call a variable`);
else if (!sum) sym.call(); // function call
else if (sym.storeOk()) sym.store(); // function = sum
this.machine.gen('Pop'); // clear stack
}
}
// print: 'print' sums;
// sums: sum [{ ',' sum }];
/** `return: 'return' [ sum ];` */
return (r, sum) {
if (sum && this.funct.storeOk())
(this.funct.store(), this.machine.gen('Pop'));
this.funct.return();
}
// loop: While cmp Do stmts 'od';
// While: 'while';
// Do: 'do';
// select: 'if' cmp Then stmts [ Else stmts ] 'fi';
// Then: 'then';
// Else: 'else';
// cmp: sum rel;
// rel: eq | ne | gt | ge | lt | le;
// eq: '=' sum;
// ne: '<>' sum;
// gt: '>' sum;
// ge: '>=' sum;
// lt: '<' sum;
// le: '<=' sum;
// sum: product [{ add | subtract }];
// add: '+' product;
// subtract: '-' product;
// product: signed [{ multiply | divide }];
// multiply: '*' signed;
// divide: '/' signed;
// signed: [ '-' ] term;
// term: input | number | name | '(' sum ')';
// input: 'input' [ Number ];
// number: Number;
/** `name: Name;` */
name (name) {
const sym = this._find(name, true);
if (sym instanceof this.Fun) sym.call();
else if (sym) sym.load();
}
}
/** [Example 7/06](../?eg=07/06): parameters and local variables.
@extends module:Seven~Machine04 */
class Machine06 extends Machine04 {
/** Data memory for frames.
@class @extends super.Memory
@property {number} fp - frame pointer.
@property {number[]} frames - list of number of parameters,
in reverse order of dynamic link.
@property {function} mapSlot() - shows one slot
@property {function} toString() - [replace] shows frames. */
get Memory () {
return this.#Memory ??= class extends super.Memory {
fp = 0; // global frame starts at 0
frames = [ 0 ]; // toString(): list of number of parameters
toString () {
let fp = this.fp, // begin of (top) frame
to = this.length; // end of (top) frame
return this.frames.map((parms, n) => {
try {
return `${fp}:[ ${this.slice(fp, to).
map(slot => this.mapSlot(slot)).join(' ')} ]`;
} catch (e) { throw e; // shouldn't happen
} finally {
to = fp; // end and begin of previous frame
if (n == this.frames.length-1) fp = 0; // globals
else fp = this[fp + parms + 1]; // previous frame
}
}).reverse().join(' ');
}
mapSlot (slot) { // hook to interpret slots
return typeof slot == 'undefined' ? 'undefined' : slot;
}
};
}
#Memory;
/** `stack: ... arguments old-pc
-> ... arguments old-pc old-fp result locals` */
Entry (parms, size) {
return memory => {
const locals = size - parms - 3, // local variables
fp = memory.length - parms - 1; // new frame's base
memory.push(memory.fp, 0); // push old-fp, result
if (locals) // push local variables, if any
memory.push(... Array(locals).fill(0));
memory.fp = fp; // new dynamic link
memory.frames.unshift(parms); // push frames stack for trace
};
}
/** `stack: ... arguments old-pc old-fp result locals
-> ... result old-pc` */
Exit (parms) {
return memory => {
const fp = memory.fp; // current frame
memory.fp = memory[fp + parms + 1]; // restore dynamic link
memory.splice(fp, Infinity, // pop frame, push result old-pc
memory[fp + parms + 2], memory[fp + parms]);
memory.frames.shift(); // pop frames stack (trace)
};
}
/** `stack: ... -> ... frame[addr]` */
LoadFP (addr) {
return memory => memory.push(memory[memory.fp + addr]);
}
/** `stack: ... val -> ... val | frame[addr]: val` */
StoreFP (addr) {
return memory => memory[memory.fp + addr] = memory.at(-1);
}
}
/** [Example 7/06](../?eg=07/06): compile functions with parameters and local variables.
@extends module:Seven~Functions04 */
class Parameters06 extends Functions04 {
/** Describes a global or local variable.
@class @extends super.Var
@property {number} depth - 0: global, 1: local.
@property {function} load() - [replace] global/local.
@property {function} store() - [replace] global/local. */
get Var () { return this.#Var ??= class extends super.Var {
depth; // 0: global, else local
constructor (owner, name, addr, depth) {
super(owner, name, addr); this.depth = depth;
}
load () { // generate load instruction
if (this.depth) // local
this.owner.machine.gen('LoadFP', this.addr);
else // global
this.owner.machine.gen('Load', this.addr);
}
store () { // generate store instruction
if (this.depth) // local
this.owner.machine.gen('StoreFP', this.addr);
else // global
this.owner.machine.gen('Store', this.addr);
}
toString () {
return `${this.name} at ${this.depth ? '+' : ''}${this.addr}`;
}
};
}
#Var;
/** Describes a function with parameters and local variables.
@class @extends super.Fun
@property {number} parms - number of parameters.
@property {number} addr - offset of function result slot in frame.
@property {Map} locals - maps names to local variables.
@property {number} size - size of frame.
@property {function} entry() - [replace] slot for `Entry`.
@property {function} setParms() - captures number of parameters, starts frame.
@property {function} undo() - [extend] also reset locals.
@property {function} store() - [replace] use `StoreFP`.
@property {function} exit() - [replace] fill `Entry`, `Exit`. */
get Fun () { return this.#Fun ??= class extends super.Fun {
parms; // number of parameters
addr; // offset of function result slot in frame
#locals = new Map(); // maps local names to descriptions
get locals () { return this.#locals; }
set locals (locals) { this.#locals = locals; }
#size = 0; // next address, frame size
get size () { return this.#size; }
set size (size) { this.#size = size; }
entry () { // defines start address, arranges slot for Entry
this.start = this.owner.machine.code.push(null) - 1;
}
setParms () { // frame: parms, old-pc, old-fp, result
if (typeof this.parms != 'undefined'
&& this.parms != this.size)
this.owner.parser.error(`${this.name} parameters: ` +
`previously ${this.parms}, now ${this.size}`);
this.parms = this.size; // set number of parameters
this.size += 2; // leave room for old pc and old fp
this.addr = this.size ++; // leave slot for result
}
undo () {
this.locals = new Map(); // undefine parameters
this.size = 0; // reset next address, frame size
super.undo();
}
store () { // use `StoreFP`
this.owner.machine.gen('StoreFP', this.addr);
}
exit () { // fills Entry, generates Exit and Return
this.owner.machine.code[this.start] =
this.owner.machine.ins('Entry', this.parms, this.size);
this.owner.machine.gen('Exit', this.parms);
this.owner.machine.gen('Return');
}
toString () {
const names = [];
this.locals.forEach(sym => names.push(sym.name));
return `function ${this.name} start ${this.start} ` +
`parms ${this.parms} size ${this.size} ` +
`[ ${names.join(' ')} ]`;
}
};
}
#Fun;
/** Manages a stack of contexts for assign or call to a name */
get context () {
if (this.#contexts.length) return this.#contexts.at(-1);
throw 'no context'; //can't happen
}
set context (context) { // push a value, pop with null
if (context) this.#contexts.push(context);
else this.#contexts.pop();
}
#contexts = [];
constructor (parser, machine = new Machine06()) {
super(parser, machine);
}
/** Replace: returns new `Var` at next local/global address. */
_alloc (name) {
if (this.funct) // create local variable
return new this.Var(this, name, this.funct.size ++, 1);
else // create global variable
return new this.Var(this, name, this.size ++, 0);
}
/** Extend: checks local then global map, returns `sym` */
_find (name, report) {
let sym;
if (this.funct && (sym = this.funct.locals.get(name)))
return sym; // local
return super._find(name, report); // global
}
/** Replace: sets innermost map, returns `sym` */
_dcl (sym, report) {
const map = this.funct ? this.funct.locals : this.symbols;
if (report && map.get(sym.name))
this.parser.error(`${sym.name}: duplicate`);
map.set(sym.name, sym);
return sym;
}
/** [Extend] Push 0 for `main` parameters.
@param {Fun} main - describes `main()`. */
_startup (main) {
for (let p = 0; p < main.parms; ++ p)
this.machine.gen('Push', 0);
super._startup(main);
}
// prog: [ vars ] funs;
// vars: 'var' names ';';
// names: Name [{ ',' Name }];
// funs: { fun };
/** `fun: head parms [ block ] ';';` */
fun (head, parms, opt, _) { super.fun(head, opt); }
// head: 'function' Name;
/** `parms: '(' [ names ] ')';` */
parms (lp, names, rp) { this.funct.setParms(); }
// block: 'begin' [ vars ] stmts 'end';
// stmts: stmt [{ ';' stmt }];
// stmt: assign | print | return | loop | select;
/** `assign: symbol action;` codes `Pop`, pops context */
assign (symbol, action) {
this.machine.gen('Pop'); this.context = null; // pop context
}
// action: store | call;
/** `store: '=' sum;` expects context, codes assignment */
store (_, sum) {
if (this.context.symbol.storeOk())
this.context.symbol.store();
}
// call: args;
/** `args: '(' [ sums ] ')';` expects context, codes call */
args (lp, sums, rp) {
const sym = this.context.symbol, // to apply args to
nargs = sums ? sums[0] : 0; // # of arguments
if (!(sym instanceof this.Fun))
this.parser.error(`${sym.name}: not a function`);
else if (nargs != sym.parms)
this.parser.error(`${sym.name} arguments: ` +
`expected ${sym.parms}, specified ${nargs}`);
else
sym.call(); // call function
}
// print: 'print' sums;
// sums: sum [{ ',' sum }];
// return: 'return' [ sum ];
// loop: While cmp Do stmts 'od';
// While: 'while';
// Do: 'do';
// select: 'if' cmp Then stmts [ Else stmts ] 'fi';
// Then: 'then';
// Else: 'else';
// cmp: sum rel;
// rel: eq | ne | gt | ge | lt | le;
// eq: '=' sum;
// ne: '<>' sum;
// gt: '>' sum;
// ge: '>=' sum;
// lt: '<' sum;
// le: '<=' sum;
// sum: product [{ add | subtract }];
// add: '+' product;
// subtract: '-' product;
// product: signed [{ multiply | divide }];
// multiply: '*' signed;
// divide: '/' signed;
// signed: [ '-' ] term;
// term: input | number | name | '(' sum ')';
// input: 'input' [ Number ];
// number: Number;
/** `name: symbol [ args ];` codes variable load, pops context */
name (sym, args) {
if (!args)
if (sym instanceof this.Fun)
this.parser.error(`${sym.name}: no argument list`);
else
sym.load(); // variable reference
this.context = null; // pop context
}
/** `symbol: Name;` pushes context, returns symbol */
symbol (name) {
let sym = this._find(name, true);
if (!sym) sym = this._dcl(this._alloc(name)); // patch
this.context = { symbol: sym }; // push symbol description
return sym;
}
}
/** [Example 7/09](../?eg=07/09): compile global functions with block structure.
@extends module:Seven~Parameters06 */
class Blocks09 extends Parameters06 {
/** Describes a function with block structure.
@class @extends super.Fun
@property {Block[]} blocks - block stack, [0] is innermost
@property {number} frameSize - must replace `size`
@property {Map} locals - [replace] delegate to blocks
@property {number} size - [replace] delegate to blocks
@property {function} push() - add a block
@property {function} pop() - end a block, maintain size
@property {function} end() - [extend] pop last block
@property {function} exit() - [replace] use `frameSize` */
get Fun () { return this.#Fun ??= class extends super.Fun {
frameSize = 0; // because this.size is local to blocks
blocks; // block stack, [0] is innermost block
get locals () { return this.blocks[0].locals; }
set locals (locals) {
try { return this.blocks[0].locals = locals; }
catch (e) { console.trace(e); throw e; } }
get size () { return this.blocks[0].size; }
set size (size) { return this.blocks[0].size = size; }
constructor (owner, name) { // creates outermost block
super(owner, name);
this.blocks = [ new this.owner.Block(0) ];
}
push () { // add block, start in encompassing block
this.blocks.unshift(
new this.owner.Block(this.blocks[0].size)
);
}
pop () { // remove block, maintain maximum frame size
this.frameSize =
Math.max(this.frameSize, this.blocks[0].size);
if (this.owner.symbols.get('trace') // trace?
instanceof this.owner.Var)
puts(this.blocks[0].toString());
this.blocks.shift();
}
end () { // [extend] pop outermost block
this.pop(); super.end();
}
exit () { // [replace] uses frameSize
this.owner.machine.code[this.start] =
this.owner.machine.ins('Entry', this.parms, this.frameSize);
this.owner.machine.gen('Exit', this.parms);
this.owner.machine.gen('Return');
}
toString () { // [replace] no symbols, show frameSize
return `function ${this.name} start ${this.start} ` +
`parms ${this.parms} frame size ${this.frameSize}`;
}
};
}
#Fun;
/** Describes a block of nested symbols.
@class
@property {Map} locals - maps names to descriptions.
@property {number} size - next variable address in block.
@property {function} toString() - describes as text. */
get Block () { return this.#Block ??= class {
locals = new Map(); // maps names in block to descriptions
size; // next variable address in block
constructor (size) { this.size = size; }
toString () {
const names = [];
this.locals.forEach(sym => names.push(sym.toString()));
return `block [ ${names.join(', ')} ]`;
}
};
}
#Block;
/** Replace: searches innermost to outermost blocks and global */
_find (name, report) {
let sym;
try {
if (this.funct) // loop inner to outer block
this.funct.blocks.forEach(block => {
sym = block.locals.get(name);
if (typeof sym != 'undefined') throw sym;
});
return sym = this.symbols.get(name); // global
} catch (sym) { // found in a block
if (sym instanceof Error) throw sym; // shouldn't happen
return sym;
} finally {
if (report && !sym)
this.parser.error(`${name}: undefined`);
}
}
// prog: [ vars ] funs;
// vars: 'var' names ';';
// names: Name [{ ',' Name }];
// funs: { fun };
// fun: head parms [ block ] ';';
// head: 'function' Name;
// parms: '(' [ names ] ')';
/** `block: begin [ vars ] stmts 'end';` */
block (b, v, s, e) { this.funct.pop(); }
/** `begin: 'begin';` */
begin (b) { this.funct.push(); }
// stmts: stmt [{ ';' stmt }];
// stmt: assign | print | return | block | loop | select;
// assign: symbol action;
// action: store | call;
// store: '=' sum;
// call: args;
// args: '(' [ sums ] ')';
// print: 'print' sums;
// sums: sum [{ ',' sum }];
// return: 'return' [ sum ];
/** `loop: While cmp Do [ vars ] stmts 'od';` */
loop (While, c, Do, v, s, o) {
this.funct.pop(); super.loop(While, c, Do);
}
// While: 'while';
/** `Do: 'do';` */
Do () { this.funct.push(); return super.Do(); }
/** `select: 'if' cmp then [ else ] 'fi';` */
select(i, c, t, e, f) {
// select: 'if' cmp Then stmts [ Else stmts ] 'fi';
super.select(i, c, t, false, e);
}
/** `then: Then [ [ vars ] stmts ];` */
then (t, opt) { this.funct.pop(); return t; }
/** `else: Else [ vars ] stmts;` */
else (e, v, s) { this.funct.pop(); return e; }
/** `Then: 'then';` */
Then (t) { this.funct.push(); return super.Then(); }
/** `Else: 'else';` */
Else (e) { this.funct.push(); return super.Else(); }
// cmp: sum rel;
// rel: eq | ne | gt | ge | lt | le;
// eq: '=' sum;
// ne: '<>' sum;
// gt: '>' sum;
// ge: '>=' sum;
// lt: '<' sum;
// le: '<=' sum;
// sum: product [{ add | subtract }];
// add: '+' product;
// subtract: '-' product;
// product: signed [{ multiply | divide }];
// multiply: '*' signed;
// divide: '/' signed;
// signed: [ '-' ] term;
// term: input | number | name | '(' sum ')';
// input: 'input' [ Number ];
// number: Number;
// name: symbol [ args ];
// symbol: Name;
}
/** [Example 7/13](../?eg=07/13): nested functions
@extends module:Seven~Machine06 */
class Machine13 extends Machine06 {
/** Data memory for nested functions.
@class @extends super.Memory
@property {number} dp - display (static link) pointer.
@property {number[]} frames - [remove]: unused.
@property {function} toString() - [replace]: uses `dp`. */
get Memory () {
return this.#Memory ??= class extends super.Memory {
dp = 0; // display (static link) pointer
// frames[] is no longer used
toString () {
let fp = this.fp, // begin of (top) frame
to = this.length, // end of (top) frame
dp = this.dp, // static link
output = [];
do {
if (!dp) fp = 0; // global frame
output.unshift(`${fp}:[ ${this.slice(fp, to).
map(slot => this.mapSlot(slot)).join(' ')} ]`);
to = fp; // end and begin of previous frame
fp = this[dp - 2]; // previous frame pointer
dp = this[dp - 1]; // previous static link
} while (to);
return output.join(' ');
}
};
}
#Memory;
/** `stack: ... arguments old-pc
-> ... arguments old-pc old-fp old-dp result display locals` */
Entry (parms, depth, size) {
return memory => {
const locals = size - parms - 4 - depth, // local variables
fp = memory.length - parms - 1, // new frame's base
dp = memory.length + 2; // new display's bottom
// push old-fp, old-dp, result
memory.push(memory.fp, memory.dp, 0);
if (depth > 1) memory.push( // push part of display
... memory.slice(memory.dp + 1, memory.dp + depth) );
memory.push(fp); // push new frame's base
if (locals) // push local variables if any
memory.push(... Array(locals).fill(0));
memory.fp = fp; // new dynamic link
memory.dp = dp; // new display's bottom
};
}
/** `stack: ... arguments old-pc old-fp old-dp result display locals
-> ... result old-pc` */
Exit (memory) {
const fp = memory.fp, // current frame, i.e., @arguments
dp = memory.dp; // current display, i.e., @result
memory.fp = memory[dp - 2]; // restore old-fp
memory.dp = memory[dp - 1]; // restore old-dp
// pop frame, push old-pc result
memory.splice(fp, Infinity, memory[dp], memory[dp - 3]);
}
/** `stack: ... -> ... frame[depth][addr]` */
LoadDP (addr, depth) {
return memory =>
memory.push(memory[memory[memory.dp + depth] + addr]);
}
/** `stack: ... val -> ... val | frame[depth][addr]: val` */
StoreDP (addr, depth) {
return memory =>
memory[memory[memory.dp+depth] + addr] = memory.at(-1);
}
}
/** [Example 7/13](../?eg=07/13): add actions and infrastructure to compile nested functions.
@mixin
*/
const Nest13 = superclass => class extends superclass {
/** List of inner to outer nested frames, `null` at end.
@instance
@memberof module:Seven~Nest13 */
get functs () { return this.#functs; } // current function stack
#functs = [ null ];
/** Replace: manage stack of functions
@instance
@memberof module:Seven~Nest13 */
get funct () { return this.functs[0]; }
set funct (sym) {
if (sym) this.functs.unshift(sym); // push function
else this.functs.shift(); // pop function
}
constructor (parser, machine = new Machine13()) {
super(parser, machine);
}
/** Describes a global or nested variable in {@linkcode module:Seven-Nest13 Nest13}.
@instance
@memberof module:Seven~Nest13
@class @extends super.Var
@property {number} depth - [extend] `>=1`: nested.
@property {function} load() - [replace] use `depth`.
@property {function} store() - [replace] use `depth`
@property {function} toString() - [replace] show `depth`. */
get Var () { return this.#Var ??= class extends super.Var {
load () { // [replace] load by depth
if (!this.depth) // global
this.owner.machine.gen('Load', this.addr);
else if (this.depth+1 != this.owner.functs.length)// nested
this.owner.machine.gen('LoadDP', this.addr, this.depth);
else // local
this.owner.machine.gen('LoadFP', this.addr);
}
store () { // [replace] store by depth
if (!this.depth) // global
this.owner.machine.gen('Store', this.addr);
else if (this.depth+1 != this.owner.functs.length)// nested
this.owner.machine.gen('StoreDP', this.addr, this.depth);
else // local
this.owner.machine.gen('StoreFP', this.addr);
}
toString () {
if (!this.depth) return `${this.name} at ${this.addr}`;
else return `${this.name} at ${this.addr}d${this.depth}`;
}
};
}
#Var;
/** Returns new `Var` at next local/global address.
@instance
@memberof module:Seven~Nest13 */
_alloc (name) {
if (this.funct) // create local variable
return new this.Var(this, name, this.funct.size ++,
this.funct.depth);
else // create global variable
return new this.Var(this, name, this.size ++, 0);
}
/** Describes a nested function with block structure in {@linkcode module:Seven-Nest13 Nest13}.
@instance
@memberof module:Seven~Nest13
@class @extends super.Fun
@property {number} depth - length of static link
@property {undefined|Block} scope - `.locals` contains `this`
@property {function} entry() - [extend] create bypass
@property {function} setParms() - [extend] room for display
@property {function} storeOk() - [replace] consider outer functions
@property {function} store() - [replace] consider `depth`
@property {function} pop() - [extend] check for undefined functions
@property {function} exit() - [replace] use `depth`, fix bypass
@property {function} toString () - [extend] display depth */
get Fun () { return this.#Fun ??= class extends super.Fun {
depth; // length of static link, 1 for global function
constructor (owner, name) { // sets depth from owner.functs
super(owner, name);
this.depth = owner.functs.length; // functs[.. null]
}
entry () { // [extend] make room for bypass
if (this.depth > 1) { // nested function
// remember where this is defined
this.scope = this.owner.funct.blocks[0];
if (typeof this.scope.bypass == 'undefined')
this.scope.bypass = // make room for bypass branch
this.owner.machine.code.push(null) - 1;
}
super.entry();
}
setParms () { // frame: parms, old-pc, old-fp, old-dp, result
super.setParms();
this.addr = this.size ++; // insert slot for old-dp
this.size += this.depth; // leave slots for display
}
storeOk () { // [replace] consider outer functions
if (this.owner.functs.some(f => f == this)) return true;
this.owner.parser.error(`${this.name}: ` +
`assigned to outside function`);
return false;
}
store () { // [replace] consider depth
if (this == this.owner.funct) // local
this.owner.machine.gen('StoreFP', this.addr);
else // outer function
this.owner.machine.gen('StoreDP', this.addr, this.depth);
}
pop () { // [extend] check for undefined functions
this.owner._check_defs(this.locals);
super.pop();
}
exit () { // [replace] uses depth, fixes bypass
this.owner.machine.code[this.start] =
this.owner.machine.ins('Entry', this.parms,
this.depth, this.frameSize);
this.owner.machine.gen('Exit'); // needs no parms info
const end = this.owner.machine.gen('Return');
if (this.scope) // need to repair bypass
this.owner.machine.code[this.scope.bypass] =
this.owner.machine.ins('Branch', end);
}
toString () { // [extend] display depth
return super.toString() + ` depth ${this.depth}`;
}
};
}
#Fun;
/** Describes a block of nested symbols.
@instance
@memberof module:Seven~Nest13
@class Block extends super.Block
@property {undefined|number} bypass - address of branch to bypass nested function definitions */
/** Replace: searches blocks, functions, and global.
@instance
@memberof module:Seven~Nest13 */
_find (name, report) {
let sym;
try {
this.functs.forEach(funct => { // loop inner to outer funct
if (funct) // loop inner to outer block
funct.blocks.forEach(block => {
sym = block.locals.get(name);
if (typeof sym != 'undefined') throw sym;
});
});
return sym = this.symbols.get(name); // global
} catch (sym) { // found in a block
if (sym instanceof Error) throw sym; // shouldn't happen
return sym;
} finally {
if (report && !sym)
this.parser.error(`${name}: undefined`);
}
}
// prog: [ vars ] funs;
// vars: 'var' names ';';
// names: Name [{ ',' Name }];
// funs: { fun };
// fun: head parms [ block ] ';';
/** `head: 'function' Name;` returns function symbol.
@instance
@memberof module:Seven~Nest13 */
head (_, name) {
let sym = this._find(name);
try {
if (sym instanceof this.Fun) {
if (sym.depth >= this.functs.length) { // same nesting level
if (typeof sym.start != 'number') throw true; // forward
this.parser.error(`${name}: duplicate`);
} // else define at deeper nesting level
} else if (sym instanceof this.Var && // same nesting level
sym.depth >= this.functs.length - 1)
this.parser.error(`${name}: used as variable and function`);
sym = this._dcl(new this.Fun(this, name)); // (re-)define
} catch (e) { throw e; // shouldn't happen
} finally {
sym.entry(); // generate code for function entry
return this.funct = sym; // in function
}
}
// parms: '(' [ names ] ')';
/** `block: begin body 'end';` [inherit].
@instance
@memberof module:Seven~Nest13 */
block (b, body, e) { super.block(b, undefined, undefined, e); }
// begin: 'begin';
// body: [ vars ] [ funs ] stmts;
// stmts: stmt [{ ';' stmt }];
// stmt: assign | print | return | block | loop | select;
// assign: symbol action;
// action: store | call;
// store: '=' sum;
// call: args;
// args: '(' [ sums ] ')';
// print: 'print' sums;
// sums: sum [{ ',' sum }];
// return: 'return' [ sum ];
/** `loop: While cmp Do body 'od';` [inherit]
@instance
@memberof module:Seven~Nest13 */
loop (While, cmp, Do, body, od) {
super.loop(While, cmp, Do, undefined, undefined, od);
}
// While: 'while';
// Do: 'do';
// select: 'if' cmp then [ else ] 'fi';
// then: Then [ body ];
/** `else: Else body;` [inherit]
@instance
@memberof module:Seven~Nest13 */
else (e, b) { return super.else(e, undefined, undefined); }
// Then: 'then';
// Else: 'else';
// cmp: sum rel;
// rel: eq | ne | gt | ge | lt | le;
// eq: '=' sum;
// ne: '<>' sum;
// gt: '>' sum;
// ge: '>=' sum;
// lt: '<' sum;
// le: '<=' sum;
// sum: product [{ add | subtract }];
// add: '+' product;
// subtract: '-' product;
// product: signed [{ multiply | divide }];
// multiply: '*' signed;
// divide: '/' signed;
// signed: [ '-' ] term;
// term: input | number | name | '(' sum ')';
// input: 'input' [ Number ];
// number: Number;
// name: symbol [ args ];
// symbol: Name;
};
export {
TCheck01,
TCheck02,
Machine04,
Functions04,
Machine06,
Parameters06,
Blocks09,
Machine13,
Nest13
};