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 { Result, RuntimeError, ThrownRuntimeError } from "./error";
|
||||
import { eval_expr } from "./evaluator";
|
||||
import { eval_expr, eval_start } from "./evaluator";
|
||||
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> {
|
||||
state: 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 { Value } from "./eval/value";
|
||||
import { Env, Value } from "./eval/value";
|
||||
import { Expr, FunctionName, SignalName, ProductPattern, SignalExpr } from "./expr";
|
||||
import { installPrimitives } from "./primitive";
|
||||
import { eval_expr } from "./eval/evaluator";
|
||||
|
||||
export type Timestamp = number;
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ export type Implementation = (args: Value[]) => Value
|
|||
// === Signals ===
|
||||
export type SignalDefinition =
|
||||
| { tag: "user", def: UserSignalDefinition }
|
||||
| { tag: "cell", def: CellDefinition }
|
||||
| { tag: "primitive", def: PrimitiveSignalDefinition }
|
||||
|
||||
type UserSignalDefinition = {
|
||||
|
|
@ -62,6 +64,19 @@ type UserSignalDefinition = {
|
|||
lastModifiedAt: Timestamp,
|
||||
}
|
||||
|
||||
type CellDefinition = {
|
||||
name: SignalName,
|
||||
raw_body: string,
|
||||
|
||||
body: Expr,
|
||||
|
||||
signalId?: SignalId,
|
||||
|
||||
// Metadata
|
||||
createdAt: Timestamp,
|
||||
lastModifiedAt: Timestamp,
|
||||
}
|
||||
|
||||
type PrimitiveSignalDefinition = {
|
||||
name: FunctionName,
|
||||
signalId: SignalId,
|
||||
|
|
@ -76,6 +91,12 @@ export namespace Program {
|
|||
| { tag: "CannotDeletePrimitiveFunction", 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> =
|
||||
| { tag: "ok", value: T }
|
||||
| { tag: "error", error: Error }
|
||||
|
|
@ -185,6 +206,24 @@ export namespace Program {
|
|||
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": {
|
||||
const def = sigDef.def;
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
|
|
@ -233,6 +272,7 @@ export namespace Program {
|
|||
}
|
||||
}
|
||||
|
||||
// === Functions ===
|
||||
export type CreateFunction = {
|
||||
name: FunctionName,
|
||||
parameters: ProductPattern[],
|
||||
|
|
@ -316,5 +356,107 @@ export namespace Program {
|
|||
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