Extra Features
This commit is contained in:
23
ast/ast.go
23
ast/ast.go
@@ -356,3 +356,26 @@ func (hl HashLiteral) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hl HashLiteral) expressionNode() {}
|
func (hl HashLiteral) expressionNode() {}
|
||||||
|
|
||||||
|
type WhileExpression struct {
|
||||||
|
Token token.Token // The 'while' token
|
||||||
|
Condition Expression
|
||||||
|
Consequence *BlockStatement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (we *WhileExpression) expressionNode() {}
|
||||||
|
|
||||||
|
// TokenLiteral prints the literal value of the token associated with this node
|
||||||
|
func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal }
|
||||||
|
|
||||||
|
// String returns a stringified version of the AST for debugging
|
||||||
|
func (we *WhileExpression) String() string {
|
||||||
|
var out bytes.Buffer
|
||||||
|
|
||||||
|
out.WriteString("while")
|
||||||
|
out.WriteString(we.Condition.String())
|
||||||
|
out.WriteString(" ")
|
||||||
|
out.WriteString(we.Consequence.String())
|
||||||
|
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const (
|
|||||||
OpClosure
|
OpClosure
|
||||||
OpGetFree
|
OpGetFree
|
||||||
OpCurrentClosure
|
OpCurrentClosure
|
||||||
|
OpNoop
|
||||||
)
|
)
|
||||||
|
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
@@ -79,6 +80,7 @@ var definitions = map[Opcode]*Definition{
|
|||||||
OpClosure: {"OpClosure", []int{2, 1}},
|
OpClosure: {"OpClosure", []int{2, 1}},
|
||||||
OpGetFree: {"OpGetFree", []int{1}},
|
OpGetFree: {"OpGetFree", []int{1}},
|
||||||
OpCurrentClosure: {"OpCurrentClosure", []int{}},
|
OpCurrentClosure: {"OpCurrentClosure", []int{}},
|
||||||
|
OpNoop: {"OpNoop", []int{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lookup(op byte) (*Definition, error) {
|
func Lookup(op byte) (*Definition, error) {
|
||||||
|
|||||||
@@ -75,7 +75,10 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.emit(code.OpPop)
|
|
||||||
|
if !c.lastInstructionIs(code.OpNoop) {
|
||||||
|
c.emit(code.OpPop)
|
||||||
|
}
|
||||||
|
|
||||||
case *ast.InfixExpression:
|
case *ast.InfixExpression:
|
||||||
if node.Operator == "<" {
|
if node.Operator == "<" {
|
||||||
@@ -196,7 +199,11 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *ast.LetStatement:
|
case *ast.LetStatement:
|
||||||
symbol := c.symbolTable.Define(node.Name.Value)
|
symbol, ok := c.symbolTable.Resolve(node.Name.Value)
|
||||||
|
if !ok {
|
||||||
|
symbol = c.symbolTable.Define(node.Name.Value)
|
||||||
|
}
|
||||||
|
|
||||||
err := c.Compile(node.Value)
|
err := c.Compile(node.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -328,6 +335,27 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||||||
|
|
||||||
c.emit(code.OpCall, len(node.Arguments))
|
c.emit(code.OpCall, len(node.Arguments))
|
||||||
|
|
||||||
|
case *ast.WhileExpression:
|
||||||
|
jumpConditionPos := len(c.currentInstructions())
|
||||||
|
|
||||||
|
err := c.Compile(node.Condition)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit an `OpJump`with a bogus value
|
||||||
|
jumpIfFalsePos := c.emit(code.OpJumpNotTruthy, 0xFFFF)
|
||||||
|
|
||||||
|
err = c.Compile(node.Consequence)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.emit(code.OpJump, jumpConditionPos)
|
||||||
|
|
||||||
|
afterConsequencePos := c.emit(code.OpNoop)
|
||||||
|
c.changeOperand(jumpIfFalsePos, afterConsequencePos)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -836,6 +836,24 @@ func TestLetStatementScopes(t *testing.T) {
|
|||||||
code.Make(code.OpPop),
|
code.Make(code.OpPop),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let a = 0;
|
||||||
|
let a = a + 1;
|
||||||
|
`,
|
||||||
|
expectedConstants: []interface{}{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
code.Make(code.OpSetGlobal, 0),
|
||||||
|
code.Make(code.OpGetGlobal, 0),
|
||||||
|
code.Make(code.OpConstant, 1),
|
||||||
|
code.Make(code.OpAdd),
|
||||||
|
code.Make(code.OpSetGlobal, 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runCompilerTests(t, tests)
|
runCompilerTests(t, tests)
|
||||||
@@ -854,7 +872,7 @@ func TestBuiltins(t *testing.T) {
|
|||||||
code.Make(code.OpArray, 0),
|
code.Make(code.OpArray, 0),
|
||||||
code.Make(code.OpCall, 1),
|
code.Make(code.OpCall, 1),
|
||||||
code.Make(code.OpPop),
|
code.Make(code.OpPop),
|
||||||
code.Make(code.OpGetBuiltin, 5),
|
code.Make(code.OpGetBuiltin, 6),
|
||||||
code.Make(code.OpArray, 0),
|
code.Make(code.OpArray, 0),
|
||||||
code.Make(code.OpConstant, 0),
|
code.Make(code.OpConstant, 0),
|
||||||
code.Make(code.OpCall, 2),
|
code.Make(code.OpCall, 2),
|
||||||
@@ -950,6 +968,33 @@ func TestRecursiveFunctions(t *testing.T) {
|
|||||||
runCompilerTests(t, tests)
|
runCompilerTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIteration(t *testing.T) {
|
||||||
|
tests := []compilerTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
while (true) { 10 };
|
||||||
|
`,
|
||||||
|
expectedConstants: []interface{}{10},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
// 0000
|
||||||
|
code.Make(code.OpTrue),
|
||||||
|
// 0001
|
||||||
|
code.Make(code.OpJumpNotTruthy, 11),
|
||||||
|
// 0004
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
// 0007
|
||||||
|
code.Make(code.OpPop),
|
||||||
|
// 0008
|
||||||
|
code.Make(code.OpJump, 0),
|
||||||
|
// 0011
|
||||||
|
code.Make(code.OpNoop),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCompilerTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import (
|
|||||||
|
|
||||||
var builtins = map[string]*object.Builtin{
|
var builtins = map[string]*object.Builtin{
|
||||||
"len": object.GetBuiltinByName("len"),
|
"len": object.GetBuiltinByName("len"),
|
||||||
|
"input": object.GetBuiltinByName("input"),
|
||||||
|
"print": object.GetBuiltinByName("print"),
|
||||||
"first": object.GetBuiltinByName("first"),
|
"first": object.GetBuiltinByName("first"),
|
||||||
"last": object.GetBuiltinByName("last"),
|
"last": object.GetBuiltinByName("last"),
|
||||||
"rest": object.GetBuiltinByName("rest"),
|
"rest": object.GetBuiltinByName("rest"),
|
||||||
"push": object.GetBuiltinByName("push"),
|
"push": object.GetBuiltinByName("push"),
|
||||||
"puts": object.GetBuiltinByName("puts"),
|
"pop": object.GetBuiltinByName("pop"),
|
||||||
|
"exit": object.GetBuiltinByName("exit"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
|||||||
case *ast.IfExpression:
|
case *ast.IfExpression:
|
||||||
return evalIfExpression(node, env)
|
return evalIfExpression(node, env)
|
||||||
|
|
||||||
|
case *ast.WhileExpression:
|
||||||
|
return evalWhileExpression(node, env)
|
||||||
|
|
||||||
case *ast.ReturnStatement:
|
case *ast.ReturnStatement:
|
||||||
val := Eval(node.ReturnValue, env)
|
val := Eval(node.ReturnValue, env)
|
||||||
if isError(val) {
|
if isError(val) {
|
||||||
@@ -123,6 +126,29 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
|
||||||
|
var result object.Object
|
||||||
|
|
||||||
|
for {
|
||||||
|
condition := Eval(we.Condition, env)
|
||||||
|
if isError(condition) {
|
||||||
|
return condition
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTruthy(condition) {
|
||||||
|
result = Eval(we.Consequence, env)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return NULL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||||
var result object.Object
|
var result object.Object
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package evaluator
|
package evaluator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
"monkey/parser"
|
"monkey/parser"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -338,22 +341,25 @@ func TestBuiltinFunctions(t *testing.T) {
|
|||||||
{`len("")`, 0},
|
{`len("")`, 0},
|
||||||
{`len("four")`, 4},
|
{`len("four")`, 4},
|
||||||
{`len("hello world")`, 11},
|
{`len("hello world")`, 11},
|
||||||
{`len(1)`, "argument to `len` not supported, got INTEGER"},
|
{`len(1)`, errors.New("argument to `len` not supported, got INTEGER")},
|
||||||
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
|
{`len("one", "two")`, errors.New("wrong number of arguments. got=2, want=1")},
|
||||||
{`len("∑")`, 1},
|
{`len("∑")`, 1},
|
||||||
{`len([1, 2, 3])`, 3},
|
{`len([1, 2, 3])`, 3},
|
||||||
{`len([])`, 0},
|
{`len([])`, 0},
|
||||||
{`first([1, 2, 3])`, 1},
|
{`first([1, 2, 3])`, 1},
|
||||||
{`first([])`, nil},
|
{`first([])`, nil},
|
||||||
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
|
{`first(1)`, errors.New("argument to `first` must be ARRAY, got INTEGER")},
|
||||||
{`last([1, 2, 3])`, 3},
|
{`last([1, 2, 3])`, 3},
|
||||||
{`last([])`, nil},
|
{`last([])`, nil},
|
||||||
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
|
{`last(1)`, errors.New("argument to `last` must be ARRAY, got INTEGER")},
|
||||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||||
{`rest([])`, nil},
|
{`rest([])`, nil},
|
||||||
{`push([], 1)`, []int{1}},
|
{`push([], 1)`, []int{1}},
|
||||||
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"},
|
{`push(1, 1)`, errors.New("argument to `push` must be ARRAY, got INTEGER")},
|
||||||
{`puts("Hello World")`, nil},
|
{`print("Hello World")`, nil},
|
||||||
|
{`input()`, ""},
|
||||||
|
{`pop([])`, errors.New("cannot pop from an empty array")},
|
||||||
|
{`pop([1])`, 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -363,13 +369,15 @@ func TestBuiltinFunctions(t *testing.T) {
|
|||||||
case int:
|
case int:
|
||||||
testIntegerObject(t, evaluated, int64(expected))
|
testIntegerObject(t, evaluated, int64(expected))
|
||||||
case string:
|
case string:
|
||||||
|
testStringObject(t, evaluated, expected)
|
||||||
|
case error:
|
||||||
errObj, ok := evaluated.(*object.Error)
|
errObj, ok := evaluated.(*object.Error)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("object is not Error. got=%T (%+v)",
|
t.Errorf("object is not Error. got=%T (%+v)",
|
||||||
evaluated, evaluated)
|
evaluated, evaluated)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if errObj.Message != expected {
|
if errObj.Message != expected.Error() {
|
||||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||||
expected, errObj.Message)
|
expected, errObj.Message)
|
||||||
}
|
}
|
||||||
@@ -541,6 +549,31 @@ func TestHashIndexExpressions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWhileExpressions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{"while (false) { }", nil},
|
||||||
|
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10},
|
||||||
|
{"let n = 10; while (n > 0) { let n = n - 1 }; n", 0},
|
||||||
|
// FIXME: let is an expression statement and bind new values
|
||||||
|
// there is currently no assignment expressions :/
|
||||||
|
{"let n = 0; while (n < 10) { let n = n + 1 }", nil},
|
||||||
|
{"let n = 10; while (n > 0) { let n = n - 1 }", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
evaluated := testEval(tt.input)
|
||||||
|
integer, ok := tt.expected.(int)
|
||||||
|
if ok {
|
||||||
|
testIntegerObject(t, evaluated, int64(integer))
|
||||||
|
} else {
|
||||||
|
testNullObject(t, evaluated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testEval(input string) object.Object {
|
func testEval(input string) object.Object {
|
||||||
l := lexer.New(input)
|
l := lexer.New(input)
|
||||||
p := parser.New(l)
|
p := parser.New(l)
|
||||||
@@ -585,3 +618,32 @@ func testNullObject(t *testing.T, obj object.Object) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testStringObject(t *testing.T, obj object.Object, expected string) bool {
|
||||||
|
result, ok := obj.(*object.String)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("object is not String. got=%T (%+v)", obj, obj)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if result.Value != expected {
|
||||||
|
t.Errorf("object has wrong value. got=%s, want=%s",
|
||||||
|
result.Value, expected)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExamples(t *testing.T) {
|
||||||
|
matches, err := filepath.Glob("../examples/*.monkey")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
b, err := os.ReadFile(match)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
testEval(string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ let book = {
|
|||||||
let printBookName = fn(book) {
|
let printBookName = fn(book) {
|
||||||
let title = book["title"];
|
let title = book["title"];
|
||||||
let author = book["author"];
|
let author = book["author"];
|
||||||
puts(author + " - " + title);
|
print(author + " - " + title);
|
||||||
};
|
};
|
||||||
|
|
||||||
printBookName(book);
|
printBookName(book);
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ let fib = fn(x) {
|
|||||||
return fib(x-1) + fib(x-2)
|
return fib(x-1) + fib(x-2)
|
||||||
}
|
}
|
||||||
|
|
||||||
puts(fib(35))
|
print(fib(35))
|
||||||
@@ -8,4 +8,4 @@ let fib = fn(n, a, b) {
|
|||||||
return fib(n - 1, b, a + b)
|
return fib(n - 1, b, a + b)
|
||||||
}
|
}
|
||||||
|
|
||||||
puts(fib(35, 0, 1))
|
print(fib(35, 0, 1))
|
||||||
3
examples/input.monkey
Normal file
3
examples/input.monkey
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
let name = input("What is your name? ")
|
||||||
|
|
||||||
|
print("Hello " + name)
|
||||||
131
main.go
131
main.go
@@ -3,73 +3,90 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"monkey/compiler"
|
"monkey/compiler"
|
||||||
"monkey/evaluator"
|
|
||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
"monkey/object"
|
|
||||||
"monkey/parser"
|
"monkey/parser"
|
||||||
"monkey/vm"
|
"monkey/repl"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
var engine = flag.String("engine", "vm", "use 'vm' or 'eval'")
|
var (
|
||||||
|
engine string
|
||||||
|
interactive bool
|
||||||
|
compile bool
|
||||||
|
version bool
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
var input = `
|
func init() {
|
||||||
let fibonacci = fn(x) {
|
flag.Usage = func() {
|
||||||
if (x == 0) {
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] [<filename>]\n", path.Base(os.Args[0]))
|
||||||
0
|
flag.PrintDefaults()
|
||||||
} else {
|
os.Exit(0)
|
||||||
if (x == 1) {
|
}
|
||||||
return 1;
|
|
||||||
} else {
|
flag.BoolVar(&version, "v", false, "display version information")
|
||||||
fibonacci(x - 1) + fibonacci(x - 2);
|
flag.BoolVar(&debug, "d", false, "enable debug mode")
|
||||||
}
|
flag.BoolVar(&compile, "c", false, "compile input to bytecode")
|
||||||
}
|
|
||||||
};
|
flag.BoolVar(&interactive, "i", false, "enable interactive mode")
|
||||||
fibonacci(35);
|
flag.StringVar(&engine, "e", "vm", "engine to use (eval or vm)")
|
||||||
`
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var duration time.Duration
|
if version {
|
||||||
var result object.Object
|
fmt.Printf("%s %s", path.Base(os.Args[0]), FullVersion())
|
||||||
|
os.Exit(0)
|
||||||
l := lexer.New(input)
|
|
||||||
p := parser.New(l)
|
|
||||||
program := p.ParseProgram()
|
|
||||||
|
|
||||||
if *engine == "vm" {
|
|
||||||
comp := compiler.New()
|
|
||||||
err := comp.Compile(program)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("compiler error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
machine := vm.New(comp.Bytecode())
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
err = machine.Run()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("vm error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
duration = time.Since(start)
|
|
||||||
result = machine.LastPoppedStackElem()
|
|
||||||
} else {
|
|
||||||
env := object.NewEnvironment()
|
|
||||||
start := time.Now()
|
|
||||||
result = evaluator.Eval(program, env)
|
|
||||||
duration = time.Since(start)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(
|
user, err := user.Current()
|
||||||
"engine=%s, result=%s, duration=%s\n",
|
if err != nil {
|
||||||
*engine,
|
log.Fatalf("could not determine current user: %s", err)
|
||||||
result.Inspect(),
|
}
|
||||||
duration)
|
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if compile {
|
||||||
|
if len(args) < 1 {
|
||||||
|
log.Fatal("no source file given to compile")
|
||||||
|
}
|
||||||
|
f, err := os.Open(args[0])
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := lexer.New(string(b))
|
||||||
|
p := parser.New(l)
|
||||||
|
|
||||||
|
program := p.ParseProgram()
|
||||||
|
if len(p.Errors()) != 0 {
|
||||||
|
log.Fatal(p.Errors())
|
||||||
|
}
|
||||||
|
|
||||||
|
c := compiler.New()
|
||||||
|
err = c.Compile(program)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Bytecode()
|
||||||
|
fmt.Printf("%s\n", code.Instructions)
|
||||||
|
} else {
|
||||||
|
opts := &repl.Options{
|
||||||
|
Debug: debug,
|
||||||
|
Engine: engine,
|
||||||
|
Interactive: interactive,
|
||||||
|
}
|
||||||
|
repl := repl.New(user.Username, args, opts)
|
||||||
|
repl.Run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
var Builtins = []struct {
|
var Builtins = []struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -18,7 +24,7 @@ var Builtins = []struct {
|
|||||||
case *Array:
|
case *Array:
|
||||||
return &Integer{Value: int64(len(arg.Elements))}
|
return &Integer{Value: int64(len(arg.Elements))}
|
||||||
case *String:
|
case *String:
|
||||||
return &Integer{Value: int64(len(arg.Value))}
|
return &Integer{Value: int64(utf8.RuneCountInString(arg.Value))}
|
||||||
default:
|
default:
|
||||||
return newError("argument to `len` not supported, got %s",
|
return newError("argument to `len` not supported, got %s",
|
||||||
args[0].Type())
|
args[0].Type())
|
||||||
@@ -27,7 +33,30 @@ var Builtins = []struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"puts",
|
"input",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) > 0 {
|
||||||
|
obj, ok := args[0].(*String)
|
||||||
|
if !ok {
|
||||||
|
return newError(
|
||||||
|
"argument to `input` not supported, got %s",
|
||||||
|
args[0].Type(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stdout, obj.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
line, _, err := buffer.ReadLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return newError(fmt.Sprintf("error reading input from stdin: %s", err))
|
||||||
|
}
|
||||||
|
return &String{Value: string(line)}
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"print",
|
||||||
&Builtin{Fn: func(args ...Object) Object {
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
fmt.Println(arg.Inspect())
|
fmt.Println(arg.Inspect())
|
||||||
@@ -123,6 +152,48 @@ var Builtins = []struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pop",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return newError("wrong number of arguments. got=%d, want=1",
|
||||||
|
len(args))
|
||||||
|
}
|
||||||
|
if args[0].Type() != ARRAY_OBJ {
|
||||||
|
return newError("argument to `pop` must be ARRAY, got %s",
|
||||||
|
args[0].Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := args[0].(*Array)
|
||||||
|
length := len(arr.Elements)
|
||||||
|
|
||||||
|
if length == 0 {
|
||||||
|
return newError("cannot pop from an empty array")
|
||||||
|
}
|
||||||
|
|
||||||
|
element := arr.Elements[length-1]
|
||||||
|
arr.Elements = arr.Elements[:length-1]
|
||||||
|
|
||||||
|
return element
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit",
|
||||||
|
&Builtin{Fn: func(args ...Object) Object {
|
||||||
|
if len(args) == 1 {
|
||||||
|
if args[0].Type() != INTEGER_OBJ {
|
||||||
|
return newError("argument to `exit` must be INTEGER, got %s",
|
||||||
|
args[0].Type())
|
||||||
|
}
|
||||||
|
os.Exit(int(args[0].(*Integer).Value))
|
||||||
|
} else {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func newError(format string, a ...interface{}) *Error {
|
func newError(format string, a ...interface{}) *Error {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func New(l *lexer.Lexer) *Parser {
|
|||||||
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
||||||
p.registerPrefix(token.IF, p.parseIfExpression)
|
p.registerPrefix(token.IF, p.parseIfExpression)
|
||||||
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
||||||
|
p.registerPrefix(token.WHILE, p.parseWhileExpression)
|
||||||
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
||||||
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
||||||
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
|
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
|
||||||
@@ -492,3 +493,26 @@ func (p *Parser) parseHashLiteral() ast.Expression {
|
|||||||
|
|
||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseWhileExpression() ast.Expression {
|
||||||
|
expression := &ast.WhileExpression{Token: p.curToken}
|
||||||
|
|
||||||
|
if !p.expectPeek(token.LPAREN) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.nextToken()
|
||||||
|
expression.Condition = p.parseExpression(LOWEST)
|
||||||
|
|
||||||
|
if !p.expectPeek(token.RPAREN) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.expectPeek(token.LBRACE) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expression.Consequence = p.parseBlockStatement()
|
||||||
|
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
|||||||
@@ -876,6 +876,51 @@ func TestFunctionLiteralWithName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWhileExpression(t *testing.T) {
|
||||||
|
input := `while (x < y) { x }`
|
||||||
|
|
||||||
|
l := lexer.New(input)
|
||||||
|
p := New(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if len(program.Statements) != 1 {
|
||||||
|
t.Fatalf("program.Statements does not contain %d statements. got=%d\n",
|
||||||
|
1, len(program.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T",
|
||||||
|
program.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, ok := stmt.Expression.(*ast.WhileExpression)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("stmt.Expression is not ast.WhileExpression. got=%T",
|
||||||
|
stmt.Expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testInfixExpression(t, exp.Condition, "x", "<", "y") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(exp.Consequence.Statements) != 1 {
|
||||||
|
t.Errorf("consequence is not 1 statements. got=%d\n",
|
||||||
|
len(exp.Consequence.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T",
|
||||||
|
exp.Consequence.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testIdentifier(t, consequence.Expression, "x") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
|
func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
|
||||||
if s.TokenLiteral() != "let" {
|
if s.TokenLiteral() != "let" {
|
||||||
t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())
|
t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())
|
||||||
|
|||||||
277
repl/repl.go
277
repl/repl.go
@@ -1,69 +1,29 @@
|
|||||||
package repl
|
package repl
|
||||||
|
|
||||||
|
// Package repl implements the Read-Eval-Print-Loop or interactive console
|
||||||
|
// by lexing, parsing and evaluating the input in the interpreter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"monkey/compiler"
|
"monkey/compiler"
|
||||||
|
"monkey/evaluator"
|
||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
"monkey/parser"
|
"monkey/parser"
|
||||||
"monkey/vm"
|
"monkey/vm"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PROMPT is the REPL prompt displayed for each input
|
||||||
const PROMPT = ">> "
|
const PROMPT = ">> "
|
||||||
|
|
||||||
func Start(in io.Reader, out io.Writer) {
|
// MonkeyFace is the REPL's face of shock and horror when you encounter a
|
||||||
scanner := bufio.NewScanner(in)
|
// parser error :D
|
||||||
|
const MonkeyFace = ` __,__
|
||||||
constants := []object.Object{}
|
|
||||||
globals := make([]object.Object, vm.GlobalsSize)
|
|
||||||
symbolTable := compiler.NewSymbolTable()
|
|
||||||
for i, v := range object.Builtins {
|
|
||||||
symbolTable.DefineBuiltin(i, v.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
fmt.Fprintf(out, PROMPT)
|
|
||||||
scanned := scanner.Scan()
|
|
||||||
if !scanned {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line := scanner.Text()
|
|
||||||
l := lexer.New(line)
|
|
||||||
p := parser.New(l)
|
|
||||||
|
|
||||||
program := p.ParseProgram()
|
|
||||||
if len(p.Errors()) != 0 {
|
|
||||||
printParserErrors(out, p.Errors())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
comp := compiler.NewWithState(symbolTable, constants)
|
|
||||||
err := comp.Compile(program)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
code := comp.Bytecode()
|
|
||||||
constants = code.Constants
|
|
||||||
|
|
||||||
machine := vm.NewWithGlobalState(comp.Bytecode(), globals)
|
|
||||||
err = machine.Run()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
stackTop := machine.LastPoppedStackElem()
|
|
||||||
io.WriteString(out, stackTop.Inspect())
|
|
||||||
io.WriteString(out, "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MONKEY_FACE = ` __,__
|
|
||||||
.--. .-" "-. .--.
|
.--. .-" "-. .--.
|
||||||
/ .. \/ .-. .-. \/ .. \
|
/ .. \/ .-. .-. \/ .. \
|
||||||
| | '| / Y \ |' | |
|
| | '| / Y \ |' | |
|
||||||
@@ -76,11 +36,222 @@ const MONKEY_FACE = ` __,__
|
|||||||
'-----'
|
'-----'
|
||||||
`
|
`
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Debug bool
|
||||||
|
Engine string
|
||||||
|
Interactive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type VMState struct {
|
||||||
|
constants []object.Object
|
||||||
|
globals []object.Object
|
||||||
|
symbols *compiler.SymbolTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVMState() *VMState {
|
||||||
|
symbolTable := compiler.NewSymbolTable()
|
||||||
|
for i, v := range object.Builtins {
|
||||||
|
symbolTable.DefineBuiltin(i, v.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VMState{
|
||||||
|
constants: []object.Object{},
|
||||||
|
globals: make([]object.Object, vm.GlobalsSize),
|
||||||
|
symbols: symbolTable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type REPL struct {
|
||||||
|
user string
|
||||||
|
args []string
|
||||||
|
opts *Options
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(user string, args []string, opts *Options) *REPL {
|
||||||
|
return &REPL{user, args, opts}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eval parses and evalulates the program given by f and returns the resulting
|
||||||
|
// environment, any errors are printed to stderr
|
||||||
|
func (r *REPL) Eval(f io.Reader) (env *object.Environment) {
|
||||||
|
env = object.NewEnvironment()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := lexer.New(string(b))
|
||||||
|
p := parser.New(l)
|
||||||
|
|
||||||
|
program := p.ParseProgram()
|
||||||
|
if len(p.Errors()) != 0 {
|
||||||
|
printParserErrors(os.Stderr, p.Errors())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluator.Eval(program, env)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec parses, compiles and executes the program given by f and returns
|
||||||
|
// the resulting virtual machine, any errors are printed to stderr
|
||||||
|
func (r *REPL) Exec(f io.Reader) (state *VMState) {
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state = NewVMState()
|
||||||
|
|
||||||
|
l := lexer.New(string(b))
|
||||||
|
p := parser.New(l)
|
||||||
|
|
||||||
|
program := p.ParseProgram()
|
||||||
|
if len(p.Errors()) != 0 {
|
||||||
|
printParserErrors(os.Stderr, p.Errors())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := compiler.NewWithState(state.symbols, state.constants)
|
||||||
|
err = c.Compile(program)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Bytecode()
|
||||||
|
state.constants = code.Constants
|
||||||
|
|
||||||
|
machine := vm.NewWithGlobalState(code, state.globals)
|
||||||
|
err = machine.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartEvalLoop starts the REPL in a continious eval loop
|
||||||
|
func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environment) {
|
||||||
|
scanner := bufio.NewScanner(in)
|
||||||
|
|
||||||
|
if env == nil {
|
||||||
|
env = object.NewEnvironment()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Printf(PROMPT)
|
||||||
|
scanned := scanner.Scan()
|
||||||
|
if !scanned {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
l := lexer.New(line)
|
||||||
|
p := parser.New(l)
|
||||||
|
|
||||||
|
program := p.ParseProgram()
|
||||||
|
if len(p.Errors()) != 0 {
|
||||||
|
printParserErrors(out, p.Errors())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := evaluator.Eval(program, env)
|
||||||
|
if obj != nil {
|
||||||
|
io.WriteString(out, obj.Inspect())
|
||||||
|
io.WriteString(out, "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartExecLoop starts the REPL in a continious exec loop
|
||||||
|
func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
|
||||||
|
scanner := bufio.NewScanner(in)
|
||||||
|
|
||||||
|
if state == nil {
|
||||||
|
state = NewVMState()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Printf(PROMPT)
|
||||||
|
scanned := scanner.Scan()
|
||||||
|
if !scanned {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
l := lexer.New(line)
|
||||||
|
p := parser.New(l)
|
||||||
|
|
||||||
|
program := p.ParseProgram()
|
||||||
|
if len(p.Errors()) != 0 {
|
||||||
|
printParserErrors(out, p.Errors())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c := compiler.NewWithState(state.symbols, state.constants)
|
||||||
|
err := c.Compile(program)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Bytecode()
|
||||||
|
state.constants = code.Constants
|
||||||
|
|
||||||
|
machine := vm.NewWithGlobalState(code, state.globals)
|
||||||
|
err = machine.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stackTop := machine.LastPoppedStackElem()
|
||||||
|
io.WriteString(out, stackTop.Inspect())
|
||||||
|
io.WriteString(out, "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REPL) Run() {
|
||||||
|
if len(r.args) == 1 {
|
||||||
|
f, err := os.Open(r.args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not open source file %s: %s", r.args[0], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.opts.Engine == "eval" {
|
||||||
|
env := r.Eval(f)
|
||||||
|
if r.opts.Interactive {
|
||||||
|
r.StartEvalLoop(os.Stdin, os.Stdout, env)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state := r.Exec(f)
|
||||||
|
if r.opts.Interactive {
|
||||||
|
r.StartExecLoop(os.Stdin, os.Stdout, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Hello %s! This is the Monkey programming language!\n", r.user)
|
||||||
|
fmt.Printf("Feel free to type in commands\n")
|
||||||
|
if r.opts.Engine == "eval" {
|
||||||
|
r.StartEvalLoop(os.Stdin, os.Stdout, nil)
|
||||||
|
} else {
|
||||||
|
r.StartExecLoop(os.Stdin, os.Stdout, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func printParserErrors(out io.Writer, errors []string) {
|
func printParserErrors(out io.Writer, errors []string) {
|
||||||
io.WriteString(out, MONKEY_FACE)
|
io.WriteString(out, MonkeyFace)
|
||||||
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
||||||
io.WriteString(out, " parser errors:\n")
|
io.WriteString(out, " parser errors:\n")
|
||||||
for _, msg := range errors {
|
for _, msg := range errors {
|
||||||
io.WriteString(out, "\t"+msg+"\t")
|
io.WriteString(out, "\t"+msg+"\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const (
|
|||||||
IF = "IF"
|
IF = "IF"
|
||||||
ELSE = "ELSE"
|
ELSE = "ELSE"
|
||||||
RETURN = "RETURN"
|
RETURN = "RETURN"
|
||||||
|
WHILE = "WHILE"
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords = map[string]TokenType{
|
var keywords = map[string]TokenType{
|
||||||
@@ -60,6 +61,7 @@ var keywords = map[string]TokenType{
|
|||||||
"if": IF,
|
"if": IF,
|
||||||
"else": ELSE,
|
"else": ELSE,
|
||||||
"return": RETURN,
|
"return": RETURN,
|
||||||
|
"while": WHILE,
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupIdent(ident string) TokenType {
|
func LookupIdent(ident string) TokenType {
|
||||||
|
|||||||
18
version.go
Normal file
18
version.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Version release version
|
||||||
|
Version = "0.0.1"
|
||||||
|
|
||||||
|
// GitCommit will be overwritten automatically by the build system
|
||||||
|
GitCommit = "HEAD"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FullVersion returns the full version and commit hash
|
||||||
|
func FullVersion() string {
|
||||||
|
return fmt.Sprintf("%s@%s", Version, GitCommit)
|
||||||
|
}
|
||||||
35
vim/monkey.vim
Normal file
35
vim/monkey.vim
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
" Vim Syntax File
|
||||||
|
" Language: monkey
|
||||||
|
" Creator: James Mills, prologic at shortcircuit dot net dot au
|
||||||
|
" Last Change: 31st January 2019
|
||||||
|
|
||||||
|
if version < 600
|
||||||
|
syntax clear
|
||||||
|
elseif exists("b:current_syntax")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
syntax case match
|
||||||
|
|
||||||
|
syntax keyword xType true false
|
||||||
|
|
||||||
|
syntax keyword xKeyword let fn if else return while
|
||||||
|
|
||||||
|
syntax keyword xFunction len input print first last rest push pop exit
|
||||||
|
|
||||||
|
syntax keyword xOperator == != < > !
|
||||||
|
syntax keyword xOperator + - * /
|
||||||
|
|
||||||
|
syntax region xString start=/"/ skip=/\\./ end=/"/
|
||||||
|
|
||||||
|
" syntax region xComment start='#' end='$' keepend
|
||||||
|
|
||||||
|
highlight link xType Type
|
||||||
|
highlight link xKeyword Keyword
|
||||||
|
highlight link xFunction Function
|
||||||
|
highlight link xString String
|
||||||
|
" highlight link xComment Comment
|
||||||
|
highlight link xOperator Operator
|
||||||
|
highlight Operator ctermfg=5
|
||||||
|
|
||||||
|
let b:current_syntax = "monkey"
|
||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"monkey/lexer"
|
"monkey/lexer"
|
||||||
"monkey/object"
|
"monkey/object"
|
||||||
"monkey/parser"
|
"monkey/parser"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -597,7 +599,7 @@ func TestBuiltinFunctions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{`len([1, 2, 3])`, 3},
|
{`len([1, 2, 3])`, 3},
|
||||||
{`len([])`, 0},
|
{`len([])`, 0},
|
||||||
{`puts("hello", "world!")`, Null},
|
{`print("hello", "world!")`, Null},
|
||||||
{`first([1, 2, 3])`, 1},
|
{`first([1, 2, 3])`, 1},
|
||||||
{`first([])`, Null},
|
{`first([])`, Null},
|
||||||
{`first(1)`,
|
{`first(1)`,
|
||||||
@@ -620,6 +622,12 @@ func TestBuiltinFunctions(t *testing.T) {
|
|||||||
Message: "argument to `push` must be ARRAY, got INTEGER",
|
Message: "argument to `push` must be ARRAY, got INTEGER",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{`input()`, ""},
|
||||||
|
{`pop([])`, &object.Error{
|
||||||
|
Message: "cannot pop from an empty array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{`pop([1])`, 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
runVmTests(t, tests)
|
runVmTests(t, tests)
|
||||||
@@ -779,3 +787,49 @@ func TestRecursiveFibonacci(t *testing.T) {
|
|||||||
|
|
||||||
runVmTests(t, tests)
|
runVmTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIterations(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{"while (false) { }", nil},
|
||||||
|
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10},
|
||||||
|
{"let n = 10; while (n > 0) { let n = n - 1 }; n", 0},
|
||||||
|
// FIXME: let is an expression statement and bind new values
|
||||||
|
// there is currently no assignment expressions :/
|
||||||
|
{"let n = 0; while (n < 10) { let n = n + 1 }", nil},
|
||||||
|
{"let n = 10; while (n > 0) { let n = n - 1 }", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExamples(t *testing.T) {
|
||||||
|
matches, err := filepath.Glob("../examples/*.monkey")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
b, err := os.ReadFile(match)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := string(b)
|
||||||
|
program := parse(input)
|
||||||
|
|
||||||
|
c := compiler.New()
|
||||||
|
err = c.Compile(program)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(input)
|
||||||
|
t.Fatalf("compiler error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := New(c.Bytecode())
|
||||||
|
|
||||||
|
err = vm.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(input)
|
||||||
|
t.Fatalf("vm error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user