diff --git a/src/lang/debug/repl.ts b/src/lang/debug/repl.ts index 513b754..82fbdf5 100644 --- a/src/lang/debug/repl.ts +++ b/src/lang/debug/repl.ts @@ -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(); diff --git a/src/lang/primitive.ts b/src/lang/primitive.ts new file mode 100644 index 0000000..aa90725 --- /dev/null +++ b/src/lang/primitive.ts @@ -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([]); + }); + +} diff --git a/src/lang/program.ts b/src/lang/program.ts index ac5a10d..24fe0ee 100644 --- a/src/lang/program.ts +++ b/src/lang/program.ts @@ -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 = | { tag: "ok", value: T } - | { tag: "error", error: Error }; - // | { tag: "ParseError", message: string } // TODO + | { tag: "error", error: Error } export namespace Result { export function ok(value: T): Result { return { tag: "ok", value } } export function error(error: Error): Result { 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 { + 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 { + 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 { - // 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 { + 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 { - // 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 { - // TODO: - return (0 as any); - } - - export function get_user_function(program: Program, name: FunctionName): Result { - // TODO: - return (0 as any); + return Result.ok(undefined); } } diff --git a/tmp_repl/tmp_repl.md b/tmp_repl/tmp_repl.md index 5bc61d2..6358c6e 100644 --- a/tmp_repl/tmp_repl.md +++ b/tmp_repl/tmp_repl.md @@ -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