Skip to content

Commit 8bbb62e

Browse files
committed
treat closures and builtins as values, simplify builtins
1 parent 2c0f786 commit 8bbb62e

File tree

6 files changed

+112
-201
lines changed

6 files changed

+112
-201
lines changed

src/builtin.ts

Lines changed: 0 additions & 159 deletions
This file was deleted.

src/evaluator.ts

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
// Copyright (c) 2025 Marco Nikander
22

3-
import { Flat_Expression, Flat_AST, is_literal, is_identifier, is_reference, is_lambda, is_let, is_call, is_binding, is_builtin, is_if } from "./flat_ast";
4-
import { evaluate_builtin } from "./builtin";
3+
import { Id, Flat_Expression, Flat_AST, is_literal, is_identifier, is_reference, is_lambda, is_let, is_call, is_binding, is_builtin, is_if, Flat_Builtin } from "./flat_ast";
4+
import { Item } from "./item";
55

6-
export type Value = { tag: 'Primitive', value: boolean | number | string };
6+
export type Value = PrimitiveValue | ClosureValue | BuiltinValue;
7+
export type PrimitiveValue = { tag: 'Primitive', value: boolean | number | string}
8+
export type ClosureValue = { tag: 'Closure', binding: Id, body: Id, env: Environment };
9+
export type BuiltinValue = { tag: 'Builtin', name: string, arity: number, impl: ((args: PrimitiveValue[]) => PrimitiveValue), args: PrimitiveValue[] };
710

811
// note that the environment stores everything as dynamic (i.e. runtime) values, even the constants from the Flat_AST, so that everything can be evaluated directly
912
export type Environment = {
@@ -30,7 +33,7 @@ export function lookup(id: number, env: Environment): Value {
3033
}
3134
}
3235

33-
export function evaluate(expr: Flat_Expression, ast: Flat_AST, env: Environment, stacked_args: Value[]): Value {
36+
export function evaluate(expr: Flat_Expression, ast: Flat_AST, env: Environment): Value {
3437
if (is_literal(expr, ast)) {
3538
return { tag: 'Primitive', value: expr.value };
3639
}
@@ -41,42 +44,106 @@ export function evaluate(expr: Flat_Expression, ast: Flat_AST, env: Environment,
4144
return lookup(expr.id, env);
4245
}
4346
else if (is_reference(expr, ast)) {
44-
return evaluate(ast[expr.target.id], ast, env, stacked_args);
47+
return evaluate(ast[expr.target.id], ast, env); // could/should this be replaced with a direct lookup?
4548
}
4649
else if (is_builtin(expr, ast)) {
47-
return evaluate_builtin(expr, ast, env, stacked_args);
50+
return make_builtin(expr, ast);
4851
}
4952
else if (is_lambda(expr, ast)) {
50-
// dequeue an argument and store it in the environment instead
51-
let first = stacked_args.pop();
52-
if (first !== undefined) {
53-
let extended_env = extend_env(env);
54-
extended_env.bindings.set(expr.binding.id, first);
55-
return evaluate(ast[expr.body.id], ast, extended_env, stacked_args)
56-
}
57-
else {
58-
throw new Error("No arguments to bind to variable");
59-
}
53+
return { tag: 'Closure', binding: expr.binding, body: expr.body, env: env };
6054
}
6155
else if (is_let(expr, ast)) {
6256
let extended_env = extend_env(env);
63-
extended_env.bindings.set(expr.binding.id, evaluate(ast[expr.value.id], ast, env, stacked_args));
64-
return evaluate(ast[expr.body.id], ast, extended_env, stacked_args);
57+
extended_env.bindings.set(expr.binding.id, evaluate(ast[expr.value.id], ast, env));
58+
return evaluate(ast[expr.body.id], ast, extended_env);
6559
}
6660
else if (is_if(expr, ast)) {
67-
const condition = evaluate(ast[expr.condition.id], ast, env, stacked_args);
68-
if (typeof (condition.value) === "boolean") {
69-
return evaluate(ast[condition.value ? expr.if_true.id : expr.if_false.id], ast, env, stacked_args);
61+
const condition = evaluate(ast[expr.condition.id], ast, env);
62+
if (is_primitive_value(condition) && typeof (condition.value) === "boolean") {
63+
return evaluate(ast[condition.value ? expr.if_true.id : expr.if_false.id], ast, env);
7064
} else {
71-
throw new Error(`Condition in 'if' expression did not evaluate to a boolean value`);
65+
throw new Error(`Expect condition in 'if' expression to evaluate to a boolean value, evaluated to ${condition.tag} instead`);
7266
}
7367
}
7468
else if (is_call(expr, ast)) {
7569
// enqueue the provided argument
76-
const evaluated_arg = evaluate(ast[expr.arg.id], ast, env, stacked_args);
77-
return evaluate(ast[expr.body.id], ast, env, [...stacked_args, evaluated_arg]);
70+
const evaluated_fn = evaluate(ast[expr.body.id], ast, env);
71+
const evaluated_arg = evaluate(ast[expr.arg.id], ast, env);
72+
return apply(evaluated_fn, evaluated_arg, ast);
7873
}
7974
else {
8075
throw new Error("unhandled case in evaluation control flow");
8176
}
8277
}
78+
79+
function apply(fn: Value, arg: Value, ast: Flat_AST): Value {
80+
if (is_closure_value(fn)) {
81+
let extended_env = extend_env(fn.env);
82+
extended_env.bindings.set(fn.binding.id, arg);
83+
return evaluate(ast[fn.body.id], ast, extended_env);
84+
}
85+
else if (is_builtin_value(fn)) {
86+
if (!is_primitive_value(arg)) {
87+
throw Error(`Tried calling builtin function '${fn.name}' with non-primitive argument of type '${arg.tag}'`);
88+
}
89+
else {
90+
fn.args.push(arg);
91+
if (fn.args.length == fn.arity) {
92+
return fn.impl(fn.args);
93+
}
94+
else {
95+
return fn;
96+
}
97+
}
98+
}
99+
else {
100+
throw(`Attempted to call a non-function value ${fn.value} of type ${fn.tag}`);
101+
}
102+
}
103+
104+
export function is_primitive_value(item: Item): item is PrimitiveValue {
105+
return item.tag === 'Primitive';
106+
}
107+
108+
function is_closure_value(item: Item): item is ClosureValue {
109+
return item.tag === 'Closure';
110+
}
111+
112+
function is_builtin_value(item: Item): item is BuiltinValue {
113+
return item.tag === 'Builtin';
114+
}
115+
116+
function make_builtin(expr: Flat_Builtin, ast: Flat_AST): BuiltinValue {
117+
switch (expr.name) {
118+
case '~':
119+
return { tag: 'Builtin', name: expr.name, arity: 1, impl: args => { return { tag: 'Primitive', value: -args[0].value }}, args: [] }
120+
case '!':
121+
return { tag: 'Builtin', name: expr.name, arity: 1, impl: args => { return { tag: 'Primitive', value: !args[0].value }}, args: [] }
122+
case '==':
123+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value == args[1].value }}, args: [] }
124+
case '!=':
125+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value != args[1].value }}, args: [] }
126+
case '<':
127+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value < args[1].value }}, args: [] }
128+
case '>':
129+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value > args[1].value }}, args: [] }
130+
case '<=':
131+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value <= args[1].value }}, args: [] }
132+
case '>=':
133+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value >= args[1].value }}, args: [] }
134+
case '+':
135+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value + args[1].value }}, args: [] }
136+
case '-':
137+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value - args[1].value }}, args: [] }
138+
case '*':
139+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value * args[1].value }}, args: [] }
140+
case '/':
141+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value / args[1].value }}, args: [] }
142+
case '%':
143+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value % args[1].value }}, args: [] }
144+
case '&&':
145+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value && args[1].value }}, args: [] }
146+
case '||':
147+
return { tag: 'Builtin', name: expr.name, arity: 2, impl: args => { return { tag: 'Primitive', value: args[0].value || args[1].value }}, args: [] }
148+
}
149+
}

