|
|
|
|
@ -1,16 +1,34 @@ |
|
|
|
|
import { withLog, withProfile } from './debug'; |
|
|
|
|
import { withProfile as simpleWithProfile, withLog } from './debug'; |
|
|
|
|
|
|
|
|
|
const withProfile = (term: Term, action: string, func: () => Term) => simpleWithProfile( |
|
|
|
|
`${term.expression()}${term.cache ? ' [cached]' : ''}`, |
|
|
|
|
{ |
|
|
|
|
cat: action, |
|
|
|
|
id: term.id, |
|
|
|
|
bindings: Object.fromEntries(Object.entries(term.bindings).map(([k, v]) => [k, v.toString()])), |
|
|
|
|
}, |
|
|
|
|
func, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
let index = 0; |
|
|
|
|
|
|
|
|
|
export abstract class Term { |
|
|
|
|
protected id = index++; |
|
|
|
|
protected cache: Term | null = null; |
|
|
|
|
public id = index++; |
|
|
|
|
public cache: Term | null = null; |
|
|
|
|
|
|
|
|
|
public constructor(protected bindings: Record<string, Term>) { } |
|
|
|
|
public constructor() { } |
|
|
|
|
|
|
|
|
|
public refs(_name: string): boolean { |
|
|
|
|
return false; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public bound(_name: string): boolean { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get bindings(): Record<string, Term> { |
|
|
|
|
return {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public bind(_name: string, _term: Term): Term { |
|
|
|
|
return this; |
|
|
|
|
@ -49,29 +67,29 @@ export abstract class Term { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Termify = { |
|
|
|
|
(term: Term): Term; |
|
|
|
|
(name: string): Term; |
|
|
|
|
(namespace: string, name: string): Term; |
|
|
|
|
} |
|
|
|
|
(term: Term): Term |
|
|
|
|
(name: string): Term |
|
|
|
|
(namespace: string, name: string): Term |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const termify: Termify = (termOrNameOrNamespace: Term | string, maybeName?: string) => |
|
|
|
|
typeof termOrNameOrNamespace === 'string' ? r(termOrNameOrNamespace as string, maybeName as string) : termOrNameOrNamespace; |
|
|
|
|
|
|
|
|
|
export class Undef extends Term { |
|
|
|
|
constructor() { super({}); } |
|
|
|
|
constructor() { super(); } |
|
|
|
|
|
|
|
|
|
override reduce(): Term { |
|
|
|
|
throw new Error(`undef`); |
|
|
|
|
throw new Error('undef'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class Native extends Term { |
|
|
|
|
constructor( |
|
|
|
|
public constructor( |
|
|
|
|
public value: any, |
|
|
|
|
) { super({}); } |
|
|
|
|
) { super(); } |
|
|
|
|
|
|
|
|
|
public override expression(): string { |
|
|
|
|
return `\`${JSON.stringify(this.value)}\`` |
|
|
|
|
return `\`${JSON.stringify(this.value)}\``; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override stringify(): string { |
|
|
|
|
@ -80,17 +98,25 @@ export class Native extends Term { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class Deferred extends Term { |
|
|
|
|
constructor( |
|
|
|
|
public func: (bindings: Record<string, Term>) => Term, |
|
|
|
|
bindings: Record<string, Term> = {}, |
|
|
|
|
) { super(bindings); } |
|
|
|
|
public constructor( |
|
|
|
|
protected func: (bindings: Record<string, Term>) => Term, |
|
|
|
|
protected _bindings: Record<string, Term> = {}, |
|
|
|
|
) { super(); } |
|
|
|
|
|
|
|
|
|
override refs(_name: string): boolean { |
|
|
|
|
public override refs(_name: string): boolean { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override bound(name: string): boolean { |
|
|
|
|
return !!this._bindings[name]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override get bindings(): Record<string, Term> { |
|
|
|
|
return this._bindings; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override bind(name: string, term: Term): Term { |
|
|
|
|
return this.bindings[name] || !this.refs(name) |
|
|
|
|
return this.bound(name) || !this.refs(name) |
|
|
|
|
? this |
|
|
|
|
: new Deferred(this.func, { ...this.bindings, [name]: term }); |
|
|
|
|
} |
|
|
|
|
@ -99,8 +125,8 @@ export class Deferred extends Term { |
|
|
|
|
return this.cache = withLog( |
|
|
|
|
`Executing ${this.stringify()}`, |
|
|
|
|
() => withProfile( |
|
|
|
|
`${this.expression()}${this.cache ? ' [cached]' : ''}`, |
|
|
|
|
{ cat: 'deferred' }, |
|
|
|
|
this, |
|
|
|
|
'deferred', |
|
|
|
|
() => this.cache ?? Object.entries(this.bindings).reduce((t, b) => t.bind(...b), this.func(this.bindings)).reduce(), |
|
|
|
|
), |
|
|
|
|
r => `got ${r.stringify()}`, |
|
|
|
|
@ -113,54 +139,69 @@ export class Deferred extends Term { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class Ref extends Term { |
|
|
|
|
constructor( |
|
|
|
|
public namespace: string, |
|
|
|
|
public name: string, |
|
|
|
|
bindings: Record<string, Term> = {}, |
|
|
|
|
) { super(bindings); } |
|
|
|
|
public constructor( |
|
|
|
|
protected namespace: string, |
|
|
|
|
protected name: string, |
|
|
|
|
protected binding: Term | null = null, |
|
|
|
|
) { super(); } |
|
|
|
|
|
|
|
|
|
override refs(name: string): boolean { |
|
|
|
|
public override refs(name: string): boolean { |
|
|
|
|
return this.name === name; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override bound(name: string): boolean { |
|
|
|
|
return this.name === name && !!this.binding; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override get bindings(): Record<string, Term> { |
|
|
|
|
return this.binding ? { [this.name]: this.binding } : {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override bind(name: string, term: Term): Term { |
|
|
|
|
return this.bindings[name] || !this.refs(name) |
|
|
|
|
return this.bound(name) || !this.refs(name) |
|
|
|
|
? this |
|
|
|
|
: new Ref(this.namespace, this.name, { [name]: term }); |
|
|
|
|
: new Ref(this.namespace, this.name, term); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override reduce(): Term { |
|
|
|
|
return this.cache = withLog( |
|
|
|
|
`Substituting ${this.stringify()}`, |
|
|
|
|
() => withProfile( |
|
|
|
|
`${this.expression()}${this.cache ? ' [cached]' : ''}`, |
|
|
|
|
{ cat: 'ref' }, |
|
|
|
|
() => this.cache ?? (this.namespace === 'local' ? this.bindings[this.name] : globals[`${this.namespace}::${this.name}`] ?? new Undef()).reduce(), |
|
|
|
|
this, |
|
|
|
|
'deref', |
|
|
|
|
() => this.cache ?? ((this.namespace === 'local' ? this.binding : globals[`${this.namespace}::${this.name}`]) ?? new Undef()).reduce(), |
|
|
|
|
), |
|
|
|
|
r => `with ${r.stringify()}`, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override expression(): string { |
|
|
|
|
public override expression(): string { |
|
|
|
|
return this.name; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class Lambda extends Term { |
|
|
|
|
constructor( |
|
|
|
|
public constructor( |
|
|
|
|
public param: string, |
|
|
|
|
public body: Term, |
|
|
|
|
bindings: Record<string, Term> = {}, |
|
|
|
|
) { super(bindings); } |
|
|
|
|
) { super(); } |
|
|
|
|
|
|
|
|
|
override refs(name: string): boolean { |
|
|
|
|
public override refs(name: string): boolean { |
|
|
|
|
return this.param !== name && this.body.refs(name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override bind(name: string, term: Term): Term { |
|
|
|
|
return this.bindings[name] || !this.refs(name) |
|
|
|
|
public override bound(name: string): boolean { |
|
|
|
|
return this.param !== name && this.body.bound(name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override get bindings(): Record<string, Term> { |
|
|
|
|
return this.body.bindings; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override bind(name: string, term: Term): Term { |
|
|
|
|
return this.bound(name) || !this.refs(name) |
|
|
|
|
? this |
|
|
|
|
: new Lambda(this.param, this.body.bind(name, term), { ...this.bindings, [name]: term }); |
|
|
|
|
: new Lambda(this.param, this.body.bind(name, term)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override expression(): string { |
|
|
|
|
@ -171,49 +212,59 @@ export class Lambda extends Term { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class Call extends Term { |
|
|
|
|
constructor( |
|
|
|
|
public constructor( |
|
|
|
|
public func: Term, |
|
|
|
|
public arg: Term, |
|
|
|
|
bindings: Record<string, Term> = {}, |
|
|
|
|
) { super(bindings); } |
|
|
|
|
) { super(); } |
|
|
|
|
|
|
|
|
|
override refs(name: string): boolean { |
|
|
|
|
public override refs(name: string): boolean { |
|
|
|
|
return this.func.refs(name) || this.arg.refs(name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override bind(name: string, term: Term): Term { |
|
|
|
|
return this.bindings[name] || !this.refs(name) |
|
|
|
|
public override bound(name: string): boolean { |
|
|
|
|
return this.func.bound(name) || this.arg.bound(name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override get bindings(): Record<string, Term> { |
|
|
|
|
return { ...this.func.bindings, ...this.arg.bindings }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override bind(name: string, term: Term): Term { |
|
|
|
|
return this.bound(name) || !this.refs(name) |
|
|
|
|
? this |
|
|
|
|
: new Call(this.func.bind(name, term), this.arg.bind(name, term), { ...this.bindings, [name]: term }); |
|
|
|
|
: new Call(this.func.bind(name, term), this.arg.bind(name, term)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override reduce(): Term { |
|
|
|
|
public override reduce(): Term { |
|
|
|
|
return this.cache = withLog( |
|
|
|
|
`Reducing ${this.stringify()}`, |
|
|
|
|
() => withProfile( |
|
|
|
|
`${this.expression()}${this.cache ? ' [cached]' : ''}`, |
|
|
|
|
{ cat: 'call' }, |
|
|
|
|
() => this.cache ?? (() => { |
|
|
|
|
this, |
|
|
|
|
'call', |
|
|
|
|
() => { |
|
|
|
|
if (this.cache) { |
|
|
|
|
return this.cache; |
|
|
|
|
} |
|
|
|
|
const func = this.func.reduce(); |
|
|
|
|
if (!(func instanceof Lambda)) { |
|
|
|
|
return new Undef(); |
|
|
|
|
} |
|
|
|
|
return withLog( |
|
|
|
|
`Binding ${this.arg.stringify()} to ${func.param}`, |
|
|
|
|
() => withProfile( |
|
|
|
|
`${func.param} = ${this.arg.expression()}`, |
|
|
|
|
() => simpleWithProfile( |
|
|
|
|
`${func.param} = ${this.arg}`, |
|
|
|
|
{ cat: 'binding' }, |
|
|
|
|
() => func.body.bind(func.param, this.arg), |
|
|
|
|
), |
|
|
|
|
r => `got ${r.stringify()}`, |
|
|
|
|
).reduce(); |
|
|
|
|
})(), |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
r => `to ${r.stringify()}`, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override expression(): string { |
|
|
|
|
public override expression(): string { |
|
|
|
|
return `${this.func instanceof Lambda ? `(${this.func.expression()})` : this.func.expression()} ${this.arg instanceof Lambda || this.arg instanceof Call ? `(${this.arg.expression()})` : this.arg.expression()}`; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -234,26 +285,25 @@ export const g = (name: string, term: Term) => { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
type Refify = { |
|
|
|
|
(name: string): Ref; |
|
|
|
|
(namespace: string, name: string): Ref; |
|
|
|
|
} |
|
|
|
|
(name: string): Ref |
|
|
|
|
(namespace: string, name: string): Ref |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export const r: Refify = (namespaceOrName: string, name?: string) => new Ref(name ? namespaceOrName : 'local', name ?? namespaceOrName); |
|
|
|
|
|
|
|
|
|
type Lambdify = { |
|
|
|
|
(param: string, body: Term): Lambda; |
|
|
|
|
(param: string, ref: string): Lambda; |
|
|
|
|
} |
|
|
|
|
(param: string, body: Term): Lambda |
|
|
|
|
(param: string, ref: string): Lambda |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export const l: Lambdify = (param: string, body: Term | string): Lambda => new Lambda(param, termify(body as string)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type Deferedify = { |
|
|
|
|
(value: (bindings: Record<string, Term>) => Term): Deferred; |
|
|
|
|
(value: boolean | number | string | object): Native; |
|
|
|
|
} |
|
|
|
|
(value: (bindings: Record<string, Term>) => Term): Deferred |
|
|
|
|
(value: boolean | number | string | object): Native |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export const _: Deferedify = ((value: any) => (typeof value === 'function' ? new Deferred(value) : new Native(value))) as Deferedify; |
|
|
|
|
export const _: Deferedify = ((value: any) => typeof value === 'function' ? new Deferred(value) : new Native(value)) as Deferedify; |
|
|
|
|
export const $ = <T>(t: Term): T => { |
|
|
|
|
const rt = t.reduce(); |
|
|
|
|
return rt instanceof Native ? rt.value : rt.stringify(); |
|
|
|
|
|