change the way text suggestions are rendered + option to disable syntactic sugar

This commit is contained in:
Joeri Exelmans 2025-05-15 22:22:45 +02:00
parent ea8c015eff
commit 2d81e42447
12 changed files with 357 additions and 291 deletions

View file

@ -1,27 +1,32 @@
import { apply, Double, Int, NotAFunctionError, trie, UnifyError } from "dope2";
import { apply, assignFn, Double, getSymbol, Int, makeGeneric, NotAFunctionError, prettyT, symbolFunction, trie, UnifyError } from "dope2";
import type { EditorState } from "./Editor";
import type { InputValueType } from "./InputBlock";
import { makeInnerEnv } from "./LetInBlock";
import { parseDouble, parseInt } from "./util/parse";
export class DeepError {
export interface DeepError {
kind: "error";
e: Error;
depth: number;
constructor(e, depth) {
this.e = e;
this.depth = depth;
}
};
t: any;
}
// a dynamically typed value = tuple (instance, type)
export interface Dynamic {
kind: "value",
i: any;
t: any;
};
export interface Unknown {
kind: "unknown";
t: any;
}
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a) };
// the value of every block is either known (Dynamic), an error, or unknown
export type ResolvedType = Dynamic | DeepError | undefined;
export type ResolvedType = Dynamic | DeepError | Unknown;
export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
if (s.kind === "input") {
@ -39,8 +44,9 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
}
if (s.kind === "lambda") {
const expr = evalEditorBlock(s.expr, env);
return undefined; // todo
// todo
}
return entirelyUnknown; // todo
};
export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType {
@ -48,45 +54,115 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
return parseLiteral(text, value.type);
}
else if (value.kind === "name") {
return trie.get(env.name2dyn)(text);
const found = trie.get(env.name2dyn)(text);
if (found) {
return { kind: "value", ...found };
} else {
return entirelyUnknown;
}
}
else { // kind === "text" -> unresolved
return;
return entirelyUnknown;
}
}
export function evalCallBlock(fn: ResolvedType, input: ResolvedType) {
if (haveValue(input) && haveValue(fn)) {
try {
const outputResolved = apply(input)(fn); // may throw
return outputResolved; // success
}
catch (e) {
if (!(e instanceof UnifyError) && !(e instanceof NotAFunctionError)) {
throw e;
}
return new DeepError(e, 0); // eval error
export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType {
if (getSymbol(fn.t) !== symbolFunction) {
if (fn.kind === "unknown") {
return entirelyUnknown; // don't flash everything red, giving the user a heart attack
}
// worst outcome: we know nothing about the result!
return {
kind: "error",
e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`),
t: entirelyUnknown.t,
depth: 0,
};
}
else if (input instanceof DeepError) {
return input; // bubble up the error
}
else if (fn instanceof DeepError) {
return new DeepError(fn.e, fn.depth+1);
}
}
try {
// fn is a function...
const outType = assignFn(fn.t, input.t); // may throw
function parseLiteral(text: string, type: string) {
// dirty
if (type === "Int") {
return { i: parseInt(text), t: Int };
if (input.kind === "error") {
return {
kind: "error",
e: input.e, // bubble up the error
depth: 0,
t: outType,
};
}
if (fn.kind === "error") {
// also bubble up
return {
kind: "error",
e: fn.e,
depth: fn.depth+1,
t: outType,
};
}
// 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 };
}
else {
// we don't know the value, but we do know the type:
return { kind: "unknown", t: outType };
}
}
if (type === "Double") {
return { i: parseDouble(text), t: Double };
catch (e) {
if ((e instanceof UnifyError)) {
// even though fn was incompatible with the given parameter, we can still suppose that our output-type will be that of fn...?
const outType = fn.t.params[1](fn.t);
return {
kind: "error",
e,
depth: 0,
t: outType,
};
}
throw e;
}
}
export function haveValue(resolved: ResolvedType) {
return resolved && !(resolved instanceof DeepError);
// return resolved && !(resolved instanceof DeepError);
return resolved.kind === "value";
}
function parseLiteral(text: string, type: string): ResolvedType {
// dirty
if (type === "Int") {
return parseAsInt(text);
}
if (type === "Double") {
return parseAsDouble(text);
}
return entirelyUnknown;
}
function parseAsDouble(text: string): ResolvedType {
if (text !== '') {
const num = Number(text);
if (!Number.isNaN(num)) {
return { kind: "value", i: num, t: Double };
}
}
return entirelyUnknown;
}
function parseAsInt(text: string): ResolvedType {
if (text !== '') {
try {
return { kind: "value", i: BigInt(text), t: Int }; // may throw
}
catch {}
}
return entirelyUnknown;
}
const literalParsers = [parseAsDouble, parseAsInt];
export function attemptParseLiteral(text: string): Dynamic[] {
return literalParsers.map(parseFn => parseFn(text))
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
}