-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initialise parser and evaluator packages and wire them through all the way to the frontend. Variable declaration via inference or type declaration, as well as calls builtin function `print` with a variadic number of arguments as literals or variables is possible now. Tweak the frontend to actually print something with the initial demo code. Just like the lexer, many cues are taken from Thorston Ball's Interpreter book source code. Link: https://github.com/juliaogris/monkey Signed-off-by: Julia Ogris <[email protected]>
- Loading branch information
1 parent
afece8a
commit e8861b9
Showing
13 changed files
with
1,433 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package evaluator | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type Builtin func(args []Value) Value | ||
|
||
func (b Builtin) Type() ValueType { return BUILTIN } | ||
func (b Builtin) String() string { return "builtin function" } | ||
|
||
func newBuiltins(e *Evaluator) map[string]Builtin { | ||
return map[string]Builtin{ | ||
"print": Builtin(e.Print), | ||
"len": Builtin(Len), | ||
} | ||
} | ||
|
||
func (e *Evaluator) Print(args []Value) Value { | ||
argList := make([]string, len(args)) | ||
for i, arg := range args { | ||
argList[i] = arg.String() | ||
} | ||
e.print(strings.Join(argList, " ")) | ||
return nil | ||
} | ||
|
||
func Len(args []Value) Value { | ||
if len(args) != 1 { | ||
return newError("'len' takes 1 argument not " + strconv.Itoa(len(args))) | ||
} | ||
switch arg := args[0].(type) { | ||
case *Map: | ||
return &Num{Val: float64(len(arg.Pairs))} | ||
case *Array: | ||
return &Num{Val: float64(len(arg.Elements))} | ||
case *String: | ||
return &Num{Val: float64(len(arg.Val))} | ||
default: | ||
return newError("'len' takes 1 argument of type 'string', array '[]' or map '{}' not " + args[0].Type().String()) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,101 @@ | ||
package evaluator | ||
|
||
import "strings" | ||
import ( | ||
"foxygo.at/evy/pkg/parser" | ||
) | ||
|
||
func Run(input string, print func(string)) { | ||
print(strings.ToUpper(input)) | ||
p := parser.New(input) | ||
prog := p.Parse() | ||
e := &Evaluator{print: print} | ||
e.builtins = newBuiltins(e) | ||
val := e.Eval(prog, NewScope()) | ||
if isError(val) { | ||
print(val.String()) | ||
} | ||
} | ||
|
||
type Evaluator struct { | ||
print func(string) | ||
builtins map[string]Builtin | ||
} | ||
|
||
func (e *Evaluator) Eval(node parser.Node, scope *Scope) Value { | ||
switch node := node.(type) { | ||
case *parser.Program: | ||
return e.evalProgram(node, scope) | ||
case *parser.Declaration: | ||
return e.evalDeclaration(node, scope) | ||
case *parser.Var: | ||
v := e.evalVar(node, scope) | ||
return v | ||
case *parser.Term: | ||
return e.evalTerm(node, scope) | ||
case *parser.NumLiteral: | ||
return &Num{Val: node.Value} | ||
case *parser.StringLiteral: | ||
return &String{Val: node.Value} | ||
case *parser.Bool: | ||
return &Bool{Val: node.Value} | ||
case *parser.FunctionCall: | ||
return e.evalFunctionCall(node, scope) | ||
} | ||
return nil | ||
} | ||
|
||
func (e *Evaluator) evalProgram(program *parser.Program, scope *Scope) Value { | ||
var result Value | ||
for _, statement := range program.Statements { | ||
result = e.Eval(statement, scope) | ||
if isError(result) { | ||
return result | ||
} | ||
} | ||
return result | ||
} | ||
|
||
func (e *Evaluator) evalDeclaration(decl *parser.Declaration, scope *Scope) Value { | ||
val := e.Eval(decl.Value, scope) | ||
if isError(val) { | ||
return val | ||
} | ||
scope.Set(decl.Var.Name, val) | ||
return nil | ||
} | ||
|
||
func (e *Evaluator) evalFunctionCall(funcCall *parser.FunctionCall, scope *Scope) Value { | ||
args := e.evalTerms(funcCall.Arguments, scope) | ||
if len(args) == 1 && isError(args[0]) { | ||
return args[0] | ||
} | ||
builtin, ok := e.builtins[funcCall.Name] | ||
if !ok { | ||
return newError("cannot find builtin function " + funcCall.Name) | ||
} | ||
return builtin(args) | ||
} | ||
|
||
func (e *Evaluator) evalVar(v *parser.Var, scope *Scope) Value { | ||
if val, ok := scope.Get(v.Name); ok { | ||
return val | ||
} | ||
return newError("cannot find variable " + v.Name) | ||
} | ||
|
||
func (e *Evaluator) evalTerm(term parser.Node, scope *Scope) Value { | ||
return e.Eval(term, scope) | ||
} | ||
|
||
func (e *Evaluator) evalTerms(terms []parser.Node, scope *Scope) []Value { | ||
result := make([]Value, len(terms)) | ||
|
||
for i, t := range terms { | ||
evaluated := e.Eval(t, scope) | ||
if isError(evaluated) { | ||
return []Value{evaluated} | ||
} | ||
result[i] = evaluated | ||
} | ||
|
||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package evaluator | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"foxygo.at/evy/pkg/assert" | ||
) | ||
|
||
func TestBasicEval(t *testing.T) { | ||
in := "a:=1\n print a 2" | ||
want := "1 2" | ||
b := bytes.Buffer{} | ||
fn := func(s string) { b.WriteString(s) } | ||
Run(in, fn) | ||
assert.Equal(t, want, b.String()) | ||
} | ||
|
||
func TestParseDeclaration(t *testing.T) { | ||
tests := map[string]string{ | ||
"a:=1": "1", | ||
`a:="abc"`: "abc", | ||
`a:=true`: "true", | ||
`a:= len "abc"`: "3", | ||
} | ||
for in, want := range tests { | ||
in, want := in, want | ||
t.Run(in, func(t *testing.T) { | ||
in += "\n print a" | ||
b := bytes.Buffer{} | ||
fn := func(s string) { b.WriteString(s) } | ||
Run(in, fn) | ||
assert.Equal(t, want, b.String()) | ||
}) | ||
} | ||
} | ||
|
||
func TestDemo(t *testing.T) { | ||
prog := ` | ||
move 10 10 | ||
line 20 20 | ||
x := 12 | ||
print "x:" x | ||
if x > 10 | ||
print "🍦 big x" | ||
end` | ||
b := bytes.Buffer{} | ||
fn := func(s string) { b.WriteString(s) } | ||
Run(prog, fn) | ||
want := "x: 12" | ||
assert.Equal(t, want, b.String()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package evaluator | ||
|
||
type Scope struct { | ||
store map[string]Value | ||
outer *Scope | ||
} | ||
|
||
func NewScope() *Scope { | ||
return &Scope{store: map[string]Value{}} | ||
} | ||
|
||
func NewEnclosedScope(outer *Scope) *Scope { | ||
return &Scope{store: map[string]Value{}, outer: outer} | ||
} | ||
|
||
func (s *Scope) Get(name string) (Value, bool) { | ||
if s == nil { | ||
return nil, false | ||
} | ||
if val, ok := s.store[name]; ok { | ||
return val, ok | ||
} | ||
return s.outer.Get(name) | ||
} | ||
|
||
func (s *Scope) Set(name string, val Value) Value { | ||
s.store[name] = val | ||
return val | ||
} |
Oops, something went wrong.