Add primitive-functions, ability to work with Program

This commit is contained in:
Yura Dupyn 2026-02-07 17:57:20 +01:00
parent 24c09c8fbe
commit 8b02e3e7d1
4 changed files with 158 additions and 49 deletions

View file

@ -19,7 +19,7 @@ const C = {
Bold: "\x1b[1m",
};
const program = Program.makeEmpty();
const program = Program.make();
function runSource(inputRaw: string, isRepl: boolean): boolean {
const input = inputRaw.trim();

57
src/lang/primitive.ts Normal file
View file

@ -0,0 +1,57 @@
import { Value } from "./eval/value";
import { Implementation, Program } from "./program";
import { ThrownRuntimeError } from "./eval/error";
import { FunctionName } from "./expr";
import { valueToString } from "./debug/value_show";
// TODO: Primitive functions like +, -, *, div, <, <=, ==, mod
export function installPrimitives(program: Program) {
function R(name: FunctionName, implementation: Implementation) {
Program.registerPrimitive(program, name, implementation);
}
// addition
R("+", (args: Value[]): Value => {
let sum = 0;
for (const arg of args) {
if (arg.tag !== "number") {
throw ThrownRuntimeError.error({ tag: "TypeMismatch", expected: "number", received: arg });
}
sum += arg.value;
}
return Value.number(sum);
});
// multiplication
R("*", (args: Value[]): Value => {
let product = 1;
for (const arg of args) {
if (arg.tag !== "number") {
throw ThrownRuntimeError.error({ tag: "TypeMismatch", expected: "number", received: arg });
}
product = product*arg.value;
}
return Value.number(product);
});
// string concat
R("++", (args: Value[]): Value => {
let sum = "";
for (const arg of args) {
if (arg.tag !== "string") {
throw ThrownRuntimeError.error({ tag: "TypeMismatch", expected: "number", received: arg });
}
sum += arg.value;
}
return Value.string(sum);
});
R("log", (args: Value[]): Value => {
for (const arg of args) {
console.log(valueToString(arg));
}
return Value.tuple([]);
});
}

View file

@ -1,6 +1,7 @@
import { ThrownRuntimeError } from "./eval/error";
import { Value } from "./eval/value";
import { Expr, FunctionName, ProductPattern } from "./expr";
import { installPrimitives } from "./primitive";
export type Timestamp = number;
@ -51,39 +52,56 @@ export type UserFunctionDefinition = {
export type PrimitiveFunctionDefinition = {
name: FunctionName,
implementation: (args: Value[]) => Value,
implementation: Implementation,
}
export type Implementation = (args: Value[]) => Value
export namespace Program {
type Error =
| { tag: "DuplicateFunctionName", name: FunctionName }
| { tag: "FunctionNotFound", name: FunctionName };
| { tag: "FunctionNotFound", name: FunctionName }
| { tag: "CannotEditPrimitiveFunction", name: FunctionName }
| { tag: "CannotDeletePrimitiveFunction", name: FunctionName }
| { tag: "PrimitiveFunctionAlreadyExists", name: FunctionName }
type Result<T> =
| { tag: "ok", value: T }
| { tag: "error", error: Error };
// | { tag: "ParseError", message: string } // TODO
| { tag: "error", error: Error }
export namespace Result {
export function ok<T>(value: T): Result<T> { return { tag: "ok", value } }
export function error<T>(error: Error): Result<T> { return { tag: "error", error } }
}
export function makeEmpty(): Program {
return {
function primitive(name: FunctionName, implementation: Implementation): FunctionDefinition {
return { tag: "primitive", def: { name, implementation } };
}
export function registerPrimitive(program: Program, name: FunctionName, implementation: Implementation): void {
const fn = program.function_definitions.get(name);
if (fn !== undefined) {
throw { tag: "PrimitiveFunctionAlreadyExists", name };
}
program.function_definitions.set(name, primitive(name, implementation));
return undefined;
}
export function make(): Program {
const program: Program = {
function_definitions: new Map(),
function_definition_order: [],
};
installPrimitives(program);
return program;
}
// TODO: Primitive functions like +, -, *, div, <, <=, ==, mod
// TODO: function to create initial program (with the above primitive functions otherwise empty)
// may throw `ThrownRuntimeError`
// may throw `ThrownRuntimeError`. This is used by evaluator.
export function lookup_function(program: Program, name: FunctionName): FunctionDefinition {
const fn = program.function_definitions.get(name);
if (!fn) {
if (fn === undefined) {
throw ThrownRuntimeError.error({
tag: "FunctionLookupFailure",
name,
@ -93,44 +111,86 @@ export namespace Program {
}
export type CreateFunction = {
name: FunctionName,
parameters: ProductPattern[],
body: Expr,
raw_parameters: string,
raw_body: string,
}
export function registerFunction(
program: Program,
{ name, body, parameters, raw_parameters, raw_body }: CreateFunction
): Result<void> {
if (program.function_definitions.has(name)) {
return Result.error({ tag: "DuplicateFunctionName", name });
}
const now: Timestamp = Date.now();
const newFunction: UserFunctionDefinition = {
name,
raw_parameters,
raw_body,
parameters,
body,
created_at: now,
last_modified_at: now,
};
program.function_definitions.set(name, { tag: "user", def: newFunction });
program.function_definition_order.push(name);
return Result.ok(undefined);
}
export type UpdateFunction = {
parameters: ProductPattern[],
body: Expr,
raw_name: string,
raw_parameters: string,
raw_body: string,
}
export type UpdateFunction = {
raw_name?: string,
raw_parameters?: string,
raw_body?: string,
export function updateFunction(
program: Program,
name: FunctionName,
{ parameters, body, raw_parameters, raw_body }: UpdateFunction
): Result<void> {
const existingEntry = program.function_definitions.get(name);
if (existingEntry === undefined) {
return Result.error({ tag: "FunctionNotFound", name });
}
if (existingEntry.tag === "primitive") {
return Result.error({ tag: "CannotEditPrimitiveFunction", name });
}
const def = existingEntry.def;
def.parameters = parameters;
def.body = body;
def.raw_parameters = raw_parameters;
def.raw_body = raw_body;
def.last_modified_at = Date.now();
return Result.ok(undefined);
}
export function add_user_function(program: Program, description: CreateFunction): Result<void> {
// TODO:
// - parsing/validation
// - raw_name (check if function already exists)
// - raw_parameters
// - raw_body
// - compute timestamp for now
return (0 as any);
}
export function deleteFunction(program: Program, name: FunctionName): Result<void> {
const existingEntry = program.function_definitions.get(name);
if (!existingEntry) {
return Result.error({ tag: "FunctionNotFound", name });
}
if (existingEntry.tag === "primitive") {
return Result.error({ tag: "CannotDeletePrimitiveFunction", name });
}
program.function_definitions.delete(name);
// TODO: What about result type? Should it on deletion return the original data of the function, and if there's a failure, how detailed should it be?
export function delete_user_function(program: Program, name: FunctionName): Result<void> {
// TODO:
// - see if the user function exists
// - if it does, delete it
// - if it doesn't ???
return (0 as any);
}
const orderIndex = program.function_definition_order.indexOf(name);
if (orderIndex !== -1) {
program.function_definition_order.splice(orderIndex, 1);
}
export function update_user_function(program: Program, name: FunctionName): Result<void> {
// TODO:
return (0 as any);
}
export function get_user_function(program: Program, name: FunctionName): Result<UserFunctionDefinition> {
// TODO:
return (0 as any);
return Result.ok(undefined);
}
}

View file

@ -2,14 +2,6 @@
npm run start
npm install -D @electron-forge/cli @electron-forge/maker-deb @electron-forge/maker-rpm @electron-forge/maker-squirrel @electron-forge/maker-zip @electron-forge/plugin-auto-unpack-natives @electron-forge/plugin-fuses @electron-forge/plugin-vite @electron/fuses @types/electron-squirrel-startup @typescript-eslint/eslint-plugin @typescript-eslint/parser electron eslint eslint-plugin-import typescript vite
npm install electron-squirrel-startup
npm install -D sass-embedded
# Tests
npx ts-node src/parser/cursor.test.ts