Simpler, better cat.ts value representation

This commit is contained in:
Tony Garnock-Jones 2023-03-12 16:17:58 +01:00
parent 239d3dad97
commit 04f313aaca
2 changed files with 120 additions and 142 deletions

View File

@ -3,7 +3,9 @@ import {
Reader,
Record,
Value as PreservesValue,
embed,
is,
isEmbedded,
stringify,
} from '@syndicate-lang/core';
@ -11,7 +13,7 @@ export type Input = PreservesValue<any>[] | string;
export type Token = number | boolean | string | symbol | Token[];
export type Value<V extends VM<V>> = number | boolean | string | Closure<V> | Primitive<V> | Value<V>[];
export type Value<V extends VM<V>> = PreservesValue<Closure<V> | Primitive<V>>;
export type Closure<V extends VM<V>> = { env: EnvironmentChain<V>, code: Token[] };
export type Primitive<V extends VM<V>> = (this: V, ... args: Value<V>[]) => Value<V>[];
export type EnvironmentChain<V extends VM<V>> = null | { rib: Environment<V>, next: EnvironmentChain<V> };
@ -96,15 +98,9 @@ export class VM<Self extends VM<Self>> {
}
apply(who: string, v: Value<Self>) {
switch (typeof v) {
case 'function':
return this.invoke(v);
case 'object':
if (!Array.isArray(v)) {
return this.invoke(v);
}
/* fall through */
default:
if (isEmbedded(v)) {
return this.invoke(v.embeddedValue);
} else {
throw new TypeError('Got non-callable in `'+who+'`: ' + stringify(v));
}
}
@ -150,29 +146,15 @@ export class VM<Self extends VM<Self>> {
case 'symbol':
const v = this.lookup(op.description!);
switch (typeof v) {
case 'number':
case 'boolean':
case 'string':
this.push(v);
break;
case 'function':
case 'object':
if (Array.isArray(v)) {
this.push(v);
if (isEmbedded(v)) {
this.invoke(v.embeddedValue);
} else {
this.invoke(v);
}
break;
default:
((_: never) => { throw new Error("Unhandled environment value: " + _); })(v);
this.push(v);
}
break;
case 'object':
this.push({ env: this.frame.env, code: op });
this.push(closure(this.frame.env, op));
break;
default:
@ -205,38 +187,24 @@ export class VM<Self extends VM<Self>> {
}
_summary(): string {
function cstr(c: Token): string {
if (typeof c === 'number') {
return '' + c;
} else if (Array.isArray(c)) {
return '[' + c.map(cstr).join(' ') + ']';
} else {
return stringify(c);
}
}
function vstr(v: Value<Self>): string {
switch (typeof v) {
case 'number':
return '' + v;
case 'function':
return '#' + v.name;
case 'object':
if (Array.isArray(v)) {
return '[' + v.map(vstr).join(' ') + ']';
} else {
return '#<closure ' + cstr(v.code) + '>';
}
default:
return stringify(v);
}
}
return `${this.rstack.length}: ${vstr(this.stack)}`;
return `${this.rstack.length}: ${stringify(this.stack)}`;
}
}
export function primitiveEnvironment<V extends VM<V>>(
input: { [key: string]: Primitive<V> },
): Environment<V> {
const output: Environment<V> = {};
Object.entries(input).forEach(([k, f]) => {
f.toString = () => `#${k}`;
output[k] = embed(f);
});
return output;
}
export const D2R = Math.PI / 180;
export const Primitives: Environment<any> = {
export const Primitives: Environment<any> = primitiveEnvironment({
'+'(a, b) { return [(a as number) + (b as number)]; },
'-'(a, b) { return [(a as number) - (b as number)]; },
'*'(a, b) { return [(a as number) * (b as number)]; },
@ -249,12 +217,14 @@ export const Primitives: Environment<any> = {
'tan'(n) { return [Math.tan(n as number * D2R)]; },
'to'() {
const n_or_ns = this.nextToken('to', (v: any): v is (symbol | symbol[]) =>
typeof v === 'symbol' || (Array.isArray(v) && v.every(w => typeof w === 'symbol')));
const n_or_ns = this.nextToken('to', (v: any): v is (symbol | symbol[]) => {
return typeof v === 'symbol' ||
(Array.isArray(v) && v.every(w => typeof w === 'symbol'));
});
const ns = Array.isArray(n_or_ns) ? n_or_ns : [n_or_ns];
const vs = this.take(ns.length);
const env = this.frame.env!;
for (let i = 0; i < ns.length; i++) env.rib[ns[i].description!] = vs[i];
ns.forEach((n, i) => env.rib[n.description!] = vs[i]);
return [];
},
'quote'() {
@ -266,10 +236,10 @@ export const Primitives: Environment<any> = {
},
'eq'(a, b) { return [is(a, b)]; },
'lt'(a, b) { return [a < b]; },
'gt'(a, b) { return [a > b]; },
'le'(a, b) { return [a <= b]; },
'ge'(a, b) { return [a >= b]; },
'lt'(a, b) { return [(a as any) < (b as any)]; },
'gt'(a, b) { return [(a as any) > (b as any)]; },
'le'(a, b) { return [(a as any) <= (b as any)]; },
'ge'(a, b) { return [(a as any) >= (b as any)]; },
'swap'(v, w) { return [w, v]; },
'dup'(v) { return [v, v]; },
@ -300,10 +270,14 @@ export const Primitives: Environment<any> = {
'ifelse'(v, t, f) { if (!!v) this.apply('ifelse', t); else this.apply('ifelse', f); return []; },
'*dump*'() { console.log(this._summary()); return []; },
};
});
function _code(s: string): Closure<any> {
return ({ env: { rib: Primitives, next: null }, code: VM.parse(s) });
function closure<V extends VM<V>>(env: EnvironmentChain<V>, code: Token[]): Value<V> {
return embed({ env, code, toString: () => `#<closure ${stringify(code)}>` });
}
function _code(s: string): Value<any> {
return closure({ rib: Primitives, next: null }, VM.parse(s));
}
Object.assign(Primitives, {

View File

@ -3,11 +3,11 @@ import {
MeshBuilder,
Plane,
Quaternion,
Ray,
Scene,
Vector3,
VertexData,
} from '@babylonjs/core/Legacy/legacy';
import { stringify } from '@preserves/core';
import * as Cat from './cat.js';
import { earcut } from './earcut';
@ -293,7 +293,10 @@ export class TurtleVM extends Cat.VM<TurtleVM> {
}
}
export const TurtlePrimitives: Cat.Environment<TurtleVM> = Object.assign({}, Cat.Primitives, {
export const TurtlePrimitives: Cat.Environment<TurtleVM> = Object.assign(
{},
Cat.Primitives,
Cat.primitiveEnvironment({
'Home'() { this.pos = new Vector3(); this.q = new Quaternion(); return []; },
'GetPos'() { return [this.pos.asArray()]; },
@ -357,7 +360,7 @@ export const TurtlePrimitives: Cat.Environment<TurtleVM> = Object.assign({}, Cat
if (typeof t === 'string' && n in CapType) {
this.cap = CapType[n];
} else {
throw new Cat.TypeError("Bad cap type: " + t);
throw new Cat.TypeError("Bad cap type: " + stringify(t));
}
return [];
},
@ -370,8 +373,9 @@ export const TurtlePrimitives: Cat.Environment<TurtleVM> = Object.assign({}, Cat
case "FRONT": this.sideOrientation = Mesh.FRONTSIDE; break;
case "BACK": this.sideOrientation = Mesh.BACKSIDE; break;
case "DOUBLE": this.sideOrientation = Mesh.DOUBLESIDE; break;
default: throw new TypeError("Invalid SideOrientation: " + s);
default: throw new TypeError("Invalid SideOrientation: " + stringify(s));
}
return [];
},
} satisfies Cat.Environment<TurtleVM>);
}) satisfies Cat.Environment<TurtleVM>,
);