Start preparing primitives for UI
This commit is contained in:
parent
5e7578c4a3
commit
1ad1c8c442
2 changed files with 153 additions and 4 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import { SignalExpr } from "../expr";
|
import { Expr, SignalExpr, SignalName } from "../expr";
|
||||||
import { Program } from "../program";
|
import { Program } from "../program";
|
||||||
import { Result, RuntimeError, ThrownRuntimeError } from "./error";
|
import { Result, RuntimeError, ThrownRuntimeError } from "./error";
|
||||||
import { eval_expr } from "./evaluator";
|
import { eval_expr, eval_start } from "./evaluator";
|
||||||
import { Env, Value } from "./value";
|
import { Env, Value } from "./value";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,6 +50,13 @@ export function eval_signal_expression(program: Program, e: SignalExpr): Signal<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// may throw `ThrownRuntimeError`
|
||||||
|
export function signal_set_value(program: Program, sig: Signal<Value>, e: Expr): Value {
|
||||||
|
const value = eval_expr(program, Env.nil(), e);
|
||||||
|
sig.set(() => value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Signal<T> {
|
export interface Signal<T> {
|
||||||
state: T,
|
state: T,
|
||||||
observers: Observer<T>[],
|
observers: Observer<T>[],
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { eval_signal_expression, makeTickSignal, Signal, SignalId, SignalRuntime } from "./eval/signal";
|
import { eval_signal_expression, makeTickSignal, signal, Signal, SignalId, SignalRuntime } from "./eval/signal";
|
||||||
import { ThrownRuntimeError } from "./eval/error";
|
import { ThrownRuntimeError } from "./eval/error";
|
||||||
import { Value } from "./eval/value";
|
import { Env, Value } from "./eval/value";
|
||||||
import { Expr, FunctionName, SignalName, ProductPattern, SignalExpr } from "./expr";
|
import { Expr, FunctionName, SignalName, ProductPattern, SignalExpr } from "./expr";
|
||||||
import { installPrimitives } from "./primitive";
|
import { installPrimitives } from "./primitive";
|
||||||
|
import { eval_expr } from "./eval/evaluator";
|
||||||
|
|
||||||
export type Timestamp = number;
|
export type Timestamp = number;
|
||||||
|
|
||||||
|
|
@ -47,6 +48,7 @@ export type Implementation = (args: Value[]) => Value
|
||||||
// === Signals ===
|
// === Signals ===
|
||||||
export type SignalDefinition =
|
export type SignalDefinition =
|
||||||
| { tag: "user", def: UserSignalDefinition }
|
| { tag: "user", def: UserSignalDefinition }
|
||||||
|
| { tag: "cell", def: CellDefinition }
|
||||||
| { tag: "primitive", def: PrimitiveSignalDefinition }
|
| { tag: "primitive", def: PrimitiveSignalDefinition }
|
||||||
|
|
||||||
type UserSignalDefinition = {
|
type UserSignalDefinition = {
|
||||||
|
|
@ -62,6 +64,19 @@ type UserSignalDefinition = {
|
||||||
lastModifiedAt: Timestamp,
|
lastModifiedAt: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CellDefinition = {
|
||||||
|
name: SignalName,
|
||||||
|
raw_body: string,
|
||||||
|
|
||||||
|
body: Expr,
|
||||||
|
|
||||||
|
signalId?: SignalId,
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
createdAt: Timestamp,
|
||||||
|
lastModifiedAt: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
type PrimitiveSignalDefinition = {
|
type PrimitiveSignalDefinition = {
|
||||||
name: FunctionName,
|
name: FunctionName,
|
||||||
signalId: SignalId,
|
signalId: SignalId,
|
||||||
|
|
@ -76,6 +91,12 @@ export namespace Program {
|
||||||
| { tag: "CannotDeletePrimitiveFunction", name: FunctionName }
|
| { tag: "CannotDeletePrimitiveFunction", name: FunctionName }
|
||||||
| { tag: "PrimitiveFunctionAlreadyExists", name: FunctionName }
|
| { tag: "PrimitiveFunctionAlreadyExists", name: FunctionName }
|
||||||
|
|
||||||
|
| { tag: "DuplicateSignalName", name: SignalName }
|
||||||
|
| { tag: "SignalNotFound", name: SignalName }
|
||||||
|
| { tag: "CannotEditPrimitiveSignal", name: SignalName }
|
||||||
|
| { tag: "CannotDeletePrimitiveSignal", name: SignalName }
|
||||||
|
| { tag: "PrimitiveSignalAlreadyExists", name: SignalName }
|
||||||
|
|
||||||
type Result<T> =
|
type Result<T> =
|
||||||
| { tag: "ok", value: T }
|
| { tag: "ok", value: T }
|
||||||
| { tag: "error", error: Error }
|
| { tag: "error", error: Error }
|
||||||
|
|
@ -185,6 +206,24 @@ export namespace Program {
|
||||||
def.is_initializing = false;
|
def.is_initializing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "cell": {
|
||||||
|
const def = sigDef.def;
|
||||||
|
if (def.signalId !== undefined) {
|
||||||
|
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||||
|
if (signal === undefined) {
|
||||||
|
throw ThrownRuntimeError.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name, });
|
||||||
|
} else {
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to create the cell-signal for the first time.
|
||||||
|
const initialValue = eval_expr(program, Env.nil(), def.body);
|
||||||
|
const sig = signal(initialValue);
|
||||||
|
const id = attachNewSignal(program, sig);
|
||||||
|
def.signalId = id;
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
case "primitive": {
|
case "primitive": {
|
||||||
const def = sigDef.def;
|
const def = sigDef.def;
|
||||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||||
|
|
@ -233,6 +272,7 @@ export namespace Program {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Functions ===
|
||||||
export type CreateFunction = {
|
export type CreateFunction = {
|
||||||
name: FunctionName,
|
name: FunctionName,
|
||||||
parameters: ProductPattern[],
|
parameters: ProductPattern[],
|
||||||
|
|
@ -316,5 +356,107 @@ export namespace Program {
|
||||||
return Result.ok(undefined);
|
return Result.ok(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Signals ===
|
||||||
|
export type CreateSignal = {
|
||||||
|
name: SignalName,
|
||||||
|
body: SignalExpr,
|
||||||
|
raw_body: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerSignal(
|
||||||
|
program: Program,
|
||||||
|
{ name, body, raw_body }: CreateSignal
|
||||||
|
): Result<void> {
|
||||||
|
if (program.signal_definitions.has(name)) {
|
||||||
|
return Result.error({ tag: "DuplicateSignalName", name });
|
||||||
|
}
|
||||||
|
|
||||||
|
const now: Timestamp = Date.now();
|
||||||
|
|
||||||
|
const newSignal: UserSignalDefinition = {
|
||||||
|
name,
|
||||||
|
raw_body,
|
||||||
|
body,
|
||||||
|
is_initializing: false,
|
||||||
|
signalId: undefined, // Start uncompiled
|
||||||
|
|
||||||
|
createdAt: now,
|
||||||
|
lastModifiedAt: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
program.signal_definitions.set(name, { tag: "user", def: newSignal });
|
||||||
|
program.signal_definition_order.push(name);
|
||||||
|
|
||||||
|
// TODO: Note that this doesn't actually evaluate the signal and doesn't insert it into signal-runtime.
|
||||||
|
// For that we will use `get_or_create_signal`
|
||||||
|
|
||||||
|
return Result.ok(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateSignal = {
|
||||||
|
body: SignalExpr,
|
||||||
|
raw_body: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateSignal(
|
||||||
|
program: Program,
|
||||||
|
name: SignalName,
|
||||||
|
{ body, raw_body }: UpdateSignal
|
||||||
|
): Result<void> {
|
||||||
|
const existingEntry = program.signal_definitions.get(name);
|
||||||
|
|
||||||
|
if (existingEntry === undefined) {
|
||||||
|
return Result.error({ tag: "SignalNotFound", name } as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingEntry.tag === "primitive") {
|
||||||
|
return Result.error({ tag: "CannotEditPrimitiveSignal", name } as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
const def = existingEntry.def;
|
||||||
|
def.body = body;
|
||||||
|
def.raw_body = raw_body;
|
||||||
|
def.lastModifiedAt = Date.now();
|
||||||
|
|
||||||
|
// TODO: When to recompile?
|
||||||
|
// 2. CRITICAL: Invalidate the Runtime Cache
|
||||||
|
// We clear the ID so the next 'read' forces a re-compile.
|
||||||
|
// Note: This does NOT automatically update other signals that
|
||||||
|
// are currently holding a reference to the *old* signal ID.
|
||||||
|
// That requires a more complex hot-reload strategy, but this
|
||||||
|
// is the correct first step.
|
||||||
|
def.signalId = undefined;
|
||||||
|
def.is_initializing = false;
|
||||||
|
|
||||||
|
return Result.ok(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteSignal(program: Program, name: SignalName): Result<void> {
|
||||||
|
const existingEntry = program.signal_definitions.get(name);
|
||||||
|
|
||||||
|
if (!existingEntry) {
|
||||||
|
return Result.error({ tag: "SignalNotFound", name } as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingEntry.tag === "primitive") {
|
||||||
|
return Result.error({ tag: "CannotDeletePrimitiveSignal", name } as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
program.signal_definitions.delete(name);
|
||||||
|
|
||||||
|
const orderIndex = program.signal_definition_order.indexOf(name);
|
||||||
|
if (orderIndex !== -1) {
|
||||||
|
program.signal_definition_order.splice(orderIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Note: The old signal instance still exists in program.signal_runtime.store
|
||||||
|
// We technically leak memory here unless we also remove it from the runtime store.
|
||||||
|
// However, since other signals might still depend on that ID,
|
||||||
|
// leaving it is actually safer for now to prevent crashes.
|
||||||
|
|
||||||
|
return Result.ok(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue