nicer looking
This commit is contained in:
parent
8abbac4bc9
commit
e850952738
14 changed files with 547 additions and 104 deletions
122
src/eval.ts
122
src/eval.ts
|
|
@ -1,29 +1,36 @@
|
|||
import { apply, assignFn, Double, getSymbol, Int, makeGeneric, NotAFunctionError, prettyT, symbolFunction, trie, UnifyError } from "dope2";
|
||||
import { assignFnSubstitutions, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2";
|
||||
|
||||
import type { EditorState } from "./Editor";
|
||||
import type { InputValueType } from "./InputBlock";
|
||||
import { makeInnerEnv } from "./LetInBlock";
|
||||
import type { InputValueType, SuggestionType } from "./InputBlock";
|
||||
|
||||
interface Type {
|
||||
symbol: string;
|
||||
params: any[];
|
||||
};
|
||||
|
||||
export interface DeepError {
|
||||
kind: "error";
|
||||
e: Error;
|
||||
depth: number;
|
||||
t: any;
|
||||
t: Type;
|
||||
substitutions: Map<Type,Type>;
|
||||
}
|
||||
|
||||
// a dynamically typed value = tuple (instance, type)
|
||||
export interface Dynamic {
|
||||
kind: "value",
|
||||
i: any;
|
||||
t: any;
|
||||
t: Type;
|
||||
substitutions: Map<Type,Type>;
|
||||
};
|
||||
|
||||
export interface Unknown {
|
||||
kind: "unknown";
|
||||
t: any;
|
||||
t: Type;
|
||||
substitutions: Map<Type,Type>;
|
||||
}
|
||||
|
||||
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a) };
|
||||
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a), substitutions: new Map() };
|
||||
|
||||
// the value of every block is either known (Dynamic), an error, or unknown
|
||||
export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||
|
|
@ -41,8 +48,8 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
|
|||
return evalLetInBlock(s.value, s.name, s.inner, env);
|
||||
}
|
||||
if (s.kind === "lambda") {
|
||||
const expr = evalEditorBlock(s.expr, env);
|
||||
// todo
|
||||
return evalLambdaBlock(s.paramName, s.expr, env);
|
||||
|
||||
}
|
||||
return entirelyUnknown; // todo
|
||||
};
|
||||
|
|
@ -54,7 +61,11 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
|
|||
else if (value.kind === "name") {
|
||||
const found = trie.get(env.name2dyn)(text);
|
||||
if (found) {
|
||||
return { kind: "value", ...found };
|
||||
return {
|
||||
kind: found.kind || "value",
|
||||
...found,
|
||||
substitutions: new Map(),
|
||||
};
|
||||
} else {
|
||||
return entirelyUnknown;
|
||||
}
|
||||
|
|
@ -64,6 +75,10 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
|
|||
}
|
||||
}
|
||||
|
||||
const mergeMaps = (...maps: Map<Type,Type>[]) => {
|
||||
return new Map(maps.flatMap(m => [...m]));
|
||||
}
|
||||
|
||||
export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType {
|
||||
if (getSymbol(fn.t) !== symbolFunction) {
|
||||
if (fn.kind === "unknown") {
|
||||
|
|
@ -74,12 +89,15 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
|
|||
kind: "error",
|
||||
e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`),
|
||||
t: entirelyUnknown.t,
|
||||
substitutions: mergeMaps(fn.substitutions, input.substitutions),
|
||||
depth: 0,
|
||||
};
|
||||
}
|
||||
try {
|
||||
// fn is a function...
|
||||
const outType = assignFn(fn.t, input.t); // may throw
|
||||
const [outType, substitutions] = assignFnSubstitutions(fn.t, input.t); // may throw
|
||||
|
||||
const mergedSubstitutions = mergeMaps(substitutions, fn.substitutions, input.substitutions);
|
||||
|
||||
if (input.kind === "error") {
|
||||
return {
|
||||
|
|
@ -87,6 +105,7 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
|
|||
e: input.e, // bubble up the error
|
||||
depth: 0,
|
||||
t: outType,
|
||||
substitutions: mergedSubstitutions,
|
||||
};
|
||||
}
|
||||
if (fn.kind === "error") {
|
||||
|
|
@ -96,16 +115,26 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
|
|||
e: fn.e,
|
||||
depth: fn.depth+1,
|
||||
t: outType,
|
||||
substitutions: mergedSubstitutions,
|
||||
};
|
||||
}
|
||||
// if the above statement did not throw => types are compatible...
|
||||
if (input.kind === "value" && fn.kind === "value") {
|
||||
const outValue = fn.i(input.i);
|
||||
return { kind: "value", i: outValue, t: outType };
|
||||
return {
|
||||
kind: "value",
|
||||
i: outValue,
|
||||
t: outType,
|
||||
substitutions: mergedSubstitutions,
|
||||
};
|
||||
}
|
||||
else {
|
||||
// we don't know the value, but we do know the type:
|
||||
return { kind: "unknown", t: outType };
|
||||
return {
|
||||
kind: "unknown",
|
||||
t: outType,
|
||||
substitutions: mergedSubstitutions,
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
|
@ -117,18 +146,49 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
|
|||
e,
|
||||
depth: 0,
|
||||
t: outType,
|
||||
substitutions: mergeMaps(fn.substitutions, input.substitutions),
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env) {
|
||||
export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env): ResolvedType {
|
||||
const valueResolved = evalEditorBlock(value, env);
|
||||
const innerEnv = makeInnerEnv(env, name, valueResolved)
|
||||
return evalEditorBlock(inner, innerEnv);
|
||||
}
|
||||
|
||||
export function evalLambdaBlock(paramName: string, expr: EditorState, env): ResolvedType {
|
||||
const fn = (x: any) => {
|
||||
const innerEnv = makeInnerEnv(env, paramName, {
|
||||
kind: "value",
|
||||
i: x,
|
||||
t: TYPE_VARS[0],
|
||||
substitutions: new Map(),
|
||||
});
|
||||
const result = evalEditorBlock(expr, innerEnv);
|
||||
if (result.kind === "value") {
|
||||
return result.i;
|
||||
}
|
||||
}
|
||||
// static env: we only know the name and the type
|
||||
const staticInnerEnv = makeInnerEnv(env, paramName, {
|
||||
kind: "unknown", // parameter value is not statically known
|
||||
t: TYPE_VARS[0],
|
||||
substitutions: new Map(),
|
||||
});
|
||||
const abstractOutput = evalEditorBlock(expr, staticInnerEnv);
|
||||
const t = fnType(_ => entirelyUnknown.t)(_ => abstractOutput.t);
|
||||
const T = substitute(t, abstractOutput.substitutions, [])
|
||||
return {
|
||||
kind: "value",
|
||||
t: T,
|
||||
i: fn,
|
||||
substitutions: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
export function haveValue(resolved: ResolvedType) {
|
||||
// return resolved && !(resolved instanceof DeepError);
|
||||
return resolved.kind === "value";
|
||||
|
|
@ -149,7 +209,12 @@ function parseAsDouble(text: string): ResolvedType {
|
|||
if (text !== '') {
|
||||
const num = Number(text);
|
||||
if (!Number.isNaN(num)) {
|
||||
return { kind: "value", i: num, t: Double };
|
||||
return {
|
||||
kind: "value",
|
||||
i: num,
|
||||
t: Double,
|
||||
substitutions: new Map(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return entirelyUnknown;
|
||||
|
|
@ -157,7 +222,12 @@ function parseAsDouble(text: string): ResolvedType {
|
|||
function parseAsInt(text: string): ResolvedType {
|
||||
if (text !== '') {
|
||||
try {
|
||||
return { kind: "value", i: BigInt(text), t: Int }; // may throw
|
||||
return {
|
||||
kind: "value",
|
||||
i: BigInt(text),
|
||||
t: Int,
|
||||
substitutions: new Map(),
|
||||
}; // may throw
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
|
@ -171,10 +241,14 @@ export function attemptParseLiteral(text: string): Dynamic[] {
|
|||
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
|
||||
}
|
||||
|
||||
export function scoreResolved(resolved: ResolvedType, outPriority) {
|
||||
export function scoreResolved(resolved: ResolvedType, outPriority: (s:SuggestionType) => number) {
|
||||
const bias = outPriority(['literal', '<computed>',
|
||||
// @ts-ignore: // TODO fix this
|
||||
{t: resolved.t}]);
|
||||
{
|
||||
// @ts-ignore
|
||||
kind: "unknown",
|
||||
t: resolved.t,
|
||||
substitutions: new Map(),
|
||||
}]);
|
||||
|
||||
if (resolved.kind === "value") {
|
||||
return 1 + bias;
|
||||
|
|
@ -188,4 +262,12 @@ export function scoreResolved(resolved: ResolvedType, outPriority) {
|
|||
else {
|
||||
return -2 + bias; // even worse: fn is not a function!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function makeInnerEnv(env, name: string, value: ResolvedType) {
|
||||
if (name !== "" && value.kind === "value") {
|
||||
return growEnv(env)(name)(value);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue