-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexprs.go
298 lines (264 loc) · 7.18 KB
/
exprs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
package golisp2
import (
"fmt"
"strings"
)
type (
// Expr is the fundamental unit of lisp - it represents anything that can be
// evaluated to a value.
Expr interface {
// Eval will evaluate the underlying expression, and return the value (if
// any) that was calculated and returned.
Eval(*EvalContext) (Value, error)
// CodeStr will return the code representation of the given expression.
CodeStr() string
// SourcePos returns the location that the expression started in the source.
SourcePos() ScannerPosition
}
// CallExpr is a function call. The first expression is treated as a function,
// with the remaining elements passed to it.
CallExpr struct {
Exprs []Expr
Pos ScannerPosition
}
// IfExpr is an if expression. Cond is evaluated: if true, Case1 is
// evaluated and returned; if false Case2 will be.
IfExpr struct {
Cond Expr
Case1, Case2 Expr
Pos ScannerPosition
}
// FnExpr is a function definition expression. It has a set of arguments and a
// body, and will evaluate the body with the given arguments when called.
FnExpr struct {
Args []Arg
Body []Expr
Pos ScannerPosition
}
// Arg is a single element in a function list.
Arg struct {
Ident string
}
// LetExpr represents an assignment of a value to an identifier. When
// evaluated, adds the value to the evaluation context.
LetExpr struct {
Ident *IdentLiteral
Value Expr
Pos ScannerPosition
}
)
// NewCallExpr creates a new CallExpr out of the given sub-expressions. Will
// treat the first argument as the function, and the remaining arguments as the
// arguments.
func NewCallExpr(exprs ...Expr) *CallExpr {
return &CallExpr{
Exprs: exprs,
}
}
// Eval will evaluate the expression and return its results.
func (ce *CallExpr) Eval(ec *EvalContext) (Value, error) {
if len(ce.Exprs) == 0 {
return &NilValue{}, nil
}
fn, fnErr := evalToFunc(ec, ce.Exprs[0])
if fnErr != nil {
return nil, fnErr
}
vals := []Value{}
for _, expr := range ce.Exprs[1:] {
v, err := expr.Eval(ec)
if err != nil {
// todo (bs): augment with trace
return nil, err
}
vals = append(vals, v)
}
callVal, callValErr := fn.Fn(ec, vals...)
return callVal, callValErr
}
// CodeStr will return the code representation of the call expression.
func (ce *CallExpr) CodeStr() string {
var sb strings.Builder
sb.WriteString("(")
for i, e := range ce.Exprs {
if i > 0 {
sb.WriteString(" ")
}
sb.WriteString(e.CodeStr())
}
sb.WriteString(")\n")
return sb.String()
}
// SourcePos is the location in source this expression came from.
func (ce *CallExpr) SourcePos() ScannerPosition {
return ce.Pos
}
// NewIfExpr builds a new if statement with the given condition and cases. The
// cases may be left nil.
func NewIfExpr(cond Expr, case1, case2 Expr) *IfExpr {
if case1 == nil {
case1 = NewNilLiteral()
}
if case2 == nil {
case2 = NewNilLiteral()
}
return &IfExpr{
Cond: cond,
Case1: case1,
Case2: case2,
}
}
// Eval evaluates the if and returns the evaluated contents of the according
// case.
func (ie *IfExpr) Eval(ec *EvalContext) (Value, error) {
condV, condVErr := ie.Cond.Eval(ec)
if condVErr != nil {
return nil, condVErr
}
asBool, isBool := condV.(*BoolValue)
if !isBool {
return nil, &TypeError{
Actual: fmt.Sprintf("%T", condV),
Expected: fmt.Sprintf("%T", (*BoolValue)(nil)),
Pos: ie.Cond.SourcePos(),
}
}
if asBool.Val {
return ie.Case1.Eval(ec)
}
return ie.Case2.Eval(ec)
}
// CodeStr will return the code representation of the if expression.
func (ie *IfExpr) CodeStr() string {
var sb strings.Builder
sb.WriteString("(if ")
sb.WriteString(ie.Cond.CodeStr())
sb.WriteString("\n")
sb.WriteString(ie.Case1.CodeStr())
sb.WriteString("\n")
sb.WriteString(ie.Case2.CodeStr())
sb.WriteString(")\n")
return sb.String()
}
// SourcePos is the location in source this expression came from.
func (ie *IfExpr) SourcePos() ScannerPosition {
return ie.Pos
}
// NewFnExpr builds a new function expression with the given arguments and body.
func NewFnExpr(args []Arg, body []Expr) *FnExpr {
return &FnExpr{
Args: args,
Body: body,
}
}
// Eval returns an evaluate-able function value. Note that this does *not*
// execute the function; it must be evaluated within a call to be actually
// executed.
func (fe *FnExpr) Eval(parentEc *EvalContext) (Value, error) {
// ques (bs): how should stack traces work here? At this point, for full
// traces (rather than just "origination errors")
fn := func(_ *EvalContext, vals ...Value) (Value, error) {
if len(fe.Args) != len(vals) {
// todo (bs): add pos information.
return nil, fmt.Errorf("expected %d arguments in call; got %d",
len(fe.Args), len(vals))
}
evalEc := parentEc.SubContext(nil)
for i, arg := range fe.Args {
evalEc.Add(arg.Ident, vals[i])
}
var evalV Value
for _, e := range fe.Body {
v, err := e.Eval(evalEc)
if err != nil {
// todo (bs): add pos information
return nil, err
}
evalV = v
}
if evalV == nil {
evalV = &NilValue{}
}
return evalV, nil
}
return &FuncValue{
Fn: fn,
}, nil
}
// CodeStr will return the code representation of the fn expression.
func (fe *FnExpr) CodeStr() string {
var sb strings.Builder
sb.WriteString("(fn (")
for i, a := range fe.Args {
if i > 0 {
sb.WriteString(" ")
}
sb.WriteString(a.Ident)
}
sb.WriteString(")\n")
for _, e := range fe.Body {
sb.WriteString(e.CodeStr())
}
sb.WriteString(")\n")
return sb.String()
}
// SourcePos is the location in source this expression came from.
func (fe *FnExpr) SourcePos() ScannerPosition {
return fe.Pos
}
// Eval will assign the underlying value to the ident on the context, and return
// the value.
func (le *LetExpr) Eval(ec *EvalContext) (Value, error) {
identStr := le.Ident.Val
v, err := le.Value.Eval(ec)
if err != nil {
// todo (bs): maybe add pos information
return nil, err
}
ec.Add(identStr, v)
return v, nil
}
// CodeStr will return the code representation of the let expression.
func (le *LetExpr) CodeStr() string {
return fmt.Sprintf("(let %s %s)", le.Ident.Val, le.Value.CodeStr())
}
// SourcePos is the location in source this expression came from.
func (le *LetExpr) SourcePos() ScannerPosition {
return le.Pos
}
// evalToFunc will evaluate the given expression, expecting a function. Will
// return a well-formed error i
func evalToFunc(evalCtx *EvalContext, expr Expr) (*FuncValue, error) {
var val Value
switch v := expr.(type) {
case *IdentLiteral:
// In the case of idents, manually inspect to see if it's nil. This is to
// make errors more obvious in the case of a function simply being an
// undefined name.
identVal, hasIdent := evalCtx.Resolve(v.Val)
if !hasIdent {
return nil, &EvalError{
Msg: fmt.Sprintf(
"undefined identifier '%s' cannot be used as function", v.Val),
Pos: v.SourcePos(),
}
}
val = identVal
default:
var v1Err error
val, v1Err = expr.Eval(evalCtx)
if v1Err != nil {
// note (bs): for stack errors; this would still need to be wrapped
return nil, v1Err
}
}
asFn, isFn := val.(*FuncValue)
if !isFn {
return nil, &TypeError{
Actual: fmt.Sprintf("%T", val),
Expected: fmt.Sprintf("%T", (*FuncValue)(nil)),
Pos: expr.SourcePos(),
}
}
return asFn, nil
}