Add primitive-functions, ability to work with Program
This commit is contained in:
parent
24c09c8fbe
commit
8b02e3e7d1
4 changed files with 158 additions and 49 deletions
|
|
@ -19,7 +19,7 @@ const C = {
|
||||||
Bold: "\x1b[1m",
|
Bold: "\x1b[1m",
|
||||||
};
|
};
|
||||||
|
|
||||||
const program = Program.makeEmpty();
|
const program = Program.make();
|
||||||
|
|
||||||
function runSource(inputRaw: string, isRepl: boolean): boolean {
|
function runSource(inputRaw: string, isRepl: boolean): boolean {
|
||||||
const input = inputRaw.trim();
|
const input = inputRaw.trim();
|
||||||
|
|
|
||||||
57
src/lang/primitive.ts
Normal file
57
src/lang/primitive.ts
Normal 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([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { ThrownRuntimeError } from "./eval/error";
|
import { ThrownRuntimeError } from "./eval/error";
|
||||||
import { Value } from "./eval/value";
|
import { Value } from "./eval/value";
|
||||||
import { Expr, FunctionName, ProductPattern } from "./expr";
|
import { Expr, FunctionName, ProductPattern } from "./expr";
|
||||||
|
import { installPrimitives } from "./primitive";
|
||||||
|
|
||||||
export type Timestamp = number;
|
export type Timestamp = number;
|
||||||
|
|
||||||
|
|
@ -51,39 +52,56 @@ export type UserFunctionDefinition = {
|
||||||
|
|
||||||
export type PrimitiveFunctionDefinition = {
|
export type PrimitiveFunctionDefinition = {
|
||||||
name: FunctionName,
|
name: FunctionName,
|
||||||
implementation: (args: Value[]) => Value,
|
implementation: Implementation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Implementation = (args: Value[]) => Value
|
||||||
|
|
||||||
export namespace Program {
|
export namespace Program {
|
||||||
|
|
||||||
type Error =
|
type Error =
|
||||||
| { tag: "DuplicateFunctionName", name: FunctionName }
|
| { 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> =
|
type Result<T> =
|
||||||
| { tag: "ok", value: T }
|
| { tag: "ok", value: T }
|
||||||
| { tag: "error", error: Error };
|
| { tag: "error", error: Error }
|
||||||
// | { tag: "ParseError", message: string } // TODO
|
|
||||||
|
|
||||||
export namespace Result {
|
export namespace Result {
|
||||||
export function ok<T>(value: T): Result<T> { return { tag: "ok", value } }
|
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 error<T>(error: Error): Result<T> { return { tag: "error", error } }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeEmpty(): Program {
|
function primitive(name: FunctionName, implementation: Implementation): FunctionDefinition {
|
||||||
return {
|
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_definitions: new Map(),
|
||||||
function_definition_order: [],
|
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`. This is used by evaluator.
|
||||||
|
|
||||||
// may throw `ThrownRuntimeError`
|
|
||||||
export function lookup_function(program: Program, name: FunctionName): FunctionDefinition {
|
export function lookup_function(program: Program, name: FunctionName): FunctionDefinition {
|
||||||
const fn = program.function_definitions.get(name);
|
const fn = program.function_definitions.get(name);
|
||||||
if (!fn) {
|
if (fn === undefined) {
|
||||||
throw ThrownRuntimeError.error({
|
throw ThrownRuntimeError.error({
|
||||||
tag: "FunctionLookupFailure",
|
tag: "FunctionLookupFailure",
|
||||||
name,
|
name,
|
||||||
|
|
@ -93,44 +111,86 @@ export namespace Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateFunction = {
|
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_name: string,
|
||||||
raw_parameters: string,
|
raw_parameters: string,
|
||||||
raw_body: string,
|
raw_body: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateFunction = {
|
export function updateFunction(
|
||||||
raw_name?: string,
|
program: Program,
|
||||||
raw_parameters?: string,
|
name: FunctionName,
|
||||||
raw_body?: string,
|
{ 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> {
|
export function deleteFunction(program: Program, name: FunctionName): Result<void> {
|
||||||
// TODO:
|
const existingEntry = program.function_definitions.get(name);
|
||||||
// - parsing/validation
|
if (!existingEntry) {
|
||||||
// - raw_name (check if function already exists)
|
return Result.error({ tag: "FunctionNotFound", name });
|
||||||
// - raw_parameters
|
}
|
||||||
// - raw_body
|
if (existingEntry.tag === "primitive") {
|
||||||
// - compute timestamp for now
|
return Result.error({ tag: "CannotDeletePrimitiveFunction", name });
|
||||||
return (0 as any);
|
}
|
||||||
}
|
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?
|
const orderIndex = program.function_definition_order.indexOf(name);
|
||||||
export function delete_user_function(program: Program, name: FunctionName): Result<void> {
|
if (orderIndex !== -1) {
|
||||||
// TODO:
|
program.function_definition_order.splice(orderIndex, 1);
|
||||||
// - see if the user function exists
|
}
|
||||||
// - if it does, delete it
|
|
||||||
// - if it doesn't ???
|
|
||||||
return (0 as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update_user_function(program: Program, name: FunctionName): Result<void> {
|
return Result.ok(undefined);
|
||||||
// TODO:
|
|
||||||
return (0 as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function get_user_function(program: Program, name: FunctionName): Result<UserFunctionDefinition> {
|
|
||||||
// TODO:
|
|
||||||
return (0 as any);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,6 @@
|
||||||
|
|
||||||
npm run start
|
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
|
# Tests
|
||||||
|
|
||||||
npx ts-node src/parser/cursor.test.ts
|
npx ts-node src/parser/cursor.test.ts
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue