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
912export 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+ }
0 commit comments