src/flat_ast.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type Flat_If = {id: number, token?: number, tag: 'Flat_If', condi
1313
export type Flat_Call = {id: number, token?: number, tag: 'Flat_Call', body: Id, arg: Id};
1414
export type Flat_Builtin = {id: number, token?: number, tag: 'Flat_Builtin', name: "==" | "!=" | "<" | ">" | "<=" | ">=" | "+" | "-" | "*" | "/" | "%" | "~" | "&&" | "||" | "!"};
1515

16+
export const builtins: readonly string[] = ["==" , "!=" , "<" , ">" , "<=" , ">=" , "+" , "-" , "*" , "/" , "%" , "~" , "&&" , "||" , "!"];
17+
1618
export function is_literal(expr: Flat_Expression, ast: Flat_AST): expr is Flat_Literal { return expr.tag === 'Flat_Literal'; }
1719
export function is_identifier(expr: Flat_Expression, ast: Flat_AST): expr is Flat_Identifier { return expr.tag === 'Flat_Identifier'; }
1820
export function is_binding(expr: Flat_Expression, ast: Flat_AST): expr is Flat_Binding { return expr.tag === 'Flat_Binding'; }

src/interpreter.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { lex, Token } from "./lexer";
44
import { parse } from "./parser";
5-
import { Environment, Value, evaluate, make_env } from "./evaluator";
5+
import { Environment, Value, evaluate, is_primitive_value, make_env } from "./evaluator";
66
import { Flat_AST } from "./flat_ast";
77
import { flatten } from "./flatten";
88
import { resolve_names } from "./name_resolution";
@@ -14,7 +14,12 @@ export function interpret(prompt: string): boolean | number | string {
1414
const linked_ast: Flat_AST = resolve_names(ast);
1515

1616
let env: Environment = make_env();
17-
const evaluated: Value = evaluate(linked_ast[0], linked_ast, env, []);
17+
const result: Value = evaluate(linked_ast[0], linked_ast, env);
1818

19-
return evaluated.value;
19+
if (is_primitive_value(result)) {
20+
return result.value;
21+
}
22+
else {
23+
throw Error(`Interpreter expected a boolean, number, or string result, but got a '${result.tag}' instead.`);
24+
}
2025
}

src/name_resolution.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// Copyright (c) 2025 Marco Nikander
22

33
import { Item } from "./item";
4-
import { Flat_Binding, Flat_Reference, Flat_Expression, Flat_AST, Flat_Builtin, is_literal, is_identifier, is_reference, is_lambda, is_let, is_call, is_binding, is_if } from "./flat_ast";
5-
import { builtins } from "./builtin";
4+
import { Flat_Binding, Flat_Reference, Flat_Expression, Flat_AST, Flat_Builtin, builtins, is_literal, is_identifier, is_reference, is_lambda, is_let, is_call, is_binding, is_if } from "./flat_ast";
65

76
export type GlobalScope = {
87
tag: "GlobalScope"

0 commit comments

Comments
 (0)