Simpler, better cat.ts value representation
This commit is contained in:
parent
239d3dad97
commit
04f313aaca
108
src/cat.ts
108
src/cat.ts
|
@ -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,16 +98,10 @@ 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:
|
||||
throw new TypeError('Got non-callable in `'+who+'`: ' + stringify(v));
|
||||
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);
|
||||
} else {
|
||||
this.invoke(v);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
((_: never) => { throw new Error("Unhandled environment value: " + _); })(v);
|
||||
if (isEmbedded(v)) {
|
||||
this.invoke(v.embeddedValue);
|
||||
} else {
|
||||
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, {
|
||||
|
|
154
src/turtle.ts
154
src/turtle.ts
|
@ -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,85 +293,89 @@ export class TurtleVM extends Cat.VM<TurtleVM> {
|
|||
}
|
||||
}
|
||||
|
||||
export const TurtlePrimitives: Cat.Environment<TurtleVM> = Object.assign({}, Cat.Primitives, {
|
||||
'Home'() { this.pos = new Vector3(); this.q = new Quaternion(); return []; },
|
||||
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()]; },
|
||||
'GetX'() { return [this.pos.x]; },
|
||||
'GetY'() { return [this.pos.y]; },
|
||||
'GetZ'() { return [this.pos.z]; },
|
||||
'SetX'(v) { this.pos.x = v as number; return []; },
|
||||
'SetY'(v) { this.pos.y = v as number; return []; },
|
||||
'SetZ'(v) { this.pos.z = v as number; return []; },
|
||||
'SetPos'(v) {
|
||||
const [x, y, z] = v as number[];
|
||||
this.pos.set(x as number, y as number, z as number);
|
||||
return [];
|
||||
},
|
||||
'GetPos'() { return [this.pos.asArray()]; },
|
||||
'GetX'() { return [this.pos.x]; },
|
||||
'GetY'() { return [this.pos.y]; },
|
||||
'GetZ'() { return [this.pos.z]; },
|
||||
'SetX'(v) { this.pos.x = v as number; return []; },
|
||||
'SetY'(v) { this.pos.y = v as number; return []; },
|
||||
'SetZ'(v) { this.pos.z = v as number; return []; },
|
||||
'SetPos'(v) {
|
||||
const [x, y, z] = v as number[];
|
||||
this.pos.set(x as number, y as number, z as number);
|
||||
return [];
|
||||
},
|
||||
|
||||
'GetHeading'() { return [this.euler.asArray().map(v => v / Cat.D2R)]; },
|
||||
'GetRX'() { return [this.euler.x / Cat.D2R]; },
|
||||
'GetRY'() { return [this.euler.y / Cat.D2R]; },
|
||||
'GetRZ'() { return [this.euler.z / Cat.D2R]; },
|
||||
'SetRX'(v) { this.q.x = v as number * Cat.D2R; return []; },
|
||||
'SetRY'(v) { this.q.y = v as number * Cat.D2R; return []; },
|
||||
'SetRZ'(v) { this.q.z = v as number * Cat.D2R; return []; },
|
||||
'SetHeading'(v) {
|
||||
const [x, y, z] = v as number[];
|
||||
this.q = Quaternion.FromEulerAngles(x as number * Cat.D2R, y as number * Cat.D2R, z as number * Cat.D2R);
|
||||
return [];
|
||||
},
|
||||
'GetHeading'() { return [this.euler.asArray().map(v => v / Cat.D2R)]; },
|
||||
'GetRX'() { return [this.euler.x / Cat.D2R]; },
|
||||
'GetRY'() { return [this.euler.y / Cat.D2R]; },
|
||||
'GetRZ'() { return [this.euler.z / Cat.D2R]; },
|
||||
'SetRX'(v) { this.q.x = v as number * Cat.D2R; return []; },
|
||||
'SetRY'(v) { this.q.y = v as number * Cat.D2R; return []; },
|
||||
'SetRZ'(v) { this.q.z = v as number * Cat.D2R; return []; },
|
||||
'SetHeading'(v) {
|
||||
const [x, y, z] = v as number[];
|
||||
this.q = Quaternion.FromEulerAngles(x as number * Cat.D2R, y as number * Cat.D2R, z as number * Cat.D2R);
|
||||
return [];
|
||||
},
|
||||
|
||||
'F'(dist) { this.forwardBy(dist as number); return []; },
|
||||
'B'(dist) { this.forwardBy(-(dist as number)); return []; },
|
||||
'F'(dist) { this.forwardBy(dist as number); return []; },
|
||||
'B'(dist) { this.forwardBy(-(dist as number)); return []; },
|
||||
|
||||
'RX'(degrees) { this.rotate(0, degrees as number, 0); return []; },
|
||||
'RY'(degrees) { this.rotate(degrees as number, 0, 0); return []; },
|
||||
'RZ'(degrees) { this.rotate(0, 0, degrees as number); return []; },
|
||||
'RX'(degrees) { this.rotate(0, degrees as number, 0); return []; },
|
||||
'RY'(degrees) { this.rotate(degrees as number, 0, 0); return []; },
|
||||
'RZ'(degrees) { this.rotate(0, 0, degrees as number); return []; },
|
||||
|
||||
'U'(degrees) { this.relativeRotate(0, -(degrees as number), 0); return []; },
|
||||
'D'(degrees) { this.relativeRotate(0, degrees as number, 0); return []; },
|
||||
'L'(degrees) { this.relativeRotate(-(degrees as number), 0, 0); return []; },
|
||||
'R'(degrees) { this.relativeRotate(degrees as number, 0, 0); return []; },
|
||||
'CW'(degrees) { this.relativeRotate(0, 0, -(degrees as number)); return []; },
|
||||
'CCW'(degrees) { this.relativeRotate(0, 0, degrees as number); return []; },
|
||||
'U'(degrees) { this.relativeRotate(0, -(degrees as number), 0); return []; },
|
||||
'D'(degrees) { this.relativeRotate(0, degrees as number, 0); return []; },
|
||||
'L'(degrees) { this.relativeRotate(-(degrees as number), 0, 0); return []; },
|
||||
'R'(degrees) { this.relativeRotate(degrees as number, 0, 0); return []; },
|
||||
'CW'(degrees) { this.relativeRotate(0, 0, -(degrees as number)); return []; },
|
||||
'CCW'(degrees) { this.relativeRotate(0, 0, degrees as number); return []; },
|
||||
|
||||
'ClearPen'() { this.pen.clear(); return []; },
|
||||
'DefinePen'() { this.pen.set(); return []; },
|
||||
'GetPen'() { return [this.pen.templatePath.map(v => v.asArray())]; },
|
||||
'SetPen'(p0) {
|
||||
const p = p0 as [number, number, number][];
|
||||
if (this.pen.isDown && this.pen.templatePath.length !== p.length) {
|
||||
throw new PenError("Cannot set pen with different number of points when pen is down");
|
||||
}
|
||||
this.pen.templatePath = p.map(v => Vector3.FromArray(v));
|
||||
return [];
|
||||
},
|
||||
'ClearPen'() { this.pen.clear(); return []; },
|
||||
'DefinePen'() { this.pen.set(); return []; },
|
||||
'GetPen'() { return [this.pen.templatePath.map(v => v.asArray())]; },
|
||||
'SetPen'(p0) {
|
||||
const p = p0 as [number, number, number][];
|
||||
if (this.pen.isDown && this.pen.templatePath.length !== p.length) {
|
||||
throw new PenError("Cannot set pen with different number of points when pen is down");
|
||||
}
|
||||
this.pen.templatePath = p.map(v => Vector3.FromArray(v));
|
||||
return [];
|
||||
},
|
||||
|
||||
'PenDown'() { this.penDown(); return []; },
|
||||
'PenUp'() { this.penUp(false); return []; },
|
||||
'Close'() { this.penUp(true); return []; },
|
||||
'PenDown'() { this.penDown(); return []; },
|
||||
'PenUp'() { this.penUp(false); return []; },
|
||||
'Close'() { this.penUp(true); return []; },
|
||||
|
||||
'SetCap'(t) {
|
||||
const n = t as keyof typeof CapType;
|
||||
if (typeof t === 'string' && n in CapType) {
|
||||
this.cap = CapType[n];
|
||||
} else {
|
||||
throw new Cat.TypeError("Bad cap type: " + t);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
'SetWireframe'(b) { this.wireframe = b as boolean; return []; },
|
||||
'SetSmooth'(b) { this.smooth = b as boolean; return []; },
|
||||
'SetMiter'(b) { this.miter = b as boolean; return []; },
|
||||
'SetSideOrientation'(s) {
|
||||
switch (s) {
|
||||
case "DEFAULT": this.sideOrientation = Mesh.DEFAULTSIDE; break;
|
||||
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);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
} satisfies Cat.Environment<TurtleVM>);
|
||||
'SetCap'(t) {
|
||||
const n = t as keyof typeof CapType;
|
||||
if (typeof t === 'string' && n in CapType) {
|
||||
this.cap = CapType[n];
|
||||
} else {
|
||||
throw new Cat.TypeError("Bad cap type: " + stringify(t));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
'SetWireframe'(b) { this.wireframe = b as boolean; return []; },
|
||||
'SetSmooth'(b) { this.smooth = b as boolean; return []; },
|
||||
'SetMiter'(b) { this.miter = b as boolean; return []; },
|
||||
'SetSideOrientation'(s) {
|
||||
switch (s) {
|
||||
case "DEFAULT": this.sideOrientation = Mesh.DEFAULTSIDE; break;
|
||||
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: " + stringify(s));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
}) satisfies Cat.Environment<TurtleVM>,
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue