Add code from the book.
Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
parent
32f71702c9
commit
230fe61b12
255 changed files with 59009 additions and 0 deletions
1
wcig_code_1_2/00/.envrc
Normal file
1
wcig_code_1_2/00/.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
export GOPATH=$(pwd)
|
||||
339
wcig_code_1_2/00/src/monkey/ast/ast.go
Normal file
339
wcig_code_1_2/00/src/monkey/ast/ast.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"monkey/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The base Node interface
|
||||
type Node interface {
|
||||
TokenLiteral() string
|
||||
String() string
|
||||
}
|
||||
|
||||
// All statement nodes implement this
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
// All expression nodes implement this
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (p *Program) TokenLiteral() string {
|
||||
if len(p.Statements) > 0 {
|
||||
return p.Statements[0].TokenLiteral()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range p.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Statements
|
||||
type LetStatement struct {
|
||||
Token token.Token // the token.LET token
|
||||
Name *Identifier
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (ls *LetStatement) statementNode() {}
|
||||
func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
|
||||
func (ls *LetStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(ls.TokenLiteral() + " ")
|
||||
out.WriteString(ls.Name.String())
|
||||
out.WriteString(" = ")
|
||||
|
||||
if ls.Value != nil {
|
||||
out.WriteString(ls.Value.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ReturnStatement struct {
|
||||
Token token.Token // the 'return' token
|
||||
ReturnValue Expression
|
||||
}
|
||||
|
||||
func (rs *ReturnStatement) statementNode() {}
|
||||
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
|
||||
func (rs *ReturnStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(rs.TokenLiteral() + " ")
|
||||
|
||||
if rs.ReturnValue != nil {
|
||||
out.WriteString(rs.ReturnValue.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ExpressionStatement struct {
|
||||
Token token.Token // the first token of the expression
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (es *ExpressionStatement) statementNode() {}
|
||||
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
|
||||
func (es *ExpressionStatement) String() string {
|
||||
if es.Expression != nil {
|
||||
return es.Expression.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BlockStatement struct {
|
||||
Token token.Token // the { token
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (bs *BlockStatement) statementNode() {}
|
||||
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
|
||||
func (bs *BlockStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range bs.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Expressions
|
||||
type Identifier struct {
|
||||
Token token.Token // the token.IDENT token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (i *Identifier) expressionNode() {}
|
||||
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
|
||||
func (i *Identifier) String() string { return i.Value }
|
||||
|
||||
type Boolean struct {
|
||||
Token token.Token
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) expressionNode() {}
|
||||
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
|
||||
func (b *Boolean) String() string { return b.Token.Literal }
|
||||
|
||||
type IntegerLiteral struct {
|
||||
Token token.Token
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (il *IntegerLiteral) expressionNode() {}
|
||||
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
|
||||
func (il *IntegerLiteral) String() string { return il.Token.Literal }
|
||||
|
||||
type PrefixExpression struct {
|
||||
Token token.Token // The prefix token, e.g. !
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
|
||||
func (pe *PrefixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(pe.Operator)
|
||||
out.WriteString(pe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type InfixExpression struct {
|
||||
Token token.Token // The operator token, e.g. +
|
||||
Left Expression
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (oe *InfixExpression) expressionNode() {}
|
||||
func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
|
||||
func (oe *InfixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(oe.Left.String())
|
||||
out.WriteString(" " + oe.Operator + " ")
|
||||
out.WriteString(oe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IfExpression struct {
|
||||
Token token.Token // The 'if' token
|
||||
Condition Expression
|
||||
Consequence *BlockStatement
|
||||
Alternative *BlockStatement
|
||||
}
|
||||
|
||||
func (ie *IfExpression) expressionNode() {}
|
||||
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IfExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("if")
|
||||
out.WriteString(ie.Condition.String())
|
||||
out.WriteString(" ")
|
||||
out.WriteString(ie.Consequence.String())
|
||||
|
||||
if ie.Alternative != nil {
|
||||
out.WriteString("else ")
|
||||
out.WriteString(ie.Alternative.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type FunctionLiteral struct {
|
||||
Token token.Token // The 'fn' token
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fl *FunctionLiteral) expressionNode() {}
|
||||
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
|
||||
func (fl *FunctionLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range fl.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString(fl.TokenLiteral())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") ")
|
||||
out.WriteString(fl.Body.String())
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type CallExpression struct {
|
||||
Token token.Token // The '(' token
|
||||
Function Expression // Identifier or FunctionLiteral
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (ce *CallExpression) expressionNode() {}
|
||||
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
|
||||
func (ce *CallExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
args := []string{}
|
||||
for _, a := range ce.Arguments {
|
||||
args = append(args, a.String())
|
||||
}
|
||||
|
||||
out.WriteString(ce.Function.String())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(args, ", "))
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type StringLiteral struct {
|
||||
Token token.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) expressionNode() {}
|
||||
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
|
||||
func (sl *StringLiteral) String() string { return sl.Token.Literal }
|
||||
|
||||
type ArrayLiteral struct {
|
||||
Token token.Token // the '[' token
|
||||
Elements []Expression
|
||||
}
|
||||
|
||||
func (al *ArrayLiteral) expressionNode() {}
|
||||
func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal }
|
||||
func (al *ArrayLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, el := range al.Elements {
|
||||
elements = append(elements, el.String())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IndexExpression struct {
|
||||
Token token.Token // The [ token
|
||||
Left Expression
|
||||
Index Expression
|
||||
}
|
||||
|
||||
func (ie *IndexExpression) expressionNode() {}
|
||||
func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IndexExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(ie.Left.String())
|
||||
out.WriteString("[")
|
||||
out.WriteString(ie.Index.String())
|
||||
out.WriteString("])")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashLiteral struct {
|
||||
Token token.Token // the '{' token
|
||||
Pairs map[Expression]Expression
|
||||
}
|
||||
|
||||
func (hl *HashLiteral) expressionNode() {}
|
||||
func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal }
|
||||
func (hl *HashLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for key, value := range hl.Pairs {
|
||||
pairs = append(pairs, key.String()+":"+value.String())
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
28
wcig_code_1_2/00/src/monkey/ast/ast_test.go
Normal file
28
wcig_code_1_2/00/src/monkey/ast/ast_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"monkey/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
program := &Program{
|
||||
Statements: []Statement{
|
||||
&LetStatement{
|
||||
Token: token.Token{Type: token.LET, Literal: "let"},
|
||||
Name: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
|
||||
Value: "myVar",
|
||||
},
|
||||
Value: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
|
||||
Value: "anotherVar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if program.String() != "let myVar = anotherVar;" {
|
||||
t.Errorf("program.String() wrong. got=%q", program.String())
|
||||
}
|
||||
}
|
||||
117
wcig_code_1_2/00/src/monkey/evaluator/builtins.go
Normal file
117
wcig_code_1_2/00/src/monkey/evaluator/builtins.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"len": &object.Builtin{Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
|
||||
switch arg := args[0].(type) {
|
||||
case *object.Array:
|
||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
||||
case *object.String:
|
||||
return &object.Integer{Value: int64(len(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
},
|
||||
},
|
||||
"puts": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"first": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"last": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"rest": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]object.Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &object.Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"push": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]object.Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &object.Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
}
|
||||
442
wcig_code_1_2/00/src/monkey/evaluator/evaluator.go
Normal file
442
wcig_code_1_2/00/src/monkey/evaluator/evaluator.go
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var (
|
||||
NULL = &object.Null{}
|
||||
TRUE = &object.Boolean{Value: true}
|
||||
FALSE = &object.Boolean{Value: false}
|
||||
)
|
||||
|
||||
func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
switch node := node.(type) {
|
||||
|
||||
// Statements
|
||||
case *ast.Program:
|
||||
return evalProgram(node, env)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
return evalBlockStatement(node, env)
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
return Eval(node.Expression, env)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
val := Eval(node.ReturnValue, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
return &object.ReturnValue{Value: val}
|
||||
|
||||
case *ast.LetStatement:
|
||||
val := Eval(node.Value, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
env.Set(node.Name.Value, val)
|
||||
|
||||
// Expressions
|
||||
case *ast.IntegerLiteral:
|
||||
return &object.Integer{Value: node.Value}
|
||||
|
||||
case *ast.StringLiteral:
|
||||
return &object.String{Value: node.Value}
|
||||
|
||||
case *ast.Boolean:
|
||||
return nativeBoolToBooleanObject(node.Value)
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
return evalPrefixExpression(node.Operator, right)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
|
||||
return evalInfixExpression(node.Operator, left, right)
|
||||
|
||||
case *ast.IfExpression:
|
||||
return evalIfExpression(node, env)
|
||||
|
||||
case *ast.Identifier:
|
||||
return evalIdentifier(node, env)
|
||||
|
||||
case *ast.FunctionLiteral:
|
||||
params := node.Parameters
|
||||
body := node.Body
|
||||
return &object.Function{Parameters: params, Env: env, Body: body}
|
||||
|
||||
case *ast.CallExpression:
|
||||
function := Eval(node.Function, env)
|
||||
if isError(function) {
|
||||
return function
|
||||
}
|
||||
|
||||
args := evalExpressions(node.Arguments, env)
|
||||
if len(args) == 1 && isError(args[0]) {
|
||||
return args[0]
|
||||
}
|
||||
|
||||
return applyFunction(function, args)
|
||||
|
||||
case *ast.ArrayLiteral:
|
||||
elements := evalExpressions(node.Elements, env)
|
||||
if len(elements) == 1 && isError(elements[0]) {
|
||||
return elements[0]
|
||||
}
|
||||
return &object.Array{Elements: elements}
|
||||
|
||||
case *ast.IndexExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
index := Eval(node.Index, env)
|
||||
if isError(index) {
|
||||
return index
|
||||
}
|
||||
return evalIndexExpression(left, index)
|
||||
|
||||
case *ast.HashLiteral:
|
||||
return evalHashLiteral(node, env)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range program.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
switch result := result.(type) {
|
||||
case *object.ReturnValue:
|
||||
return result.Value
|
||||
case *object.Error:
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func evalBlockStatement(
|
||||
block *ast.BlockStatement,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range block.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
if result != nil {
|
||||
rt := result.Type()
|
||||
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return TRUE
|
||||
}
|
||||
return FALSE
|
||||
}
|
||||
|
||||
func evalPrefixExpression(operator string, right object.Object) object.Object {
|
||||
switch operator {
|
||||
case "!":
|
||||
return evalBangOperatorExpression(right)
|
||||
case "-":
|
||||
return evalMinusPrefixOperatorExpression(right)
|
||||
default:
|
||||
return newError("unknown operator: %s%s", operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
|
||||
return evalIntegerInfixExpression(operator, left, right)
|
||||
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
|
||||
return evalStringInfixExpression(operator, left, right)
|
||||
case operator == "==":
|
||||
return nativeBoolToBooleanObject(left == right)
|
||||
case operator == "!=":
|
||||
return nativeBoolToBooleanObject(left != right)
|
||||
case left.Type() != right.Type():
|
||||
return newError("type mismatch: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalBangOperatorExpression(right object.Object) object.Object {
|
||||
switch right {
|
||||
case TRUE:
|
||||
return FALSE
|
||||
case FALSE:
|
||||
return TRUE
|
||||
case NULL:
|
||||
return TRUE
|
||||
default:
|
||||
return FALSE
|
||||
}
|
||||
}
|
||||
|
||||
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
|
||||
if right.Type() != object.INTEGER_OBJ {
|
||||
return newError("unknown operator: -%s", right.Type())
|
||||
}
|
||||
|
||||
value := right.(*object.Integer).Value
|
||||
return &object.Integer{Value: -value}
|
||||
}
|
||||
|
||||
func evalIntegerInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
leftVal := left.(*object.Integer).Value
|
||||
rightVal := right.(*object.Integer).Value
|
||||
|
||||
switch operator {
|
||||
case "+":
|
||||
return &object.Integer{Value: leftVal + rightVal}
|
||||
case "-":
|
||||
return &object.Integer{Value: leftVal - rightVal}
|
||||
case "*":
|
||||
return &object.Integer{Value: leftVal * rightVal}
|
||||
case "/":
|
||||
return &object.Integer{Value: leftVal / rightVal}
|
||||
case "<":
|
||||
return nativeBoolToBooleanObject(leftVal < rightVal)
|
||||
case ">":
|
||||
return nativeBoolToBooleanObject(leftVal > rightVal)
|
||||
case "==":
|
||||
return nativeBoolToBooleanObject(leftVal == rightVal)
|
||||
case "!=":
|
||||
return nativeBoolToBooleanObject(leftVal != rightVal)
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalStringInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
if operator != "+" {
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
|
||||
leftVal := left.(*object.String).Value
|
||||
rightVal := right.(*object.String).Value
|
||||
return &object.String{Value: leftVal + rightVal}
|
||||
}
|
||||
|
||||
func evalIfExpression(
|
||||
ie *ast.IfExpression,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
condition := Eval(ie.Condition, env)
|
||||
if isError(condition) {
|
||||
return condition
|
||||
}
|
||||
|
||||
if isTruthy(condition) {
|
||||
return Eval(ie.Consequence, env)
|
||||
} else if ie.Alternative != nil {
|
||||
return Eval(ie.Alternative, env)
|
||||
} else {
|
||||
return NULL
|
||||
}
|
||||
}
|
||||
|
||||
func evalIdentifier(
|
||||
node *ast.Identifier,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
if val, ok := env.Get(node.Value); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
if builtin, ok := builtins[node.Value]; ok {
|
||||
return builtin
|
||||
}
|
||||
|
||||
return newError("identifier not found: " + node.Value)
|
||||
}
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj {
|
||||
case NULL:
|
||||
return false
|
||||
case TRUE:
|
||||
return true
|
||||
case FALSE:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func newError(format string, a ...interface{}) *object.Error {
|
||||
return &object.Error{Message: fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
func isError(obj object.Object) bool {
|
||||
if obj != nil {
|
||||
return obj.Type() == object.ERROR_OBJ
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func evalExpressions(
|
||||
exps []ast.Expression,
|
||||
env *object.Environment,
|
||||
) []object.Object {
|
||||
var result []object.Object
|
||||
|
||||
for _, e := range exps {
|
||||
evaluated := Eval(e, env)
|
||||
if isError(evaluated) {
|
||||
return []object.Object{evaluated}
|
||||
}
|
||||
result = append(result, evaluated)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func applyFunction(fn object.Object, args []object.Object) object.Object {
|
||||
switch fn := fn.(type) {
|
||||
|
||||
case *object.Function:
|
||||
extendedEnv := extendFunctionEnv(fn, args)
|
||||
evaluated := Eval(fn.Body, extendedEnv)
|
||||
return unwrapReturnValue(evaluated)
|
||||
|
||||
case *object.Builtin:
|
||||
return fn.Fn(args...)
|
||||
|
||||
default:
|
||||
return newError("not a function: %s", fn.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func extendFunctionEnv(
|
||||
fn *object.Function,
|
||||
args []object.Object,
|
||||
) *object.Environment {
|
||||
env := object.NewEnclosedEnvironment(fn.Env)
|
||||
|
||||
for paramIdx, param := range fn.Parameters {
|
||||
env.Set(param.Value, args[paramIdx])
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func unwrapReturnValue(obj object.Object) object.Object {
|
||||
if returnValue, ok := obj.(*object.ReturnValue); ok {
|
||||
return returnValue.Value
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func evalIndexExpression(left, index object.Object) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return evalArrayIndexExpression(left, index)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return evalHashIndexExpression(left, index)
|
||||
default:
|
||||
return newError("index operator not supported: %s", left.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalArrayIndexExpression(array, index object.Object) object.Object {
|
||||
arrayObject := array.(*object.Array)
|
||||
idx := index.(*object.Integer).Value
|
||||
max := int64(len(arrayObject.Elements) - 1)
|
||||
|
||||
if idx < 0 || idx > max {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return arrayObject.Elements[idx]
|
||||
}
|
||||
|
||||
func evalHashLiteral(
|
||||
node *ast.HashLiteral,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
|
||||
for keyNode, valueNode := range node.Pairs {
|
||||
key := Eval(keyNode, env)
|
||||
if isError(key) {
|
||||
return key
|
||||
}
|
||||
|
||||
hashKey, ok := key.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", key.Type())
|
||||
}
|
||||
|
||||
value := Eval(valueNode, env)
|
||||
if isError(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
hashed := hashKey.HashKey()
|
||||
pairs[hashed] = object.HashPair{Key: key, Value: value}
|
||||
}
|
||||
|
||||
return &object.Hash{Pairs: pairs}
|
||||
}
|
||||
|
||||
func evalHashIndexExpression(hash, index object.Object) object.Object {
|
||||
hashObject := hash.(*object.Hash)
|
||||
|
||||
key, ok := index.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
|
||||
pair, ok := hashObject.Pairs[key.HashKey()]
|
||||
if !ok {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return pair.Value
|
||||
}
|
||||
629
wcig_code_1_2/00/src/monkey/evaluator/evaluator_test.go
Normal file
629
wcig_code_1_2/00/src/monkey/evaluator/evaluator_test.go
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvalIntegerExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"5", 5},
|
||||
{"10", 10},
|
||||
{"-5", -5},
|
||||
{"-10", -10},
|
||||
{"5 + 5 + 5 + 5 - 10", 10},
|
||||
{"2 * 2 * 2 * 2 * 2", 32},
|
||||
{"-50 + 100 + -50", 0},
|
||||
{"5 * 2 + 10", 20},
|
||||
{"5 + 2 * 10", 25},
|
||||
{"20 + 2 * -10", 0},
|
||||
{"50 / 2 * 2 + 10", 60},
|
||||
{"2 * (5 + 10)", 30},
|
||||
{"3 * 3 * 3 + 10", 37},
|
||||
{"3 * (3 * 3) + 10", 37},
|
||||
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalBooleanExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1 < 2", true},
|
||||
{"1 > 2", false},
|
||||
{"1 < 1", false},
|
||||
{"1 > 1", false},
|
||||
{"1 == 1", true},
|
||||
{"1 != 1", false},
|
||||
{"1 == 2", false},
|
||||
{"1 != 2", true},
|
||||
{"true == true", true},
|
||||
{"false == false", true},
|
||||
{"true == false", false},
|
||||
{"true != false", true},
|
||||
{"false != true", true},
|
||||
{"(1 < 2) == true", true},
|
||||
{"(1 < 2) == false", false},
|
||||
{"(1 > 2) == true", false},
|
||||
{"(1 > 2) == false", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBangOperator(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"!true", false},
|
||||
{"!false", true},
|
||||
{"!5", false},
|
||||
{"!!true", true},
|
||||
{"!!false", false},
|
||||
{"!!5", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfElseExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"if (true) { 10 }", 10},
|
||||
{"if (false) { 10 }", nil},
|
||||
{"if (1) { 10 }", 10},
|
||||
{"if (1 < 2) { 10 }", 10},
|
||||
{"if (1 > 2) { 10 }", nil},
|
||||
{"if (1 > 2) { 10 } else { 20 }", 20},
|
||||
{"if (1 < 2) { 10 } else { 20 }", 10},
|
||||
}
|
||||
|
||||
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 TestReturnStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"return 10;", 10},
|
||||
{"return 10; 9;", 10},
|
||||
{"return 2 * 5; 9;", 10},
|
||||
{"9; return 2 * 5; 9;", 10},
|
||||
{"if (10 > 1) { return 10; }", 10},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
return x;
|
||||
x + 10;
|
||||
};
|
||||
f(10);`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
let result = x + 10;
|
||||
return result;
|
||||
return 10;
|
||||
};
|
||||
f(10);`,
|
||||
20,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedMessage string
|
||||
}{
|
||||
{
|
||||
"5 + true;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5 + true; 5;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"-true",
|
||||
"unknown operator: -BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false + true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5; true + false; 5",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`"Hello" - "World"`,
|
||||
"unknown operator: STRING - STRING",
|
||||
},
|
||||
{
|
||||
"if (10 > 1) { true + false; }",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return true + false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"foobar",
|
||||
"identifier not found: foobar",
|
||||
},
|
||||
{
|
||||
`{"name": "Monkey"}[fn(x) { x }];`,
|
||||
"unusable as hash key: FUNCTION",
|
||||
},
|
||||
{
|
||||
`999[1]`,
|
||||
"index operator not supported: INTEGER",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("no error object returned. got=%T(%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if errObj.Message != tt.expectedMessage {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
tt.expectedMessage, errObj.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLetStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let a = 5; a;", 5},
|
||||
{"let a = 5 * 5; a;", 25},
|
||||
{"let a = 5; let b = a; b;", 5},
|
||||
{"let a = 5; let b = a; let c = a + b + 5; c;", 15},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionObject(t *testing.T) {
|
||||
input := "fn(x) { x + 2; };"
|
||||
|
||||
evaluated := testEval(input)
|
||||
fn, ok := evaluated.(*object.Function)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(fn.Parameters) != 1 {
|
||||
t.Fatalf("function has wrong parameters. Parameters=%+v",
|
||||
fn.Parameters)
|
||||
}
|
||||
|
||||
if fn.Parameters[0].String() != "x" {
|
||||
t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
|
||||
}
|
||||
|
||||
expectedBody := "(x + 2)"
|
||||
|
||||
if fn.Body.String() != expectedBody {
|
||||
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionApplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let identity = fn(x) { x; }; identity(5);", 5},
|
||||
{"let identity = fn(x) { return x; }; identity(5);", 5},
|
||||
{"let double = fn(x) { x * 2; }; double(5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
|
||||
{"fn(x) { x; }(5)", 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnclosingEnvironments(t *testing.T) {
|
||||
input := `
|
||||
let first = 10;
|
||||
let second = 10;
|
||||
let third = 10;
|
||||
|
||||
let ourFunction = fn(first) {
|
||||
let second = 20;
|
||||
|
||||
first + second + third;
|
||||
};
|
||||
|
||||
ourFunction(20) + first + second;`
|
||||
|
||||
testIntegerObject(t, testEval(input), 70)
|
||||
}
|
||||
|
||||
func TestClosures(t *testing.T) {
|
||||
input := `
|
||||
let newAdder = fn(x) {
|
||||
fn(y) { x + y };
|
||||
};
|
||||
|
||||
let addTwo = newAdder(2);
|
||||
addTwo(2);`
|
||||
|
||||
testIntegerObject(t, testEval(input), 4)
|
||||
}
|
||||
|
||||
func TestStringLiteral(t *testing.T) {
|
||||
input := `"Hello World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConcatenation(t *testing.T) {
|
||||
input := `"Hello" + " " + "World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinFunctions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{`len("")`, 0},
|
||||
{`len("four")`, 4},
|
||||
{`len("hello world")`, 11},
|
||||
{`len(1)`, "argument to `len` not supported, got INTEGER"},
|
||||
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
|
||||
{`len([1, 2, 3])`, 3},
|
||||
{`len([])`, 0},
|
||||
{`puts("hello", "world!")`, nil},
|
||||
{`first([1, 2, 3])`, 1},
|
||||
{`first([])`, nil},
|
||||
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
|
||||
{`last([1, 2, 3])`, 3},
|
||||
{`last([])`, nil},
|
||||
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
|
||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||
{`rest([])`, nil},
|
||||
{`push([], 1)`, []int{1}},
|
||||
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
switch expected := tt.expected.(type) {
|
||||
case int:
|
||||
testIntegerObject(t, evaluated, int64(expected))
|
||||
case nil:
|
||||
testNullObject(t, evaluated)
|
||||
case string:
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("object is not Error. got=%T (%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
if errObj.Message != expected {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
expected, errObj.Message)
|
||||
}
|
||||
case []int:
|
||||
array, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(array.Elements) != len(expected) {
|
||||
t.Errorf("wrong num of elements. want=%d, got=%d",
|
||||
len(expected), len(array.Elements))
|
||||
continue
|
||||
}
|
||||
|
||||
for i, expectedElem := range expected {
|
||||
testIntegerObject(t, array.Elements[i], int64(expectedElem))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayLiterals(t *testing.T) {
|
||||
input := "[1, 2 * 2, 3 + 3]"
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(result.Elements) != 3 {
|
||||
t.Fatalf("array has wrong num of elements. got=%d",
|
||||
len(result.Elements))
|
||||
}
|
||||
|
||||
testIntegerObject(t, result.Elements[0], 1)
|
||||
testIntegerObject(t, result.Elements[1], 4)
|
||||
testIntegerObject(t, result.Elements[2], 6)
|
||||
}
|
||||
|
||||
func TestArrayIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"[1, 2, 3][0]",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][2]",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let i = 0; [1][i];",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1 + 1];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[2];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
|
||||
6,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][3]",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][-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 TestHashLiterals(t *testing.T) {
|
||||
input := `let two = "two";
|
||||
{
|
||||
"one": 10 - 9,
|
||||
two: 1 + 1,
|
||||
"thr" + "ee": 6 / 2,
|
||||
4: 4,
|
||||
true: 5,
|
||||
false: 6
|
||||
}`
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Hash)
|
||||
if !ok {
|
||||
t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
expected := map[object.HashKey]int64{
|
||||
(&object.String{Value: "one"}).HashKey(): 1,
|
||||
(&object.String{Value: "two"}).HashKey(): 2,
|
||||
(&object.String{Value: "three"}).HashKey(): 3,
|
||||
(&object.Integer{Value: 4}).HashKey(): 4,
|
||||
TRUE.HashKey(): 5,
|
||||
FALSE.HashKey(): 6,
|
||||
}
|
||||
|
||||
if len(result.Pairs) != len(expected) {
|
||||
t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
|
||||
}
|
||||
|
||||
for expectedKey, expectedValue := range expected {
|
||||
pair, ok := result.Pairs[expectedKey]
|
||||
if !ok {
|
||||
t.Errorf("no pair for given key in Pairs")
|
||||
}
|
||||
|
||||
testIntegerObject(t, pair.Value, expectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
`{"foo": 5}["foo"]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{"foo": 5}["bar"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`let key = "foo"; {"foo": 5}[key]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{}["foo"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`{5: 5}[5]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{true: 5}[true]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{false: 5}[false]`,
|
||||
5,
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
program := p.ParseProgram()
|
||||
env := object.NewEnvironment()
|
||||
|
||||
return Eval(program, env)
|
||||
}
|
||||
|
||||
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
|
||||
result, ok := obj.(*object.Integer)
|
||||
if !ok {
|
||||
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
|
||||
result, ok := obj.(*object.Boolean)
|
||||
if !ok {
|
||||
t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%t, want=%t",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func testNullObject(t *testing.T, obj object.Object) bool {
|
||||
if obj != NULL {
|
||||
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
1
wcig_code_1_2/00/src/monkey/go.mod
Normal file
1
wcig_code_1_2/00/src/monkey/go.mod
Normal file
|
|
@ -0,0 +1 @@
|
|||
module monkey
|
||||
157
wcig_code_1_2/00/src/monkey/lexer/lexer.go
Normal file
157
wcig_code_1_2/00/src/monkey/lexer/lexer.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package lexer
|
||||
|
||||
import "monkey/token"
|
||||
|
||||
type Lexer struct {
|
||||
input string
|
||||
position int // current position in input (points to current char)
|
||||
readPosition int // current reading position in input (after current char)
|
||||
ch byte // current char under examination
|
||||
}
|
||||
|
||||
func New(input string) *Lexer {
|
||||
l := &Lexer{input: input}
|
||||
l.readChar()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() token.Token {
|
||||
var tok token.Token
|
||||
|
||||
l.skipWhitespace()
|
||||
|
||||
switch l.ch {
|
||||
case '=':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.ASSIGN, l.ch)
|
||||
}
|
||||
case '+':
|
||||
tok = newToken(token.PLUS, l.ch)
|
||||
case '-':
|
||||
tok = newToken(token.MINUS, l.ch)
|
||||
case '!':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.NOT_EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.BANG, l.ch)
|
||||
}
|
||||
case '/':
|
||||
tok = newToken(token.SLASH, l.ch)
|
||||
case '*':
|
||||
tok = newToken(token.ASTERISK, l.ch)
|
||||
case '<':
|
||||
tok = newToken(token.LT, l.ch)
|
||||
case '>':
|
||||
tok = newToken(token.GT, l.ch)
|
||||
case ';':
|
||||
tok = newToken(token.SEMICOLON, l.ch)
|
||||
case ':':
|
||||
tok = newToken(token.COLON, l.ch)
|
||||
case ',':
|
||||
tok = newToken(token.COMMA, l.ch)
|
||||
case '{':
|
||||
tok = newToken(token.LBRACE, l.ch)
|
||||
case '}':
|
||||
tok = newToken(token.RBRACE, l.ch)
|
||||
case '(':
|
||||
tok = newToken(token.LPAREN, l.ch)
|
||||
case ')':
|
||||
tok = newToken(token.RPAREN, l.ch)
|
||||
case '"':
|
||||
tok.Type = token.STRING
|
||||
tok.Literal = l.readString()
|
||||
case '[':
|
||||
tok = newToken(token.LBRACKET, l.ch)
|
||||
case ']':
|
||||
tok = newToken(token.RBRACKET, l.ch)
|
||||
case 0:
|
||||
tok.Literal = ""
|
||||
tok.Type = token.EOF
|
||||
default:
|
||||
if isLetter(l.ch) {
|
||||
tok.Literal = l.readIdentifier()
|
||||
tok.Type = token.LookupIdent(tok.Literal)
|
||||
return tok
|
||||
} else if isDigit(l.ch) {
|
||||
tok.Type = token.INT
|
||||
tok.Literal = l.readNumber()
|
||||
return tok
|
||||
} else {
|
||||
tok = newToken(token.ILLEGAL, l.ch)
|
||||
}
|
||||
}
|
||||
|
||||
l.readChar()
|
||||
return tok
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
|
||||
l.readChar()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readChar() {
|
||||
if l.readPosition >= len(l.input) {
|
||||
l.ch = 0
|
||||
} else {
|
||||
l.ch = l.input[l.readPosition]
|
||||
}
|
||||
l.position = l.readPosition
|
||||
l.readPosition += 1
|
||||
}
|
||||
|
||||
func (l *Lexer) peekChar() byte {
|
||||
if l.readPosition >= len(l.input) {
|
||||
return 0
|
||||
} else {
|
||||
return l.input[l.readPosition]
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readIdentifier() string {
|
||||
position := l.position
|
||||
for isLetter(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readNumber() string {
|
||||
position := l.position
|
||||
for isDigit(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readString() string {
|
||||
position := l.position + 1
|
||||
for {
|
||||
l.readChar()
|
||||
if l.ch == '"' || l.ch == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func isLetter(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
func newToken(tokenType token.TokenType, ch byte) token.Token {
|
||||
return token.Token{Type: tokenType, Literal: string(ch)}
|
||||
}
|
||||
143
wcig_code_1_2/00/src/monkey/lexer/lexer_test.go
Normal file
143
wcig_code_1_2/00/src/monkey/lexer/lexer_test.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package lexer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"monkey/token"
|
||||
)
|
||||
|
||||
func TestNextToken(t *testing.T) {
|
||||
input := `let five = 5;
|
||||
let ten = 10;
|
||||
|
||||
let add = fn(x, y) {
|
||||
x + y;
|
||||
};
|
||||
|
||||
let result = add(five, ten);
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
"foobar"
|
||||
"foo bar"
|
||||
[1, 2];
|
||||
{"foo": "bar"}
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
expectedType token.TokenType
|
||||
expectedLiteral string
|
||||
}{
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "five"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "ten"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "add"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.FUNCTION, "fn"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "x"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "y"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.IDENT, "x"},
|
||||
{token.PLUS, "+"},
|
||||
{token.IDENT, "y"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "result"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.IDENT, "add"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "five"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "ten"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.BANG, "!"},
|
||||
{token.MINUS, "-"},
|
||||
{token.SLASH, "/"},
|
||||
{token.ASTERISK, "*"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.GT, ">"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.IF, "if"},
|
||||
{token.LPAREN, "("},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.TRUE, "true"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.ELSE, "else"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.FALSE, "false"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.INT, "10"},
|
||||
{token.EQ, "=="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "10"},
|
||||
{token.NOT_EQ, "!="},
|
||||
{token.INT, "9"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.STRING, "foobar"},
|
||||
{token.STRING, "foo bar"},
|
||||
{token.LBRACKET, "["},
|
||||
{token.INT, "1"},
|
||||
{token.COMMA, ","},
|
||||
{token.INT, "2"},
|
||||
{token.RBRACKET, "]"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.STRING, "foo"},
|
||||
{token.COLON, ":"},
|
||||
{token.STRING, "bar"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.EOF, ""},
|
||||
}
|
||||
|
||||
l := New(input)
|
||||
|
||||
for i, tt := range tests {
|
||||
tok := l.NextToken()
|
||||
|
||||
if tok.Type != tt.expectedType {
|
||||
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
|
||||
i, tt.expectedType, tok.Type)
|
||||
}
|
||||
|
||||
if tok.Literal != tt.expectedLiteral {
|
||||
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
|
||||
i, tt.expectedLiteral, tok.Literal)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
wcig_code_1_2/00/src/monkey/main.go
Normal file
19
wcig_code_1_2/00/src/monkey/main.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/repl"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func main() {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Hello %s! This is the Monkey programming language!\n",
|
||||
user.Username)
|
||||
fmt.Printf("Feel free to type in commands\n")
|
||||
repl.Start(os.Stdin, os.Stdout)
|
||||
}
|
||||
30
wcig_code_1_2/00/src/monkey/object/environment.go
Normal file
30
wcig_code_1_2/00/src/monkey/object/environment.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package object
|
||||
|
||||
func NewEnclosedEnvironment(outer *Environment) *Environment {
|
||||
env := NewEnvironment()
|
||||
env.outer = outer
|
||||
return env
|
||||
}
|
||||
|
||||
func NewEnvironment() *Environment {
|
||||
s := make(map[string]Object)
|
||||
return &Environment{store: s, outer: nil}
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
store map[string]Object
|
||||
outer *Environment
|
||||
}
|
||||
|
||||
func (e *Environment) Get(name string) (Object, bool) {
|
||||
obj, ok := e.store[name]
|
||||
if !ok && e.outer != nil {
|
||||
obj, ok = e.outer.Get(name)
|
||||
}
|
||||
return obj, ok
|
||||
}
|
||||
|
||||
func (e *Environment) Set(name string, val Object) Object {
|
||||
e.store[name] = val
|
||||
return val
|
||||
}
|
||||
182
wcig_code_1_2/00/src/monkey/object/object.go
Normal file
182
wcig_code_1_2/00/src/monkey/object/object.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"monkey/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BuiltinFunction func(args ...Object) Object
|
||||
|
||||
type ObjectType string
|
||||
|
||||
const (
|
||||
NULL_OBJ = "NULL"
|
||||
ERROR_OBJ = "ERROR"
|
||||
|
||||
INTEGER_OBJ = "INTEGER"
|
||||
BOOLEAN_OBJ = "BOOLEAN"
|
||||
STRING_OBJ = "STRING"
|
||||
|
||||
RETURN_VALUE_OBJ = "RETURN_VALUE"
|
||||
|
||||
FUNCTION_OBJ = "FUNCTION"
|
||||
BUILTIN_OBJ = "BUILTIN"
|
||||
|
||||
ARRAY_OBJ = "ARRAY"
|
||||
HASH_OBJ = "HASH"
|
||||
)
|
||||
|
||||
type HashKey struct {
|
||||
Type ObjectType
|
||||
Value uint64
|
||||
}
|
||||
|
||||
type Hashable interface {
|
||||
HashKey() HashKey
|
||||
}
|
||||
|
||||
type Object interface {
|
||||
Type() ObjectType
|
||||
Inspect() string
|
||||
}
|
||||
|
||||
type Integer struct {
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
|
||||
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
|
||||
func (i *Integer) HashKey() HashKey {
|
||||
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
|
||||
}
|
||||
|
||||
type Boolean struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
|
||||
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
|
||||
func (b *Boolean) HashKey() HashKey {
|
||||
var value uint64
|
||||
|
||||
if b.Value {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
|
||||
return HashKey{Type: b.Type(), Value: value}
|
||||
}
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (n *Null) Type() ObjectType { return NULL_OBJ }
|
||||
func (n *Null) Inspect() string { return "null" }
|
||||
|
||||
type ReturnValue struct {
|
||||
Value Object
|
||||
}
|
||||
|
||||
func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
|
||||
func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
|
||||
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Type() ObjectType { return ERROR_OBJ }
|
||||
func (e *Error) Inspect() string { return "ERROR: " + e.Message }
|
||||
|
||||
type Function struct {
|
||||
Parameters []*ast.Identifier
|
||||
Body *ast.BlockStatement
|
||||
Env *Environment
|
||||
}
|
||||
|
||||
func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
|
||||
func (f *Function) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range f.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString("fn")
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") {\n")
|
||||
out.WriteString(f.Body.String())
|
||||
out.WriteString("\n}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type String struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (s *String) Type() ObjectType { return STRING_OBJ }
|
||||
func (s *String) Inspect() string { return s.Value }
|
||||
func (s *String) HashKey() HashKey {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(s.Value))
|
||||
|
||||
return HashKey{Type: s.Type(), Value: h.Sum64()}
|
||||
}
|
||||
|
||||
type Builtin struct {
|
||||
Fn BuiltinFunction
|
||||
}
|
||||
|
||||
func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
|
||||
func (b *Builtin) Inspect() string { return "builtin function" }
|
||||
|
||||
type Array struct {
|
||||
Elements []Object
|
||||
}
|
||||
|
||||
func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
|
||||
func (ao *Array) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, e := range ao.Elements {
|
||||
elements = append(elements, e.Inspect())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashPair struct {
|
||||
Key Object
|
||||
Value Object
|
||||
}
|
||||
|
||||
type Hash struct {
|
||||
Pairs map[HashKey]HashPair
|
||||
}
|
||||
|
||||
func (h *Hash) Type() ObjectType { return HASH_OBJ }
|
||||
func (h *Hash) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for _, pair := range h.Pairs {
|
||||
pairs = append(pairs, fmt.Sprintf("%s: %s",
|
||||
pair.Key.Inspect(), pair.Value.Inspect()))
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
60
wcig_code_1_2/00/src/monkey/object/object_test.go
Normal file
60
wcig_code_1_2/00/src/monkey/object/object_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package object
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStringHashKey(t *testing.T) {
|
||||
hello1 := &String{Value: "Hello World"}
|
||||
hello2 := &String{Value: "Hello World"}
|
||||
diff1 := &String{Value: "My name is johnny"}
|
||||
diff2 := &String{Value: "My name is johnny"}
|
||||
|
||||
if hello1.HashKey() != hello2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if diff1.HashKey() != diff2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if hello1.HashKey() == diff1.HashKey() {
|
||||
t.Errorf("strings with different content have same hash keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanHashKey(t *testing.T) {
|
||||
true1 := &Boolean{Value: true}
|
||||
true2 := &Boolean{Value: true}
|
||||
false1 := &Boolean{Value: false}
|
||||
false2 := &Boolean{Value: false}
|
||||
|
||||
if true1.HashKey() != true2.HashKey() {
|
||||
t.Errorf("trues do not have same hash key")
|
||||
}
|
||||
|
||||
if false1.HashKey() != false2.HashKey() {
|
||||
t.Errorf("falses do not have same hash key")
|
||||
}
|
||||
|
||||
if true1.HashKey() == false1.HashKey() {
|
||||
t.Errorf("true has same hash key as false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegerHashKey(t *testing.T) {
|
||||
one1 := &Integer{Value: 1}
|
||||
one2 := &Integer{Value: 1}
|
||||
two1 := &Integer{Value: 2}
|
||||
two2 := &Integer{Value: 2}
|
||||
|
||||
if one1.HashKey() != one2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if two1.HashKey() != two2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if one1.HashKey() == two1.HashKey() {
|
||||
t.Errorf("integers with twoerent content have same hash keys")
|
||||
}
|
||||
}
|
||||
491
wcig_code_1_2/00/src/monkey/parser/parser.go
Normal file
491
wcig_code_1_2/00/src/monkey/parser/parser.go
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/lexer"
|
||||
"monkey/token"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
_ int = iota
|
||||
LOWEST
|
||||
EQUALS // ==
|
||||
LESSGREATER // > or <
|
||||
SUM // +
|
||||
PRODUCT // *
|
||||
PREFIX // -X or !X
|
||||
CALL // myFunction(X)
|
||||
INDEX // array[index]
|
||||
)
|
||||
|
||||
var precedences = map[token.TokenType]int{
|
||||
token.EQ: EQUALS,
|
||||
token.NOT_EQ: EQUALS,
|
||||
token.LT: LESSGREATER,
|
||||
token.GT: LESSGREATER,
|
||||
token.PLUS: SUM,
|
||||
token.MINUS: SUM,
|
||||
token.SLASH: PRODUCT,
|
||||
token.ASTERISK: PRODUCT,
|
||||
token.LPAREN: CALL,
|
||||
token.LBRACKET: INDEX,
|
||||
}
|
||||
|
||||
type (
|
||||
prefixParseFn func() ast.Expression
|
||||
infixParseFn func(ast.Expression) ast.Expression
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
l *lexer.Lexer
|
||||
errors []string
|
||||
|
||||
curToken token.Token
|
||||
peekToken token.Token
|
||||
|
||||
prefixParseFns map[token.TokenType]prefixParseFn
|
||||
infixParseFns map[token.TokenType]infixParseFn
|
||||
}
|
||||
|
||||
func New(l *lexer.Lexer) *Parser {
|
||||
p := &Parser{
|
||||
l: l,
|
||||
errors: []string{},
|
||||
}
|
||||
|
||||
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
|
||||
p.registerPrefix(token.IDENT, p.parseIdentifier)
|
||||
p.registerPrefix(token.INT, p.parseIntegerLiteral)
|
||||
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
||||
p.registerPrefix(token.BANG, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.TRUE, p.parseBoolean)
|
||||
p.registerPrefix(token.FALSE, p.parseBoolean)
|
||||
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
||||
p.registerPrefix(token.IF, p.parseIfExpression)
|
||||
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
||||
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
||||
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
|
||||
|
||||
p.infixParseFns = make(map[token.TokenType]infixParseFn)
|
||||
p.registerInfix(token.PLUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.MINUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.SLASH, p.parseInfixExpression)
|
||||
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
|
||||
p.registerInfix(token.EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.LT, p.parseInfixExpression)
|
||||
p.registerInfix(token.GT, p.parseInfixExpression)
|
||||
|
||||
p.registerInfix(token.LPAREN, p.parseCallExpression)
|
||||
p.registerInfix(token.LBRACKET, p.parseIndexExpression)
|
||||
|
||||
// Read two tokens, so curToken and peekToken are both set
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) nextToken() {
|
||||
p.curToken = p.peekToken
|
||||
p.peekToken = p.l.NextToken()
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(t token.TokenType) bool {
|
||||
return p.curToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) peekTokenIs(t token.TokenType) bool {
|
||||
return p.peekToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) expectPeek(t token.TokenType) bool {
|
||||
if p.peekTokenIs(t) {
|
||||
p.nextToken()
|
||||
return true
|
||||
} else {
|
||||
p.peekError(t)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) Errors() []string {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
func (p *Parser) peekError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("expected next token to be %s, got %s instead",
|
||||
t, p.peekToken.Type)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) noPrefixParseFnError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("no prefix parse function for %s found", t)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) ParseProgram() *ast.Program {
|
||||
program := &ast.Program{}
|
||||
program.Statements = []ast.Statement{}
|
||||
|
||||
for !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
program.Statements = append(program.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() ast.Statement {
|
||||
switch p.curToken.Type {
|
||||
case token.LET:
|
||||
return p.parseLetStatement()
|
||||
case token.RETURN:
|
||||
return p.parseReturnStatement()
|
||||
default:
|
||||
return p.parseExpressionStatement()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseLetStatement() *ast.LetStatement {
|
||||
stmt := &ast.LetStatement{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.IDENT) {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
|
||||
if !p.expectPeek(token.ASSIGN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
|
||||
stmt := &ast.ReturnStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.ReturnValue = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
|
||||
stmt := &ast.ExpressionStatement{Token: p.curToken}
|
||||
|
||||
stmt.Expression = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpression(precedence int) ast.Expression {
|
||||
prefix := p.prefixParseFns[p.curToken.Type]
|
||||
if prefix == nil {
|
||||
p.noPrefixParseFnError(p.curToken.Type)
|
||||
return nil
|
||||
}
|
||||
leftExp := prefix()
|
||||
|
||||
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return leftExp
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
leftExp = infix(leftExp)
|
||||
}
|
||||
|
||||
return leftExp
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() int {
|
||||
if p, ok := precedences[p.peekToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() int {
|
||||
if p, ok := precedences[p.curToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) parseIdentifier() ast.Expression {
|
||||
return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parseIntegerLiteral() ast.Expression {
|
||||
lit := &ast.IntegerLiteral{Token: p.curToken}
|
||||
|
||||
value, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
|
||||
p.errors = append(p.errors, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Value = value
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseStringLiteral() ast.Expression {
|
||||
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parsePrefixExpression() ast.Expression {
|
||||
expression := &ast.PrefixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
expression.Right = p.parseExpression(PREFIX)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
|
||||
expression := &ast.InfixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
precedence := p.curPrecedence()
|
||||
p.nextToken()
|
||||
expression.Right = p.parseExpression(precedence)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBoolean() ast.Expression {
|
||||
return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)}
|
||||
}
|
||||
|
||||
func (p *Parser) parseGroupedExpression() ast.Expression {
|
||||
p.nextToken()
|
||||
|
||||
exp := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseIfExpression() ast.Expression {
|
||||
expression := &ast.IfExpression{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()
|
||||
|
||||
if p.peekTokenIs(token.ELSE) {
|
||||
p.nextToken()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
expression.Alternative = p.parseBlockStatement()
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
|
||||
block := &ast.BlockStatement{Token: p.curToken}
|
||||
block.Statements = []ast.Statement{}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
block.Statements = append(block.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionLiteral() ast.Expression {
|
||||
lit := &ast.FunctionLiteral{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.LPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Parameters = p.parseFunctionParameters()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Body = p.parseBlockStatement()
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionParameters() []*ast.Identifier {
|
||||
identifiers := []*ast.Identifier{}
|
||||
|
||||
if p.peekTokenIs(token.RPAREN) {
|
||||
p.nextToken()
|
||||
return identifiers
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
|
||||
exp := &ast.CallExpression{Token: p.curToken, Function: function}
|
||||
exp.Arguments = p.parseExpressionList(token.RPAREN)
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
|
||||
list := []ast.Expression{}
|
||||
|
||||
if p.peekTokenIs(end) {
|
||||
p.nextToken()
|
||||
return list
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
}
|
||||
|
||||
if !p.expectPeek(end) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (p *Parser) parseArrayLiteral() ast.Expression {
|
||||
array := &ast.ArrayLiteral{Token: p.curToken}
|
||||
|
||||
array.Elements = p.parseExpressionList(token.RBRACKET)
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
|
||||
exp := &ast.IndexExpression{Token: p.curToken, Left: left}
|
||||
|
||||
p.nextToken()
|
||||
exp.Index = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RBRACKET) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseHashLiteral() ast.Expression {
|
||||
hash := &ast.HashLiteral{Token: p.curToken}
|
||||
hash.Pairs = make(map[ast.Expression]ast.Expression)
|
||||
|
||||
for !p.peekTokenIs(token.RBRACE) {
|
||||
p.nextToken()
|
||||
key := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.COLON) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
value := p.parseExpression(LOWEST)
|
||||
|
||||
hash.Pairs[key] = value
|
||||
|
||||
if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
|
||||
p.prefixParseFns[tokenType] = fn
|
||||
}
|
||||
|
||||
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
|
||||
p.infixParseFns[tokenType] = fn
|
||||
}
|
||||
1083
wcig_code_1_2/00/src/monkey/parser/parser_test.go
Normal file
1083
wcig_code_1_2/00/src/monkey/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load diff
32
wcig_code_1_2/00/src/monkey/parser/parser_tracing.go
Normal file
32
wcig_code_1_2/00/src/monkey/parser/parser_tracing.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var traceLevel int = 0
|
||||
|
||||
const traceIdentPlaceholder string = "\t"
|
||||
|
||||
func identLevel() string {
|
||||
return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
|
||||
}
|
||||
|
||||
func tracePrint(fs string) {
|
||||
fmt.Printf("%s%s\n", identLevel(), fs)
|
||||
}
|
||||
|
||||
func incIdent() { traceLevel = traceLevel + 1 }
|
||||
func decIdent() { traceLevel = traceLevel - 1 }
|
||||
|
||||
func trace(msg string) string {
|
||||
incIdent()
|
||||
tracePrint("BEGIN " + msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func untrace(msg string) {
|
||||
tracePrint("END " + msg)
|
||||
decIdent()
|
||||
}
|
||||
64
wcig_code_1_2/00/src/monkey/repl/repl.go
Normal file
64
wcig_code_1_2/00/src/monkey/repl/repl.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package repl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"monkey/evaluator"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
)
|
||||
|
||||
const PROMPT = ">> "
|
||||
|
||||
func Start(in io.Reader, out io.Writer) {
|
||||
scanner := bufio.NewScanner(in)
|
||||
env := object.NewEnvironment()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
evaluated := evaluator.Eval(program, env)
|
||||
if evaluated != nil {
|
||||
io.WriteString(out, evaluated.Inspect())
|
||||
io.WriteString(out, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MONKEY_FACE = ` __,__
|
||||
.--. .-" "-. .--.
|
||||
/ .. \/ .-. .-. \/ .. \
|
||||
| | '| / Y \ |' | |
|
||||
| \ \ \ 0 | 0 / / / |
|
||||
\ '- ,\.-"""""""-./, -' /
|
||||
''-' /_ ^ ^ _\ '-''
|
||||
| \._ _./ |
|
||||
\ \ '~' / /
|
||||
'._ '-=-' _.'
|
||||
'-----'
|
||||
`
|
||||
|
||||
func printParserErrors(out io.Writer, errors []string) {
|
||||
io.WriteString(out, MONKEY_FACE)
|
||||
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
||||
io.WriteString(out, " parser errors:\n")
|
||||
for _, msg := range errors {
|
||||
io.WriteString(out, "\t"+msg+"\n")
|
||||
}
|
||||
}
|
||||
70
wcig_code_1_2/00/src/monkey/token/token.go
Normal file
70
wcig_code_1_2/00/src/monkey/token/token.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package token
|
||||
|
||||
type TokenType string
|
||||
|
||||
const (
|
||||
ILLEGAL = "ILLEGAL"
|
||||
EOF = "EOF"
|
||||
|
||||
// Identifiers + literals
|
||||
IDENT = "IDENT" // add, foobar, x, y, ...
|
||||
INT = "INT" // 1343456
|
||||
STRING = "STRING" // "foobar"
|
||||
|
||||
// Operators
|
||||
ASSIGN = "="
|
||||
PLUS = "+"
|
||||
MINUS = "-"
|
||||
BANG = "!"
|
||||
ASTERISK = "*"
|
||||
SLASH = "/"
|
||||
|
||||
LT = "<"
|
||||
GT = ">"
|
||||
|
||||
EQ = "=="
|
||||
NOT_EQ = "!="
|
||||
|
||||
// Delimiters
|
||||
COMMA = ","
|
||||
SEMICOLON = ";"
|
||||
COLON = ":"
|
||||
|
||||
LPAREN = "("
|
||||
RPAREN = ")"
|
||||
LBRACE = "{"
|
||||
RBRACE = "}"
|
||||
LBRACKET = "["
|
||||
RBRACKET = "]"
|
||||
|
||||
// Keywords
|
||||
FUNCTION = "FUNCTION"
|
||||
LET = "LET"
|
||||
TRUE = "TRUE"
|
||||
FALSE = "FALSE"
|
||||
IF = "IF"
|
||||
ELSE = "ELSE"
|
||||
RETURN = "RETURN"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Literal string
|
||||
}
|
||||
|
||||
var keywords = map[string]TokenType{
|
||||
"fn": FUNCTION,
|
||||
"let": LET,
|
||||
"true": TRUE,
|
||||
"false": FALSE,
|
||||
"if": IF,
|
||||
"else": ELSE,
|
||||
"return": RETURN,
|
||||
}
|
||||
|
||||
func LookupIdent(ident string) TokenType {
|
||||
if tok, ok := keywords[ident]; ok {
|
||||
return tok
|
||||
}
|
||||
return IDENT
|
||||
}
|
||||
1
wcig_code_1_2/02/.envrc
Normal file
1
wcig_code_1_2/02/.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
export GOPATH=$(pwd)
|
||||
339
wcig_code_1_2/02/src/monkey/ast/ast.go
Normal file
339
wcig_code_1_2/02/src/monkey/ast/ast.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"monkey/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The base Node interface
|
||||
type Node interface {
|
||||
TokenLiteral() string
|
||||
String() string
|
||||
}
|
||||
|
||||
// All statement nodes implement this
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
// All expression nodes implement this
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (p *Program) TokenLiteral() string {
|
||||
if len(p.Statements) > 0 {
|
||||
return p.Statements[0].TokenLiteral()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range p.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Statements
|
||||
type LetStatement struct {
|
||||
Token token.Token // the token.LET token
|
||||
Name *Identifier
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (ls *LetStatement) statementNode() {}
|
||||
func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
|
||||
func (ls *LetStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(ls.TokenLiteral() + " ")
|
||||
out.WriteString(ls.Name.String())
|
||||
out.WriteString(" = ")
|
||||
|
||||
if ls.Value != nil {
|
||||
out.WriteString(ls.Value.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ReturnStatement struct {
|
||||
Token token.Token // the 'return' token
|
||||
ReturnValue Expression
|
||||
}
|
||||
|
||||
func (rs *ReturnStatement) statementNode() {}
|
||||
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
|
||||
func (rs *ReturnStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(rs.TokenLiteral() + " ")
|
||||
|
||||
if rs.ReturnValue != nil {
|
||||
out.WriteString(rs.ReturnValue.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ExpressionStatement struct {
|
||||
Token token.Token // the first token of the expression
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (es *ExpressionStatement) statementNode() {}
|
||||
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
|
||||
func (es *ExpressionStatement) String() string {
|
||||
if es.Expression != nil {
|
||||
return es.Expression.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BlockStatement struct {
|
||||
Token token.Token // the { token
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (bs *BlockStatement) statementNode() {}
|
||||
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
|
||||
func (bs *BlockStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range bs.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Expressions
|
||||
type Identifier struct {
|
||||
Token token.Token // the token.IDENT token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (i *Identifier) expressionNode() {}
|
||||
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
|
||||
func (i *Identifier) String() string { return i.Value }
|
||||
|
||||
type Boolean struct {
|
||||
Token token.Token
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) expressionNode() {}
|
||||
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
|
||||
func (b *Boolean) String() string { return b.Token.Literal }
|
||||
|
||||
type IntegerLiteral struct {
|
||||
Token token.Token
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (il *IntegerLiteral) expressionNode() {}
|
||||
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
|
||||
func (il *IntegerLiteral) String() string { return il.Token.Literal }
|
||||
|
||||
type PrefixExpression struct {
|
||||
Token token.Token // The prefix token, e.g. !
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
|
||||
func (pe *PrefixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(pe.Operator)
|
||||
out.WriteString(pe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type InfixExpression struct {
|
||||
Token token.Token // The operator token, e.g. +
|
||||
Left Expression
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (oe *InfixExpression) expressionNode() {}
|
||||
func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
|
||||
func (oe *InfixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(oe.Left.String())
|
||||
out.WriteString(" " + oe.Operator + " ")
|
||||
out.WriteString(oe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IfExpression struct {
|
||||
Token token.Token // The 'if' token
|
||||
Condition Expression
|
||||
Consequence *BlockStatement
|
||||
Alternative *BlockStatement
|
||||
}
|
||||
|
||||
func (ie *IfExpression) expressionNode() {}
|
||||
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IfExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("if")
|
||||
out.WriteString(ie.Condition.String())
|
||||
out.WriteString(" ")
|
||||
out.WriteString(ie.Consequence.String())
|
||||
|
||||
if ie.Alternative != nil {
|
||||
out.WriteString("else ")
|
||||
out.WriteString(ie.Alternative.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type FunctionLiteral struct {
|
||||
Token token.Token // The 'fn' token
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fl *FunctionLiteral) expressionNode() {}
|
||||
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
|
||||
func (fl *FunctionLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range fl.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString(fl.TokenLiteral())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") ")
|
||||
out.WriteString(fl.Body.String())
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type CallExpression struct {
|
||||
Token token.Token // The '(' token
|
||||
Function Expression // Identifier or FunctionLiteral
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (ce *CallExpression) expressionNode() {}
|
||||
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
|
||||
func (ce *CallExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
args := []string{}
|
||||
for _, a := range ce.Arguments {
|
||||
args = append(args, a.String())
|
||||
}
|
||||
|
||||
out.WriteString(ce.Function.String())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(args, ", "))
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type StringLiteral struct {
|
||||
Token token.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) expressionNode() {}
|
||||
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
|
||||
func (sl *StringLiteral) String() string { return sl.Token.Literal }
|
||||
|
||||
type ArrayLiteral struct {
|
||||
Token token.Token // the '[' token
|
||||
Elements []Expression
|
||||
}
|
||||
|
||||
func (al *ArrayLiteral) expressionNode() {}
|
||||
func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal }
|
||||
func (al *ArrayLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, el := range al.Elements {
|
||||
elements = append(elements, el.String())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IndexExpression struct {
|
||||
Token token.Token // The [ token
|
||||
Left Expression
|
||||
Index Expression
|
||||
}
|
||||
|
||||
func (ie *IndexExpression) expressionNode() {}
|
||||
func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IndexExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(ie.Left.String())
|
||||
out.WriteString("[")
|
||||
out.WriteString(ie.Index.String())
|
||||
out.WriteString("])")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashLiteral struct {
|
||||
Token token.Token // the '{' token
|
||||
Pairs map[Expression]Expression
|
||||
}
|
||||
|
||||
func (hl *HashLiteral) expressionNode() {}
|
||||
func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal }
|
||||
func (hl *HashLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for key, value := range hl.Pairs {
|
||||
pairs = append(pairs, key.String()+":"+value.String())
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
28
wcig_code_1_2/02/src/monkey/ast/ast_test.go
Normal file
28
wcig_code_1_2/02/src/monkey/ast/ast_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"monkey/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
program := &Program{
|
||||
Statements: []Statement{
|
||||
&LetStatement{
|
||||
Token: token.Token{Type: token.LET, Literal: "let"},
|
||||
Name: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
|
||||
Value: "myVar",
|
||||
},
|
||||
Value: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
|
||||
Value: "anotherVar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if program.String() != "let myVar = anotherVar;" {
|
||||
t.Errorf("program.String() wrong. got=%q", program.String())
|
||||
}
|
||||
}
|
||||
121
wcig_code_1_2/02/src/monkey/code/code.go
Normal file
121
wcig_code_1_2/02/src/monkey/code/code.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
package code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Instructions []byte
|
||||
|
||||
func (ins Instructions) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
i := 0
|
||||
for i < len(ins) {
|
||||
def, err := Lookup(ins[i])
|
||||
if err != nil {
|
||||
fmt.Fprintf(&out, "ERROR: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
operands, read := ReadOperands(def, ins[i+1:])
|
||||
|
||||
fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
|
||||
|
||||
i += 1 + read
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
|
||||
operandCount := len(def.OperandWidths)
|
||||
|
||||
if len(operands) != operandCount {
|
||||
return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n",
|
||||
len(operands), operandCount)
|
||||
}
|
||||
|
||||
switch operandCount {
|
||||
case 0:
|
||||
return def.Name
|
||||
case 1:
|
||||
return fmt.Sprintf("%s %d", def.Name, operands[0])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
|
||||
}
|
||||
|
||||
type Opcode byte
|
||||
|
||||
const (
|
||||
OpConstant Opcode = iota
|
||||
OpAdd
|
||||
)
|
||||
|
||||
type Definition struct {
|
||||
Name string
|
||||
OperandWidths []int
|
||||
}
|
||||
|
||||
var definitions = map[Opcode]*Definition{
|
||||
OpConstant: {"OpConstant", []int{2}},
|
||||
OpAdd: {"OpAdd", []int{}},
|
||||
}
|
||||
|
||||
func Lookup(op byte) (*Definition, error) {
|
||||
def, ok := definitions[Opcode(op)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("opcode %d undefined", op)
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func Make(op Opcode, operands ...int) []byte {
|
||||
def, ok := definitions[op]
|
||||
if !ok {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
instructionLen := 1
|
||||
for _, w := range def.OperandWidths {
|
||||
instructionLen += w
|
||||
}
|
||||
|
||||
instruction := make([]byte, instructionLen)
|
||||
instruction[0] = byte(op)
|
||||
|
||||
offset := 1
|
||||
for i, o := range operands {
|
||||
width := def.OperandWidths[i]
|
||||
switch width {
|
||||
case 2:
|
||||
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
|
||||
}
|
||||
offset += width
|
||||
}
|
||||
|
||||
return instruction
|
||||
}
|
||||
|
||||
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
|
||||
operands := make([]int, len(def.OperandWidths))
|
||||
offset := 0
|
||||
|
||||
for i, width := range def.OperandWidths {
|
||||
switch width {
|
||||
case 2:
|
||||
operands[i] = int(ReadUint16(ins[offset:]))
|
||||
}
|
||||
|
||||
offset += width
|
||||
}
|
||||
|
||||
return operands, offset
|
||||
}
|
||||
|
||||
func ReadUint16(ins Instructions) uint16 {
|
||||
return binary.BigEndian.Uint16(ins)
|
||||
}
|
||||
83
wcig_code_1_2/02/src/monkey/code/code_test.go
Normal file
83
wcig_code_1_2/02/src/monkey/code/code_test.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package code
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMake(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
expected []byte
|
||||
}{
|
||||
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
|
||||
{OpAdd, []int{}, []byte{byte(OpAdd)}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
if len(instruction) != len(tt.expected) {
|
||||
t.Errorf("instruction has wrong length. want=%d, got=%d",
|
||||
len(tt.expected), len(instruction))
|
||||
}
|
||||
|
||||
for i, b := range tt.expected {
|
||||
if instruction[i] != tt.expected[i] {
|
||||
t.Errorf("wrong byte at pos %d. want=%d, got=%d",
|
||||
i, b, instruction[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionsString(t *testing.T) {
|
||||
instructions := []Instructions{
|
||||
Make(OpAdd),
|
||||
Make(OpConstant, 2),
|
||||
Make(OpConstant, 65535),
|
||||
}
|
||||
|
||||
expected := `0000 OpAdd
|
||||
0001 OpConstant 2
|
||||
0004 OpConstant 65535
|
||||
`
|
||||
|
||||
concatted := Instructions{}
|
||||
for _, ins := range instructions {
|
||||
concatted = append(concatted, ins...)
|
||||
}
|
||||
|
||||
if concatted.String() != expected {
|
||||
t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q",
|
||||
expected, concatted.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadOperands(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
bytesRead int
|
||||
}{
|
||||
{OpConstant, []int{65535}, 2},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
def, err := Lookup(byte(tt.op))
|
||||
if err != nil {
|
||||
t.Fatalf("definition not found: %q\n", err)
|
||||
}
|
||||
|
||||
operandsRead, n := ReadOperands(def, instruction[1:])
|
||||
if n != tt.bytesRead {
|
||||
t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
|
||||
}
|
||||
|
||||
for i, want := range tt.operands {
|
||||
if operandsRead[i] != want {
|
||||
t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
wcig_code_1_2/02/src/monkey/compiler/compiler.go
Normal file
92
wcig_code_1_2/02/src/monkey/compiler/compiler.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
instructions code.Instructions
|
||||
constants []object.Object
|
||||
}
|
||||
|
||||
func New() *Compiler {
|
||||
return &Compiler{
|
||||
instructions: code.Instructions{},
|
||||
constants: []object.Object{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) Compile(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Program:
|
||||
for _, s := range node.Statements {
|
||||
err := c.Compile(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
err := c.Compile(node.Expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *ast.InfixExpression:
|
||||
err := c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "+":
|
||||
c.emit(code.OpAdd)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
|
||||
case *ast.IntegerLiteral:
|
||||
integer := &object.Integer{Value: node.Value}
|
||||
c.emit(code.OpConstant, c.addConstant(integer))
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) Bytecode() *Bytecode {
|
||||
return &Bytecode{
|
||||
Instructions: c.instructions,
|
||||
Constants: c.constants,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addConstant(obj object.Object) int {
|
||||
c.constants = append(c.constants, obj)
|
||||
return len(c.constants) - 1
|
||||
}
|
||||
|
||||
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
|
||||
ins := code.Make(op, operands...)
|
||||
pos := c.addInstruction(ins)
|
||||
return pos
|
||||
}
|
||||
|
||||
func (c *Compiler) addInstruction(ins []byte) int {
|
||||
posNewInstruction := len(c.instructions)
|
||||
c.instructions = append(c.instructions, ins...)
|
||||
return posNewInstruction
|
||||
}
|
||||
|
||||
type Bytecode struct {
|
||||
Instructions code.Instructions
|
||||
Constants []object.Object
|
||||
}
|
||||
135
wcig_code_1_2/02/src/monkey/compiler/compiler_test.go
Normal file
135
wcig_code_1_2/02/src/monkey/compiler/compiler_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "1 + 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpAdd),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
type compilerTestCase struct {
|
||||
input string
|
||||
expectedConstants []interface{}
|
||||
expectedInstructions []code.Instructions
|
||||
}
|
||||
|
||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
compiler := New()
|
||||
err := compiler.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
bytecode := compiler.Bytecode()
|
||||
|
||||
err = testInstructions(tt.expectedInstructions, bytecode.Instructions)
|
||||
if err != nil {
|
||||
t.Fatalf("testInstructions failed: %s", err)
|
||||
}
|
||||
|
||||
err = testConstants(t, tt.expectedConstants, bytecode.Constants)
|
||||
if err != nil {
|
||||
t.Fatalf("testConstants failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testInstructions(
|
||||
expected []code.Instructions,
|
||||
actual code.Instructions,
|
||||
) error {
|
||||
concatted := concatInstructions(expected)
|
||||
|
||||
if len(actual) != len(concatted) {
|
||||
return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q",
|
||||
concatted, actual)
|
||||
}
|
||||
|
||||
for i, ins := range concatted {
|
||||
if actual[i] != ins {
|
||||
return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q",
|
||||
i, concatted, actual)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func concatInstructions(s []code.Instructions) code.Instructions {
|
||||
out := code.Instructions{}
|
||||
|
||||
for _, ins := range s {
|
||||
out = append(out, ins...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func testConstants(
|
||||
t *testing.T,
|
||||
expected []interface{},
|
||||
actual []object.Object,
|
||||
) error {
|
||||
if len(expected) != len(actual) {
|
||||
return fmt.Errorf("wrong number of constants. got=%d, want=%d",
|
||||
len(actual), len(expected))
|
||||
}
|
||||
|
||||
for i, constant := range expected {
|
||||
switch constant := constant.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(constant), actual[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("constant %d - testIntegerObject failed: %s",
|
||||
i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
wcig_code_1_2/02/src/monkey/evaluator/builtins.go
Normal file
117
wcig_code_1_2/02/src/monkey/evaluator/builtins.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"len": &object.Builtin{Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
|
||||
switch arg := args[0].(type) {
|
||||
case *object.Array:
|
||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
||||
case *object.String:
|
||||
return &object.Integer{Value: int64(len(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
},
|
||||
},
|
||||
"puts": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"first": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"last": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"rest": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]object.Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &object.Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"push": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]object.Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &object.Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
}
|
||||
442
wcig_code_1_2/02/src/monkey/evaluator/evaluator.go
Normal file
442
wcig_code_1_2/02/src/monkey/evaluator/evaluator.go
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var (
|
||||
NULL = &object.Null{}
|
||||
TRUE = &object.Boolean{Value: true}
|
||||
FALSE = &object.Boolean{Value: false}
|
||||
)
|
||||
|
||||
func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
switch node := node.(type) {
|
||||
|
||||
// Statements
|
||||
case *ast.Program:
|
||||
return evalProgram(node, env)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
return evalBlockStatement(node, env)
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
return Eval(node.Expression, env)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
val := Eval(node.ReturnValue, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
return &object.ReturnValue{Value: val}
|
||||
|
||||
case *ast.LetStatement:
|
||||
val := Eval(node.Value, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
env.Set(node.Name.Value, val)
|
||||
|
||||
// Expressions
|
||||
case *ast.IntegerLiteral:
|
||||
return &object.Integer{Value: node.Value}
|
||||
|
||||
case *ast.StringLiteral:
|
||||
return &object.String{Value: node.Value}
|
||||
|
||||
case *ast.Boolean:
|
||||
return nativeBoolToBooleanObject(node.Value)
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
return evalPrefixExpression(node.Operator, right)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
|
||||
return evalInfixExpression(node.Operator, left, right)
|
||||
|
||||
case *ast.IfExpression:
|
||||
return evalIfExpression(node, env)
|
||||
|
||||
case *ast.Identifier:
|
||||
return evalIdentifier(node, env)
|
||||
|
||||
case *ast.FunctionLiteral:
|
||||
params := node.Parameters
|
||||
body := node.Body
|
||||
return &object.Function{Parameters: params, Env: env, Body: body}
|
||||
|
||||
case *ast.CallExpression:
|
||||
function := Eval(node.Function, env)
|
||||
if isError(function) {
|
||||
return function
|
||||
}
|
||||
|
||||
args := evalExpressions(node.Arguments, env)
|
||||
if len(args) == 1 && isError(args[0]) {
|
||||
return args[0]
|
||||
}
|
||||
|
||||
return applyFunction(function, args)
|
||||
|
||||
case *ast.ArrayLiteral:
|
||||
elements := evalExpressions(node.Elements, env)
|
||||
if len(elements) == 1 && isError(elements[0]) {
|
||||
return elements[0]
|
||||
}
|
||||
return &object.Array{Elements: elements}
|
||||
|
||||
case *ast.IndexExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
index := Eval(node.Index, env)
|
||||
if isError(index) {
|
||||
return index
|
||||
}
|
||||
return evalIndexExpression(left, index)
|
||||
|
||||
case *ast.HashLiteral:
|
||||
return evalHashLiteral(node, env)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range program.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
switch result := result.(type) {
|
||||
case *object.ReturnValue:
|
||||
return result.Value
|
||||
case *object.Error:
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func evalBlockStatement(
|
||||
block *ast.BlockStatement,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range block.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
if result != nil {
|
||||
rt := result.Type()
|
||||
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return TRUE
|
||||
}
|
||||
return FALSE
|
||||
}
|
||||
|
||||
func evalPrefixExpression(operator string, right object.Object) object.Object {
|
||||
switch operator {
|
||||
case "!":
|
||||
return evalBangOperatorExpression(right)
|
||||
case "-":
|
||||
return evalMinusPrefixOperatorExpression(right)
|
||||
default:
|
||||
return newError("unknown operator: %s%s", operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
|
||||
return evalIntegerInfixExpression(operator, left, right)
|
||||
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
|
||||
return evalStringInfixExpression(operator, left, right)
|
||||
case operator == "==":
|
||||
return nativeBoolToBooleanObject(left == right)
|
||||
case operator == "!=":
|
||||
return nativeBoolToBooleanObject(left != right)
|
||||
case left.Type() != right.Type():
|
||||
return newError("type mismatch: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalBangOperatorExpression(right object.Object) object.Object {
|
||||
switch right {
|
||||
case TRUE:
|
||||
return FALSE
|
||||
case FALSE:
|
||||
return TRUE
|
||||
case NULL:
|
||||
return TRUE
|
||||
default:
|
||||
return FALSE
|
||||
}
|
||||
}
|
||||
|
||||
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
|
||||
if right.Type() != object.INTEGER_OBJ {
|
||||
return newError("unknown operator: -%s", right.Type())
|
||||
}
|
||||
|
||||
value := right.(*object.Integer).Value
|
||||
return &object.Integer{Value: -value}
|
||||
}
|
||||
|
||||
func evalIntegerInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
leftVal := left.(*object.Integer).Value
|
||||
rightVal := right.(*object.Integer).Value
|
||||
|
||||
switch operator {
|
||||
case "+":
|
||||
return &object.Integer{Value: leftVal + rightVal}
|
||||
case "-":
|
||||
return &object.Integer{Value: leftVal - rightVal}
|
||||
case "*":
|
||||
return &object.Integer{Value: leftVal * rightVal}
|
||||
case "/":
|
||||
return &object.Integer{Value: leftVal / rightVal}
|
||||
case "<":
|
||||
return nativeBoolToBooleanObject(leftVal < rightVal)
|
||||
case ">":
|
||||
return nativeBoolToBooleanObject(leftVal > rightVal)
|
||||
case "==":
|
||||
return nativeBoolToBooleanObject(leftVal == rightVal)
|
||||
case "!=":
|
||||
return nativeBoolToBooleanObject(leftVal != rightVal)
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalStringInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
if operator != "+" {
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
|
||||
leftVal := left.(*object.String).Value
|
||||
rightVal := right.(*object.String).Value
|
||||
return &object.String{Value: leftVal + rightVal}
|
||||
}
|
||||
|
||||
func evalIfExpression(
|
||||
ie *ast.IfExpression,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
condition := Eval(ie.Condition, env)
|
||||
if isError(condition) {
|
||||
return condition
|
||||
}
|
||||
|
||||
if isTruthy(condition) {
|
||||
return Eval(ie.Consequence, env)
|
||||
} else if ie.Alternative != nil {
|
||||
return Eval(ie.Alternative, env)
|
||||
} else {
|
||||
return NULL
|
||||
}
|
||||
}
|
||||
|
||||
func evalIdentifier(
|
||||
node *ast.Identifier,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
if val, ok := env.Get(node.Value); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
if builtin, ok := builtins[node.Value]; ok {
|
||||
return builtin
|
||||
}
|
||||
|
||||
return newError("identifier not found: " + node.Value)
|
||||
}
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj {
|
||||
case NULL:
|
||||
return false
|
||||
case TRUE:
|
||||
return true
|
||||
case FALSE:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func newError(format string, a ...interface{}) *object.Error {
|
||||
return &object.Error{Message: fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
func isError(obj object.Object) bool {
|
||||
if obj != nil {
|
||||
return obj.Type() == object.ERROR_OBJ
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func evalExpressions(
|
||||
exps []ast.Expression,
|
||||
env *object.Environment,
|
||||
) []object.Object {
|
||||
var result []object.Object
|
||||
|
||||
for _, e := range exps {
|
||||
evaluated := Eval(e, env)
|
||||
if isError(evaluated) {
|
||||
return []object.Object{evaluated}
|
||||
}
|
||||
result = append(result, evaluated)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func applyFunction(fn object.Object, args []object.Object) object.Object {
|
||||
switch fn := fn.(type) {
|
||||
|
||||
case *object.Function:
|
||||
extendedEnv := extendFunctionEnv(fn, args)
|
||||
evaluated := Eval(fn.Body, extendedEnv)
|
||||
return unwrapReturnValue(evaluated)
|
||||
|
||||
case *object.Builtin:
|
||||
return fn.Fn(args...)
|
||||
|
||||
default:
|
||||
return newError("not a function: %s", fn.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func extendFunctionEnv(
|
||||
fn *object.Function,
|
||||
args []object.Object,
|
||||
) *object.Environment {
|
||||
env := object.NewEnclosedEnvironment(fn.Env)
|
||||
|
||||
for paramIdx, param := range fn.Parameters {
|
||||
env.Set(param.Value, args[paramIdx])
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func unwrapReturnValue(obj object.Object) object.Object {
|
||||
if returnValue, ok := obj.(*object.ReturnValue); ok {
|
||||
return returnValue.Value
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func evalIndexExpression(left, index object.Object) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return evalArrayIndexExpression(left, index)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return evalHashIndexExpression(left, index)
|
||||
default:
|
||||
return newError("index operator not supported: %s", left.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalArrayIndexExpression(array, index object.Object) object.Object {
|
||||
arrayObject := array.(*object.Array)
|
||||
idx := index.(*object.Integer).Value
|
||||
max := int64(len(arrayObject.Elements) - 1)
|
||||
|
||||
if idx < 0 || idx > max {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return arrayObject.Elements[idx]
|
||||
}
|
||||
|
||||
func evalHashLiteral(
|
||||
node *ast.HashLiteral,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
|
||||
for keyNode, valueNode := range node.Pairs {
|
||||
key := Eval(keyNode, env)
|
||||
if isError(key) {
|
||||
return key
|
||||
}
|
||||
|
||||
hashKey, ok := key.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", key.Type())
|
||||
}
|
||||
|
||||
value := Eval(valueNode, env)
|
||||
if isError(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
hashed := hashKey.HashKey()
|
||||
pairs[hashed] = object.HashPair{Key: key, Value: value}
|
||||
}
|
||||
|
||||
return &object.Hash{Pairs: pairs}
|
||||
}
|
||||
|
||||
func evalHashIndexExpression(hash, index object.Object) object.Object {
|
||||
hashObject := hash.(*object.Hash)
|
||||
|
||||
key, ok := index.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
|
||||
pair, ok := hashObject.Pairs[key.HashKey()]
|
||||
if !ok {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return pair.Value
|
||||
}
|
||||
629
wcig_code_1_2/02/src/monkey/evaluator/evaluator_test.go
Normal file
629
wcig_code_1_2/02/src/monkey/evaluator/evaluator_test.go
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvalIntegerExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"5", 5},
|
||||
{"10", 10},
|
||||
{"-5", -5},
|
||||
{"-10", -10},
|
||||
{"5 + 5 + 5 + 5 - 10", 10},
|
||||
{"2 * 2 * 2 * 2 * 2", 32},
|
||||
{"-50 + 100 + -50", 0},
|
||||
{"5 * 2 + 10", 20},
|
||||
{"5 + 2 * 10", 25},
|
||||
{"20 + 2 * -10", 0},
|
||||
{"50 / 2 * 2 + 10", 60},
|
||||
{"2 * (5 + 10)", 30},
|
||||
{"3 * 3 * 3 + 10", 37},
|
||||
{"3 * (3 * 3) + 10", 37},
|
||||
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalBooleanExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1 < 2", true},
|
||||
{"1 > 2", false},
|
||||
{"1 < 1", false},
|
||||
{"1 > 1", false},
|
||||
{"1 == 1", true},
|
||||
{"1 != 1", false},
|
||||
{"1 == 2", false},
|
||||
{"1 != 2", true},
|
||||
{"true == true", true},
|
||||
{"false == false", true},
|
||||
{"true == false", false},
|
||||
{"true != false", true},
|
||||
{"false != true", true},
|
||||
{"(1 < 2) == true", true},
|
||||
{"(1 < 2) == false", false},
|
||||
{"(1 > 2) == true", false},
|
||||
{"(1 > 2) == false", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBangOperator(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"!true", false},
|
||||
{"!false", true},
|
||||
{"!5", false},
|
||||
{"!!true", true},
|
||||
{"!!false", false},
|
||||
{"!!5", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfElseExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"if (true) { 10 }", 10},
|
||||
{"if (false) { 10 }", nil},
|
||||
{"if (1) { 10 }", 10},
|
||||
{"if (1 < 2) { 10 }", 10},
|
||||
{"if (1 > 2) { 10 }", nil},
|
||||
{"if (1 > 2) { 10 } else { 20 }", 20},
|
||||
{"if (1 < 2) { 10 } else { 20 }", 10},
|
||||
}
|
||||
|
||||
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 TestReturnStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"return 10;", 10},
|
||||
{"return 10; 9;", 10},
|
||||
{"return 2 * 5; 9;", 10},
|
||||
{"9; return 2 * 5; 9;", 10},
|
||||
{"if (10 > 1) { return 10; }", 10},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
return x;
|
||||
x + 10;
|
||||
};
|
||||
f(10);`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
let result = x + 10;
|
||||
return result;
|
||||
return 10;
|
||||
};
|
||||
f(10);`,
|
||||
20,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedMessage string
|
||||
}{
|
||||
{
|
||||
"5 + true;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5 + true; 5;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"-true",
|
||||
"unknown operator: -BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false + true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5; true + false; 5",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`"Hello" - "World"`,
|
||||
"unknown operator: STRING - STRING",
|
||||
},
|
||||
{
|
||||
"if (10 > 1) { true + false; }",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return true + false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"foobar",
|
||||
"identifier not found: foobar",
|
||||
},
|
||||
{
|
||||
`{"name": "Monkey"}[fn(x) { x }];`,
|
||||
"unusable as hash key: FUNCTION",
|
||||
},
|
||||
{
|
||||
`999[1]`,
|
||||
"index operator not supported: INTEGER",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("no error object returned. got=%T(%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if errObj.Message != tt.expectedMessage {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
tt.expectedMessage, errObj.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLetStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let a = 5; a;", 5},
|
||||
{"let a = 5 * 5; a;", 25},
|
||||
{"let a = 5; let b = a; b;", 5},
|
||||
{"let a = 5; let b = a; let c = a + b + 5; c;", 15},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionObject(t *testing.T) {
|
||||
input := "fn(x) { x + 2; };"
|
||||
|
||||
evaluated := testEval(input)
|
||||
fn, ok := evaluated.(*object.Function)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(fn.Parameters) != 1 {
|
||||
t.Fatalf("function has wrong parameters. Parameters=%+v",
|
||||
fn.Parameters)
|
||||
}
|
||||
|
||||
if fn.Parameters[0].String() != "x" {
|
||||
t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
|
||||
}
|
||||
|
||||
expectedBody := "(x + 2)"
|
||||
|
||||
if fn.Body.String() != expectedBody {
|
||||
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionApplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let identity = fn(x) { x; }; identity(5);", 5},
|
||||
{"let identity = fn(x) { return x; }; identity(5);", 5},
|
||||
{"let double = fn(x) { x * 2; }; double(5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
|
||||
{"fn(x) { x; }(5)", 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnclosingEnvironments(t *testing.T) {
|
||||
input := `
|
||||
let first = 10;
|
||||
let second = 10;
|
||||
let third = 10;
|
||||
|
||||
let ourFunction = fn(first) {
|
||||
let second = 20;
|
||||
|
||||
first + second + third;
|
||||
};
|
||||
|
||||
ourFunction(20) + first + second;`
|
||||
|
||||
testIntegerObject(t, testEval(input), 70)
|
||||
}
|
||||
|
||||
func TestClosures(t *testing.T) {
|
||||
input := `
|
||||
let newAdder = fn(x) {
|
||||
fn(y) { x + y };
|
||||
};
|
||||
|
||||
let addTwo = newAdder(2);
|
||||
addTwo(2);`
|
||||
|
||||
testIntegerObject(t, testEval(input), 4)
|
||||
}
|
||||
|
||||
func TestStringLiteral(t *testing.T) {
|
||||
input := `"Hello World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConcatenation(t *testing.T) {
|
||||
input := `"Hello" + " " + "World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinFunctions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{`len("")`, 0},
|
||||
{`len("four")`, 4},
|
||||
{`len("hello world")`, 11},
|
||||
{`len(1)`, "argument to `len` not supported, got INTEGER"},
|
||||
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
|
||||
{`len([1, 2, 3])`, 3},
|
||||
{`len([])`, 0},
|
||||
{`puts("hello", "world!")`, nil},
|
||||
{`first([1, 2, 3])`, 1},
|
||||
{`first([])`, nil},
|
||||
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
|
||||
{`last([1, 2, 3])`, 3},
|
||||
{`last([])`, nil},
|
||||
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
|
||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||
{`rest([])`, nil},
|
||||
{`push([], 1)`, []int{1}},
|
||||
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
switch expected := tt.expected.(type) {
|
||||
case int:
|
||||
testIntegerObject(t, evaluated, int64(expected))
|
||||
case nil:
|
||||
testNullObject(t, evaluated)
|
||||
case string:
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("object is not Error. got=%T (%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
if errObj.Message != expected {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
expected, errObj.Message)
|
||||
}
|
||||
case []int:
|
||||
array, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(array.Elements) != len(expected) {
|
||||
t.Errorf("wrong num of elements. want=%d, got=%d",
|
||||
len(expected), len(array.Elements))
|
||||
continue
|
||||
}
|
||||
|
||||
for i, expectedElem := range expected {
|
||||
testIntegerObject(t, array.Elements[i], int64(expectedElem))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayLiterals(t *testing.T) {
|
||||
input := "[1, 2 * 2, 3 + 3]"
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(result.Elements) != 3 {
|
||||
t.Fatalf("array has wrong num of elements. got=%d",
|
||||
len(result.Elements))
|
||||
}
|
||||
|
||||
testIntegerObject(t, result.Elements[0], 1)
|
||||
testIntegerObject(t, result.Elements[1], 4)
|
||||
testIntegerObject(t, result.Elements[2], 6)
|
||||
}
|
||||
|
||||
func TestArrayIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"[1, 2, 3][0]",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][2]",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let i = 0; [1][i];",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1 + 1];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[2];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
|
||||
6,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][3]",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][-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 TestHashLiterals(t *testing.T) {
|
||||
input := `let two = "two";
|
||||
{
|
||||
"one": 10 - 9,
|
||||
two: 1 + 1,
|
||||
"thr" + "ee": 6 / 2,
|
||||
4: 4,
|
||||
true: 5,
|
||||
false: 6
|
||||
}`
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Hash)
|
||||
if !ok {
|
||||
t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
expected := map[object.HashKey]int64{
|
||||
(&object.String{Value: "one"}).HashKey(): 1,
|
||||
(&object.String{Value: "two"}).HashKey(): 2,
|
||||
(&object.String{Value: "three"}).HashKey(): 3,
|
||||
(&object.Integer{Value: 4}).HashKey(): 4,
|
||||
TRUE.HashKey(): 5,
|
||||
FALSE.HashKey(): 6,
|
||||
}
|
||||
|
||||
if len(result.Pairs) != len(expected) {
|
||||
t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
|
||||
}
|
||||
|
||||
for expectedKey, expectedValue := range expected {
|
||||
pair, ok := result.Pairs[expectedKey]
|
||||
if !ok {
|
||||
t.Errorf("no pair for given key in Pairs")
|
||||
}
|
||||
|
||||
testIntegerObject(t, pair.Value, expectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
`{"foo": 5}["foo"]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{"foo": 5}["bar"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`let key = "foo"; {"foo": 5}[key]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{}["foo"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`{5: 5}[5]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{true: 5}[true]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{false: 5}[false]`,
|
||||
5,
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
program := p.ParseProgram()
|
||||
env := object.NewEnvironment()
|
||||
|
||||
return Eval(program, env)
|
||||
}
|
||||
|
||||
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
|
||||
result, ok := obj.(*object.Integer)
|
||||
if !ok {
|
||||
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
|
||||
result, ok := obj.(*object.Boolean)
|
||||
if !ok {
|
||||
t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%t, want=%t",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func testNullObject(t *testing.T, obj object.Object) bool {
|
||||
if obj != NULL {
|
||||
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
3
wcig_code_1_2/02/src/monkey/go.mod
Normal file
3
wcig_code_1_2/02/src/monkey/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module monkey
|
||||
|
||||
go 1.14
|
||||
157
wcig_code_1_2/02/src/monkey/lexer/lexer.go
Normal file
157
wcig_code_1_2/02/src/monkey/lexer/lexer.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package lexer
|
||||
|
||||
import "monkey/token"
|
||||
|
||||
type Lexer struct {
|
||||
input string
|
||||
position int // current position in input (points to current char)
|
||||
readPosition int // current reading position in input (after current char)
|
||||
ch byte // current char under examination
|
||||
}
|
||||
|
||||
func New(input string) *Lexer {
|
||||
l := &Lexer{input: input}
|
||||
l.readChar()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() token.Token {
|
||||
var tok token.Token
|
||||
|
||||
l.skipWhitespace()
|
||||
|
||||
switch l.ch {
|
||||
case '=':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.ASSIGN, l.ch)
|
||||
}
|
||||
case '+':
|
||||
tok = newToken(token.PLUS, l.ch)
|
||||
case '-':
|
||||
tok = newToken(token.MINUS, l.ch)
|
||||
case '!':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.NOT_EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.BANG, l.ch)
|
||||
}
|
||||
case '/':
|
||||
tok = newToken(token.SLASH, l.ch)
|
||||
case '*':
|
||||
tok = newToken(token.ASTERISK, l.ch)
|
||||
case '<':
|
||||
tok = newToken(token.LT, l.ch)
|
||||
case '>':
|
||||
tok = newToken(token.GT, l.ch)
|
||||
case ';':
|
||||
tok = newToken(token.SEMICOLON, l.ch)
|
||||
case ':':
|
||||
tok = newToken(token.COLON, l.ch)
|
||||
case ',':
|
||||
tok = newToken(token.COMMA, l.ch)
|
||||
case '{':
|
||||
tok = newToken(token.LBRACE, l.ch)
|
||||
case '}':
|
||||
tok = newToken(token.RBRACE, l.ch)
|
||||
case '(':
|
||||
tok = newToken(token.LPAREN, l.ch)
|
||||
case ')':
|
||||
tok = newToken(token.RPAREN, l.ch)
|
||||
case '"':
|
||||
tok.Type = token.STRING
|
||||
tok.Literal = l.readString()
|
||||
case '[':
|
||||
tok = newToken(token.LBRACKET, l.ch)
|
||||
case ']':
|
||||
tok = newToken(token.RBRACKET, l.ch)
|
||||
case 0:
|
||||
tok.Literal = ""
|
||||
tok.Type = token.EOF
|
||||
default:
|
||||
if isLetter(l.ch) {
|
||||
tok.Literal = l.readIdentifier()
|
||||
tok.Type = token.LookupIdent(tok.Literal)
|
||||
return tok
|
||||
} else if isDigit(l.ch) {
|
||||
tok.Type = token.INT
|
||||
tok.Literal = l.readNumber()
|
||||
return tok
|
||||
} else {
|
||||
tok = newToken(token.ILLEGAL, l.ch)
|
||||
}
|
||||
}
|
||||
|
||||
l.readChar()
|
||||
return tok
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
|
||||
l.readChar()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readChar() {
|
||||
if l.readPosition >= len(l.input) {
|
||||
l.ch = 0
|
||||
} else {
|
||||
l.ch = l.input[l.readPosition]
|
||||
}
|
||||
l.position = l.readPosition
|
||||
l.readPosition += 1
|
||||
}
|
||||
|
||||
func (l *Lexer) peekChar() byte {
|
||||
if l.readPosition >= len(l.input) {
|
||||
return 0
|
||||
} else {
|
||||
return l.input[l.readPosition]
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readIdentifier() string {
|
||||
position := l.position
|
||||
for isLetter(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readNumber() string {
|
||||
position := l.position
|
||||
for isDigit(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readString() string {
|
||||
position := l.position + 1
|
||||
for {
|
||||
l.readChar()
|
||||
if l.ch == '"' || l.ch == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func isLetter(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
func newToken(tokenType token.TokenType, ch byte) token.Token {
|
||||
return token.Token{Type: tokenType, Literal: string(ch)}
|
||||
}
|
||||
143
wcig_code_1_2/02/src/monkey/lexer/lexer_test.go
Normal file
143
wcig_code_1_2/02/src/monkey/lexer/lexer_test.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package lexer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"monkey/token"
|
||||
)
|
||||
|
||||
func TestNextToken(t *testing.T) {
|
||||
input := `let five = 5;
|
||||
let ten = 10;
|
||||
|
||||
let add = fn(x, y) {
|
||||
x + y;
|
||||
};
|
||||
|
||||
let result = add(five, ten);
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
"foobar"
|
||||
"foo bar"
|
||||
[1, 2];
|
||||
{"foo": "bar"}
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
expectedType token.TokenType
|
||||
expectedLiteral string
|
||||
}{
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "five"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "ten"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "add"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.FUNCTION, "fn"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "x"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "y"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.IDENT, "x"},
|
||||
{token.PLUS, "+"},
|
||||
{token.IDENT, "y"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "result"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.IDENT, "add"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "five"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "ten"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.BANG, "!"},
|
||||
{token.MINUS, "-"},
|
||||
{token.SLASH, "/"},
|
||||
{token.ASTERISK, "*"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.GT, ">"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.IF, "if"},
|
||||
{token.LPAREN, "("},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.TRUE, "true"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.ELSE, "else"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.FALSE, "false"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.INT, "10"},
|
||||
{token.EQ, "=="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "10"},
|
||||
{token.NOT_EQ, "!="},
|
||||
{token.INT, "9"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.STRING, "foobar"},
|
||||
{token.STRING, "foo bar"},
|
||||
{token.LBRACKET, "["},
|
||||
{token.INT, "1"},
|
||||
{token.COMMA, ","},
|
||||
{token.INT, "2"},
|
||||
{token.RBRACKET, "]"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.STRING, "foo"},
|
||||
{token.COLON, ":"},
|
||||
{token.STRING, "bar"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.EOF, ""},
|
||||
}
|
||||
|
||||
l := New(input)
|
||||
|
||||
for i, tt := range tests {
|
||||
tok := l.NextToken()
|
||||
|
||||
if tok.Type != tt.expectedType {
|
||||
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
|
||||
i, tt.expectedType, tok.Type)
|
||||
}
|
||||
|
||||
if tok.Literal != tt.expectedLiteral {
|
||||
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
|
||||
i, tt.expectedLiteral, tok.Literal)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
wcig_code_1_2/02/src/monkey/main.go
Normal file
19
wcig_code_1_2/02/src/monkey/main.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/repl"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func main() {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Hello %s! This is the Monkey programming language!\n",
|
||||
user.Username)
|
||||
fmt.Printf("Feel free to type in commands\n")
|
||||
repl.Start(os.Stdin, os.Stdout)
|
||||
}
|
||||
30
wcig_code_1_2/02/src/monkey/object/environment.go
Normal file
30
wcig_code_1_2/02/src/monkey/object/environment.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package object
|
||||
|
||||
func NewEnclosedEnvironment(outer *Environment) *Environment {
|
||||
env := NewEnvironment()
|
||||
env.outer = outer
|
||||
return env
|
||||
}
|
||||
|
||||
func NewEnvironment() *Environment {
|
||||
s := make(map[string]Object)
|
||||
return &Environment{store: s, outer: nil}
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
store map[string]Object
|
||||
outer *Environment
|
||||
}
|
||||
|
||||
func (e *Environment) Get(name string) (Object, bool) {
|
||||
obj, ok := e.store[name]
|
||||
if !ok && e.outer != nil {
|
||||
obj, ok = e.outer.Get(name)
|
||||
}
|
||||
return obj, ok
|
||||
}
|
||||
|
||||
func (e *Environment) Set(name string, val Object) Object {
|
||||
e.store[name] = val
|
||||
return val
|
||||
}
|
||||
182
wcig_code_1_2/02/src/monkey/object/object.go
Normal file
182
wcig_code_1_2/02/src/monkey/object/object.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"monkey/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BuiltinFunction func(args ...Object) Object
|
||||
|
||||
type ObjectType string
|
||||
|
||||
const (
|
||||
NULL_OBJ = "NULL"
|
||||
ERROR_OBJ = "ERROR"
|
||||
|
||||
INTEGER_OBJ = "INTEGER"
|
||||
BOOLEAN_OBJ = "BOOLEAN"
|
||||
STRING_OBJ = "STRING"
|
||||
|
||||
RETURN_VALUE_OBJ = "RETURN_VALUE"
|
||||
|
||||
FUNCTION_OBJ = "FUNCTION"
|
||||
BUILTIN_OBJ = "BUILTIN"
|
||||
|
||||
ARRAY_OBJ = "ARRAY"
|
||||
HASH_OBJ = "HASH"
|
||||
)
|
||||
|
||||
type HashKey struct {
|
||||
Type ObjectType
|
||||
Value uint64
|
||||
}
|
||||
|
||||
type Hashable interface {
|
||||
HashKey() HashKey
|
||||
}
|
||||
|
||||
type Object interface {
|
||||
Type() ObjectType
|
||||
Inspect() string
|
||||
}
|
||||
|
||||
type Integer struct {
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
|
||||
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
|
||||
func (i *Integer) HashKey() HashKey {
|
||||
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
|
||||
}
|
||||
|
||||
type Boolean struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
|
||||
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
|
||||
func (b *Boolean) HashKey() HashKey {
|
||||
var value uint64
|
||||
|
||||
if b.Value {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
|
||||
return HashKey{Type: b.Type(), Value: value}
|
||||
}
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (n *Null) Type() ObjectType { return NULL_OBJ }
|
||||
func (n *Null) Inspect() string { return "null" }
|
||||
|
||||
type ReturnValue struct {
|
||||
Value Object
|
||||
}
|
||||
|
||||
func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
|
||||
func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
|
||||
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Type() ObjectType { return ERROR_OBJ }
|
||||
func (e *Error) Inspect() string { return "ERROR: " + e.Message }
|
||||
|
||||
type Function struct {
|
||||
Parameters []*ast.Identifier
|
||||
Body *ast.BlockStatement
|
||||
Env *Environment
|
||||
}
|
||||
|
||||
func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
|
||||
func (f *Function) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range f.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString("fn")
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") {\n")
|
||||
out.WriteString(f.Body.String())
|
||||
out.WriteString("\n}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type String struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (s *String) Type() ObjectType { return STRING_OBJ }
|
||||
func (s *String) Inspect() string { return s.Value }
|
||||
func (s *String) HashKey() HashKey {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(s.Value))
|
||||
|
||||
return HashKey{Type: s.Type(), Value: h.Sum64()}
|
||||
}
|
||||
|
||||
type Builtin struct {
|
||||
Fn BuiltinFunction
|
||||
}
|
||||
|
||||
func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
|
||||
func (b *Builtin) Inspect() string { return "builtin function" }
|
||||
|
||||
type Array struct {
|
||||
Elements []Object
|
||||
}
|
||||
|
||||
func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
|
||||
func (ao *Array) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, e := range ao.Elements {
|
||||
elements = append(elements, e.Inspect())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashPair struct {
|
||||
Key Object
|
||||
Value Object
|
||||
}
|
||||
|
||||
type Hash struct {
|
||||
Pairs map[HashKey]HashPair
|
||||
}
|
||||
|
||||
func (h *Hash) Type() ObjectType { return HASH_OBJ }
|
||||
func (h *Hash) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for _, pair := range h.Pairs {
|
||||
pairs = append(pairs, fmt.Sprintf("%s: %s",
|
||||
pair.Key.Inspect(), pair.Value.Inspect()))
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
60
wcig_code_1_2/02/src/monkey/object/object_test.go
Normal file
60
wcig_code_1_2/02/src/monkey/object/object_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package object
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStringHashKey(t *testing.T) {
|
||||
hello1 := &String{Value: "Hello World"}
|
||||
hello2 := &String{Value: "Hello World"}
|
||||
diff1 := &String{Value: "My name is johnny"}
|
||||
diff2 := &String{Value: "My name is johnny"}
|
||||
|
||||
if hello1.HashKey() != hello2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if diff1.HashKey() != diff2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if hello1.HashKey() == diff1.HashKey() {
|
||||
t.Errorf("strings with different content have same hash keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanHashKey(t *testing.T) {
|
||||
true1 := &Boolean{Value: true}
|
||||
true2 := &Boolean{Value: true}
|
||||
false1 := &Boolean{Value: false}
|
||||
false2 := &Boolean{Value: false}
|
||||
|
||||
if true1.HashKey() != true2.HashKey() {
|
||||
t.Errorf("trues do not have same hash key")
|
||||
}
|
||||
|
||||
if false1.HashKey() != false2.HashKey() {
|
||||
t.Errorf("falses do not have same hash key")
|
||||
}
|
||||
|
||||
if true1.HashKey() == false1.HashKey() {
|
||||
t.Errorf("true has same hash key as false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegerHashKey(t *testing.T) {
|
||||
one1 := &Integer{Value: 1}
|
||||
one2 := &Integer{Value: 1}
|
||||
two1 := &Integer{Value: 2}
|
||||
two2 := &Integer{Value: 2}
|
||||
|
||||
if one1.HashKey() != one2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if two1.HashKey() != two2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if one1.HashKey() == two1.HashKey() {
|
||||
t.Errorf("integers with twoerent content have same hash keys")
|
||||
}
|
||||
}
|
||||
491
wcig_code_1_2/02/src/monkey/parser/parser.go
Normal file
491
wcig_code_1_2/02/src/monkey/parser/parser.go
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/lexer"
|
||||
"monkey/token"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
_ int = iota
|
||||
LOWEST
|
||||
EQUALS // ==
|
||||
LESSGREATER // > or <
|
||||
SUM // +
|
||||
PRODUCT // *
|
||||
PREFIX // -X or !X
|
||||
CALL // myFunction(X)
|
||||
INDEX // array[index]
|
||||
)
|
||||
|
||||
var precedences = map[token.TokenType]int{
|
||||
token.EQ: EQUALS,
|
||||
token.NOT_EQ: EQUALS,
|
||||
token.LT: LESSGREATER,
|
||||
token.GT: LESSGREATER,
|
||||
token.PLUS: SUM,
|
||||
token.MINUS: SUM,
|
||||
token.SLASH: PRODUCT,
|
||||
token.ASTERISK: PRODUCT,
|
||||
token.LPAREN: CALL,
|
||||
token.LBRACKET: INDEX,
|
||||
}
|
||||
|
||||
type (
|
||||
prefixParseFn func() ast.Expression
|
||||
infixParseFn func(ast.Expression) ast.Expression
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
l *lexer.Lexer
|
||||
errors []string
|
||||
|
||||
curToken token.Token
|
||||
peekToken token.Token
|
||||
|
||||
prefixParseFns map[token.TokenType]prefixParseFn
|
||||
infixParseFns map[token.TokenType]infixParseFn
|
||||
}
|
||||
|
||||
func New(l *lexer.Lexer) *Parser {
|
||||
p := &Parser{
|
||||
l: l,
|
||||
errors: []string{},
|
||||
}
|
||||
|
||||
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
|
||||
p.registerPrefix(token.IDENT, p.parseIdentifier)
|
||||
p.registerPrefix(token.INT, p.parseIntegerLiteral)
|
||||
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
||||
p.registerPrefix(token.BANG, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.TRUE, p.parseBoolean)
|
||||
p.registerPrefix(token.FALSE, p.parseBoolean)
|
||||
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
||||
p.registerPrefix(token.IF, p.parseIfExpression)
|
||||
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
||||
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
||||
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
|
||||
|
||||
p.infixParseFns = make(map[token.TokenType]infixParseFn)
|
||||
p.registerInfix(token.PLUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.MINUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.SLASH, p.parseInfixExpression)
|
||||
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
|
||||
p.registerInfix(token.EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.LT, p.parseInfixExpression)
|
||||
p.registerInfix(token.GT, p.parseInfixExpression)
|
||||
|
||||
p.registerInfix(token.LPAREN, p.parseCallExpression)
|
||||
p.registerInfix(token.LBRACKET, p.parseIndexExpression)
|
||||
|
||||
// Read two tokens, so curToken and peekToken are both set
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) nextToken() {
|
||||
p.curToken = p.peekToken
|
||||
p.peekToken = p.l.NextToken()
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(t token.TokenType) bool {
|
||||
return p.curToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) peekTokenIs(t token.TokenType) bool {
|
||||
return p.peekToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) expectPeek(t token.TokenType) bool {
|
||||
if p.peekTokenIs(t) {
|
||||
p.nextToken()
|
||||
return true
|
||||
} else {
|
||||
p.peekError(t)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) Errors() []string {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
func (p *Parser) peekError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("expected next token to be %s, got %s instead",
|
||||
t, p.peekToken.Type)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) noPrefixParseFnError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("no prefix parse function for %s found", t)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) ParseProgram() *ast.Program {
|
||||
program := &ast.Program{}
|
||||
program.Statements = []ast.Statement{}
|
||||
|
||||
for !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
program.Statements = append(program.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() ast.Statement {
|
||||
switch p.curToken.Type {
|
||||
case token.LET:
|
||||
return p.parseLetStatement()
|
||||
case token.RETURN:
|
||||
return p.parseReturnStatement()
|
||||
default:
|
||||
return p.parseExpressionStatement()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseLetStatement() *ast.LetStatement {
|
||||
stmt := &ast.LetStatement{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.IDENT) {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
|
||||
if !p.expectPeek(token.ASSIGN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
|
||||
stmt := &ast.ReturnStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.ReturnValue = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
|
||||
stmt := &ast.ExpressionStatement{Token: p.curToken}
|
||||
|
||||
stmt.Expression = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpression(precedence int) ast.Expression {
|
||||
prefix := p.prefixParseFns[p.curToken.Type]
|
||||
if prefix == nil {
|
||||
p.noPrefixParseFnError(p.curToken.Type)
|
||||
return nil
|
||||
}
|
||||
leftExp := prefix()
|
||||
|
||||
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return leftExp
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
leftExp = infix(leftExp)
|
||||
}
|
||||
|
||||
return leftExp
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() int {
|
||||
if p, ok := precedences[p.peekToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() int {
|
||||
if p, ok := precedences[p.curToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) parseIdentifier() ast.Expression {
|
||||
return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parseIntegerLiteral() ast.Expression {
|
||||
lit := &ast.IntegerLiteral{Token: p.curToken}
|
||||
|
||||
value, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
|
||||
p.errors = append(p.errors, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Value = value
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseStringLiteral() ast.Expression {
|
||||
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parsePrefixExpression() ast.Expression {
|
||||
expression := &ast.PrefixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
expression.Right = p.parseExpression(PREFIX)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
|
||||
expression := &ast.InfixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
precedence := p.curPrecedence()
|
||||
p.nextToken()
|
||||
expression.Right = p.parseExpression(precedence)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBoolean() ast.Expression {
|
||||
return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)}
|
||||
}
|
||||
|
||||
func (p *Parser) parseGroupedExpression() ast.Expression {
|
||||
p.nextToken()
|
||||
|
||||
exp := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseIfExpression() ast.Expression {
|
||||
expression := &ast.IfExpression{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()
|
||||
|
||||
if p.peekTokenIs(token.ELSE) {
|
||||
p.nextToken()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
expression.Alternative = p.parseBlockStatement()
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
|
||||
block := &ast.BlockStatement{Token: p.curToken}
|
||||
block.Statements = []ast.Statement{}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
block.Statements = append(block.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionLiteral() ast.Expression {
|
||||
lit := &ast.FunctionLiteral{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.LPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Parameters = p.parseFunctionParameters()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Body = p.parseBlockStatement()
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionParameters() []*ast.Identifier {
|
||||
identifiers := []*ast.Identifier{}
|
||||
|
||||
if p.peekTokenIs(token.RPAREN) {
|
||||
p.nextToken()
|
||||
return identifiers
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
|
||||
exp := &ast.CallExpression{Token: p.curToken, Function: function}
|
||||
exp.Arguments = p.parseExpressionList(token.RPAREN)
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
|
||||
list := []ast.Expression{}
|
||||
|
||||
if p.peekTokenIs(end) {
|
||||
p.nextToken()
|
||||
return list
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
}
|
||||
|
||||
if !p.expectPeek(end) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (p *Parser) parseArrayLiteral() ast.Expression {
|
||||
array := &ast.ArrayLiteral{Token: p.curToken}
|
||||
|
||||
array.Elements = p.parseExpressionList(token.RBRACKET)
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
|
||||
exp := &ast.IndexExpression{Token: p.curToken, Left: left}
|
||||
|
||||
p.nextToken()
|
||||
exp.Index = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RBRACKET) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseHashLiteral() ast.Expression {
|
||||
hash := &ast.HashLiteral{Token: p.curToken}
|
||||
hash.Pairs = make(map[ast.Expression]ast.Expression)
|
||||
|
||||
for !p.peekTokenIs(token.RBRACE) {
|
||||
p.nextToken()
|
||||
key := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.COLON) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
value := p.parseExpression(LOWEST)
|
||||
|
||||
hash.Pairs[key] = value
|
||||
|
||||
if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
|
||||
p.prefixParseFns[tokenType] = fn
|
||||
}
|
||||
|
||||
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
|
||||
p.infixParseFns[tokenType] = fn
|
||||
}
|
||||
1083
wcig_code_1_2/02/src/monkey/parser/parser_test.go
Normal file
1083
wcig_code_1_2/02/src/monkey/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load diff
32
wcig_code_1_2/02/src/monkey/parser/parser_tracing.go
Normal file
32
wcig_code_1_2/02/src/monkey/parser/parser_tracing.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var traceLevel int = 0
|
||||
|
||||
const traceIdentPlaceholder string = "\t"
|
||||
|
||||
func identLevel() string {
|
||||
return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
|
||||
}
|
||||
|
||||
func tracePrint(fs string) {
|
||||
fmt.Printf("%s%s\n", identLevel(), fs)
|
||||
}
|
||||
|
||||
func incIdent() { traceLevel = traceLevel + 1 }
|
||||
func decIdent() { traceLevel = traceLevel - 1 }
|
||||
|
||||
func trace(msg string) string {
|
||||
incIdent()
|
||||
tracePrint("BEGIN " + msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func untrace(msg string) {
|
||||
tracePrint("END " + msg)
|
||||
decIdent()
|
||||
}
|
||||
75
wcig_code_1_2/02/src/monkey/repl/repl.go
Normal file
75
wcig_code_1_2/02/src/monkey/repl/repl.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package repl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"monkey/compiler"
|
||||
"monkey/lexer"
|
||||
"monkey/parser"
|
||||
"monkey/vm"
|
||||
)
|
||||
|
||||
const PROMPT = ">> "
|
||||
|
||||
func Start(in io.Reader, out io.Writer) {
|
||||
scanner := bufio.NewScanner(in)
|
||||
|
||||
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.New()
|
||||
err := comp.Compile(program)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
machine := vm.New(comp.Bytecode())
|
||||
err = machine.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
stackTop := machine.StackTop()
|
||||
io.WriteString(out, stackTop.Inspect())
|
||||
io.WriteString(out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
const MONKEY_FACE = ` __,__
|
||||
.--. .-" "-. .--.
|
||||
/ .. \/ .-. .-. \/ .. \
|
||||
| | '| / Y \ |' | |
|
||||
| \ \ \ 0 | 0 / / / |
|
||||
\ '- ,\.-"""""""-./, -' /
|
||||
''-' /_ ^ ^ _\ '-''
|
||||
| \._ _./ |
|
||||
\ \ '~' / /
|
||||
'._ '-=-' _.'
|
||||
'-----'
|
||||
`
|
||||
|
||||
func printParserErrors(out io.Writer, errors []string) {
|
||||
io.WriteString(out, MONKEY_FACE)
|
||||
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
||||
io.WriteString(out, " parser errors:\n")
|
||||
for _, msg := range errors {
|
||||
io.WriteString(out, "\t"+msg+"\n")
|
||||
}
|
||||
}
|
||||
70
wcig_code_1_2/02/src/monkey/token/token.go
Normal file
70
wcig_code_1_2/02/src/monkey/token/token.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package token
|
||||
|
||||
type TokenType string
|
||||
|
||||
const (
|
||||
ILLEGAL = "ILLEGAL"
|
||||
EOF = "EOF"
|
||||
|
||||
// Identifiers + literals
|
||||
IDENT = "IDENT" // add, foobar, x, y, ...
|
||||
INT = "INT" // 1343456
|
||||
STRING = "STRING" // "foobar"
|
||||
|
||||
// Operators
|
||||
ASSIGN = "="
|
||||
PLUS = "+"
|
||||
MINUS = "-"
|
||||
BANG = "!"
|
||||
ASTERISK = "*"
|
||||
SLASH = "/"
|
||||
|
||||
LT = "<"
|
||||
GT = ">"
|
||||
|
||||
EQ = "=="
|
||||
NOT_EQ = "!="
|
||||
|
||||
// Delimiters
|
||||
COMMA = ","
|
||||
SEMICOLON = ";"
|
||||
COLON = ":"
|
||||
|
||||
LPAREN = "("
|
||||
RPAREN = ")"
|
||||
LBRACE = "{"
|
||||
RBRACE = "}"
|
||||
LBRACKET = "["
|
||||
RBRACKET = "]"
|
||||
|
||||
// Keywords
|
||||
FUNCTION = "FUNCTION"
|
||||
LET = "LET"
|
||||
TRUE = "TRUE"
|
||||
FALSE = "FALSE"
|
||||
IF = "IF"
|
||||
ELSE = "ELSE"
|
||||
RETURN = "RETURN"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Literal string
|
||||
}
|
||||
|
||||
var keywords = map[string]TokenType{
|
||||
"fn": FUNCTION,
|
||||
"let": LET,
|
||||
"true": TRUE,
|
||||
"false": FALSE,
|
||||
"if": IF,
|
||||
"else": ELSE,
|
||||
"return": RETURN,
|
||||
}
|
||||
|
||||
func LookupIdent(ident string) TokenType {
|
||||
if tok, ok := keywords[ident]; ok {
|
||||
return tok
|
||||
}
|
||||
return IDENT
|
||||
}
|
||||
81
wcig_code_1_2/02/src/monkey/vm/vm.go
Normal file
81
wcig_code_1_2/02/src/monkey/vm/vm.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/code"
|
||||
"monkey/compiler"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
const StackSize = 2048
|
||||
|
||||
type VM struct {
|
||||
constants []object.Object
|
||||
instructions code.Instructions
|
||||
|
||||
stack []object.Object
|
||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||
}
|
||||
|
||||
func New(bytecode *compiler.Bytecode) *VM {
|
||||
return &VM{
|
||||
instructions: bytecode.Instructions,
|
||||
constants: bytecode.Constants,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
sp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) StackTop() object.Object {
|
||||
if vm.sp == 0 {
|
||||
return nil
|
||||
}
|
||||
return vm.stack[vm.sp-1]
|
||||
}
|
||||
|
||||
func (vm *VM) Run() error {
|
||||
for ip := 0; ip < len(vm.instructions); ip++ {
|
||||
op := code.Opcode(vm.instructions[ip])
|
||||
|
||||
switch op {
|
||||
case code.OpConstant:
|
||||
constIndex := code.ReadUint16(vm.instructions[ip+1:])
|
||||
ip += 2
|
||||
|
||||
err := vm.push(vm.constants[constIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpAdd:
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
leftValue := left.(*object.Integer).Value
|
||||
rightValue := right.(*object.Integer).Value
|
||||
|
||||
result := leftValue + rightValue
|
||||
vm.push(&object.Integer{Value: result})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) push(o object.Object) error {
|
||||
if vm.sp >= StackSize {
|
||||
return fmt.Errorf("stack overflow")
|
||||
}
|
||||
|
||||
vm.stack[vm.sp] = o
|
||||
vm.sp++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) pop() object.Object {
|
||||
o := vm.stack[vm.sp-1]
|
||||
vm.sp--
|
||||
return o
|
||||
}
|
||||
87
wcig_code_1_2/02/src/monkey/vm/vm_test.go
Normal file
87
wcig_code_1_2/02/src/monkey/vm/vm_test.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/compiler"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"1", 1},
|
||||
{"2", 2},
|
||||
{"1 + 2", 3},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
type vmTestCase struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}
|
||||
|
||||
func runVmTests(t *testing.T, tests []vmTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
comp := compiler.New()
|
||||
err := comp.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
vm := New(comp.Bytecode())
|
||||
err = vm.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("vm error: %s", err)
|
||||
}
|
||||
|
||||
stackElem := vm.StackTop()
|
||||
|
||||
testExpectedObject(t, tt.expected, stackElem)
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testExpectedObject(
|
||||
t *testing.T,
|
||||
expected interface{},
|
||||
actual object.Object,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
switch expected := expected.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(expected), actual)
|
||||
if err != nil {
|
||||
t.Errorf("testIntegerObject failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1
wcig_code_1_2/03/.envrc
Normal file
1
wcig_code_1_2/03/.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
export GOPATH=$(pwd)
|
||||
339
wcig_code_1_2/03/src/monkey/ast/ast.go
Normal file
339
wcig_code_1_2/03/src/monkey/ast/ast.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"monkey/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The base Node interface
|
||||
type Node interface {
|
||||
TokenLiteral() string
|
||||
String() string
|
||||
}
|
||||
|
||||
// All statement nodes implement this
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
// All expression nodes implement this
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (p *Program) TokenLiteral() string {
|
||||
if len(p.Statements) > 0 {
|
||||
return p.Statements[0].TokenLiteral()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range p.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Statements
|
||||
type LetStatement struct {
|
||||
Token token.Token // the token.LET token
|
||||
Name *Identifier
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (ls *LetStatement) statementNode() {}
|
||||
func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
|
||||
func (ls *LetStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(ls.TokenLiteral() + " ")
|
||||
out.WriteString(ls.Name.String())
|
||||
out.WriteString(" = ")
|
||||
|
||||
if ls.Value != nil {
|
||||
out.WriteString(ls.Value.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ReturnStatement struct {
|
||||
Token token.Token // the 'return' token
|
||||
ReturnValue Expression
|
||||
}
|
||||
|
||||
func (rs *ReturnStatement) statementNode() {}
|
||||
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
|
||||
func (rs *ReturnStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(rs.TokenLiteral() + " ")
|
||||
|
||||
if rs.ReturnValue != nil {
|
||||
out.WriteString(rs.ReturnValue.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ExpressionStatement struct {
|
||||
Token token.Token // the first token of the expression
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (es *ExpressionStatement) statementNode() {}
|
||||
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
|
||||
func (es *ExpressionStatement) String() string {
|
||||
if es.Expression != nil {
|
||||
return es.Expression.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BlockStatement struct {
|
||||
Token token.Token // the { token
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (bs *BlockStatement) statementNode() {}
|
||||
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
|
||||
func (bs *BlockStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range bs.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Expressions
|
||||
type Identifier struct {
|
||||
Token token.Token // the token.IDENT token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (i *Identifier) expressionNode() {}
|
||||
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
|
||||
func (i *Identifier) String() string { return i.Value }
|
||||
|
||||
type Boolean struct {
|
||||
Token token.Token
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) expressionNode() {}
|
||||
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
|
||||
func (b *Boolean) String() string { return b.Token.Literal }
|
||||
|
||||
type IntegerLiteral struct {
|
||||
Token token.Token
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (il *IntegerLiteral) expressionNode() {}
|
||||
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
|
||||
func (il *IntegerLiteral) String() string { return il.Token.Literal }
|
||||
|
||||
type PrefixExpression struct {
|
||||
Token token.Token // The prefix token, e.g. !
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
|
||||
func (pe *PrefixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(pe.Operator)
|
||||
out.WriteString(pe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type InfixExpression struct {
|
||||
Token token.Token // The operator token, e.g. +
|
||||
Left Expression
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (oe *InfixExpression) expressionNode() {}
|
||||
func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
|
||||
func (oe *InfixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(oe.Left.String())
|
||||
out.WriteString(" " + oe.Operator + " ")
|
||||
out.WriteString(oe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IfExpression struct {
|
||||
Token token.Token // The 'if' token
|
||||
Condition Expression
|
||||
Consequence *BlockStatement
|
||||
Alternative *BlockStatement
|
||||
}
|
||||
|
||||
func (ie *IfExpression) expressionNode() {}
|
||||
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IfExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("if")
|
||||
out.WriteString(ie.Condition.String())
|
||||
out.WriteString(" ")
|
||||
out.WriteString(ie.Consequence.String())
|
||||
|
||||
if ie.Alternative != nil {
|
||||
out.WriteString("else ")
|
||||
out.WriteString(ie.Alternative.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type FunctionLiteral struct {
|
||||
Token token.Token // The 'fn' token
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fl *FunctionLiteral) expressionNode() {}
|
||||
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
|
||||
func (fl *FunctionLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range fl.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString(fl.TokenLiteral())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") ")
|
||||
out.WriteString(fl.Body.String())
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type CallExpression struct {
|
||||
Token token.Token // The '(' token
|
||||
Function Expression // Identifier or FunctionLiteral
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (ce *CallExpression) expressionNode() {}
|
||||
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
|
||||
func (ce *CallExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
args := []string{}
|
||||
for _, a := range ce.Arguments {
|
||||
args = append(args, a.String())
|
||||
}
|
||||
|
||||
out.WriteString(ce.Function.String())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(args, ", "))
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type StringLiteral struct {
|
||||
Token token.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) expressionNode() {}
|
||||
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
|
||||
func (sl *StringLiteral) String() string { return sl.Token.Literal }
|
||||
|
||||
type ArrayLiteral struct {
|
||||
Token token.Token // the '[' token
|
||||
Elements []Expression
|
||||
}
|
||||
|
||||
func (al *ArrayLiteral) expressionNode() {}
|
||||
func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal }
|
||||
func (al *ArrayLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, el := range al.Elements {
|
||||
elements = append(elements, el.String())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IndexExpression struct {
|
||||
Token token.Token // The [ token
|
||||
Left Expression
|
||||
Index Expression
|
||||
}
|
||||
|
||||
func (ie *IndexExpression) expressionNode() {}
|
||||
func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IndexExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(ie.Left.String())
|
||||
out.WriteString("[")
|
||||
out.WriteString(ie.Index.String())
|
||||
out.WriteString("])")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashLiteral struct {
|
||||
Token token.Token // the '{' token
|
||||
Pairs map[Expression]Expression
|
||||
}
|
||||
|
||||
func (hl *HashLiteral) expressionNode() {}
|
||||
func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal }
|
||||
func (hl *HashLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for key, value := range hl.Pairs {
|
||||
pairs = append(pairs, key.String()+":"+value.String())
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
28
wcig_code_1_2/03/src/monkey/ast/ast_test.go
Normal file
28
wcig_code_1_2/03/src/monkey/ast/ast_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"monkey/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
program := &Program{
|
||||
Statements: []Statement{
|
||||
&LetStatement{
|
||||
Token: token.Token{Type: token.LET, Literal: "let"},
|
||||
Name: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
|
||||
Value: "myVar",
|
||||
},
|
||||
Value: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
|
||||
Value: "anotherVar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if program.String() != "let myVar = anotherVar;" {
|
||||
t.Errorf("program.String() wrong. got=%q", program.String())
|
||||
}
|
||||
}
|
||||
155
wcig_code_1_2/03/src/monkey/code/code.go
Normal file
155
wcig_code_1_2/03/src/monkey/code/code.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Instructions []byte
|
||||
|
||||
func (ins Instructions) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
i := 0
|
||||
for i < len(ins) {
|
||||
def, err := Lookup(ins[i])
|
||||
if err != nil {
|
||||
fmt.Fprintf(&out, "ERROR: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
operands, read := ReadOperands(def, ins[i+1:])
|
||||
|
||||
fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
|
||||
|
||||
i += 1 + read
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
|
||||
operandCount := len(def.OperandWidths)
|
||||
|
||||
if len(operands) != operandCount {
|
||||
return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n",
|
||||
len(operands), operandCount)
|
||||
}
|
||||
|
||||
switch operandCount {
|
||||
case 0:
|
||||
return def.Name
|
||||
case 1:
|
||||
return fmt.Sprintf("%s %d", def.Name, operands[0])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
|
||||
}
|
||||
|
||||
type Opcode byte
|
||||
|
||||
const (
|
||||
OpConstant Opcode = iota
|
||||
|
||||
OpAdd
|
||||
|
||||
OpPop
|
||||
|
||||
OpSub
|
||||
OpMul
|
||||
OpDiv
|
||||
|
||||
OpTrue
|
||||
OpFalse
|
||||
|
||||
OpEqual
|
||||
OpNotEqual
|
||||
OpGreaterThan
|
||||
|
||||
OpMinus
|
||||
OpBang
|
||||
)
|
||||
|
||||
type Definition struct {
|
||||
Name string
|
||||
OperandWidths []int
|
||||
}
|
||||
|
||||
var definitions = map[Opcode]*Definition{
|
||||
OpConstant: {"OpConstant", []int{2}},
|
||||
|
||||
OpAdd: {"OpAdd", []int{}},
|
||||
|
||||
OpPop: {"OpPop", []int{}},
|
||||
|
||||
OpSub: {"OpSub", []int{}},
|
||||
OpMul: {"OpMul", []int{}},
|
||||
OpDiv: {"OpDiv", []int{}},
|
||||
|
||||
OpTrue: {"OpTrue", []int{}},
|
||||
OpFalse: {"OpFalse", []int{}},
|
||||
|
||||
OpEqual: {"OpEqual", []int{}},
|
||||
OpNotEqual: {"OpNotEqual", []int{}},
|
||||
OpGreaterThan: {"OpGreaterThan", []int{}},
|
||||
|
||||
OpMinus: {"OpMinus", []int{}},
|
||||
OpBang: {"OpBang", []int{}},
|
||||
}
|
||||
|
||||
func Lookup(op byte) (*Definition, error) {
|
||||
def, ok := definitions[Opcode(op)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("opcode %d undefined", op)
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func Make(op Opcode, operands ...int) []byte {
|
||||
def, ok := definitions[op]
|
||||
if !ok {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
instructionLen := 1
|
||||
for _, w := range def.OperandWidths {
|
||||
instructionLen += w
|
||||
}
|
||||
|
||||
instruction := make([]byte, instructionLen)
|
||||
instruction[0] = byte(op)
|
||||
|
||||
offset := 1
|
||||
for i, o := range operands {
|
||||
width := def.OperandWidths[i]
|
||||
switch width {
|
||||
case 2:
|
||||
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
|
||||
}
|
||||
offset += width
|
||||
}
|
||||
|
||||
return instruction
|
||||
}
|
||||
|
||||
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
|
||||
operands := make([]int, len(def.OperandWidths))
|
||||
offset := 0
|
||||
|
||||
for i, width := range def.OperandWidths {
|
||||
switch width {
|
||||
case 2:
|
||||
operands[i] = int(ReadUint16(ins[offset:]))
|
||||
}
|
||||
|
||||
offset += width
|
||||
}
|
||||
|
||||
return operands, offset
|
||||
}
|
||||
|
||||
func ReadUint16(ins Instructions) uint16 {
|
||||
return binary.BigEndian.Uint16(ins)
|
||||
}
|
||||
83
wcig_code_1_2/03/src/monkey/code/code_test.go
Normal file
83
wcig_code_1_2/03/src/monkey/code/code_test.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package code
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMake(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
expected []byte
|
||||
}{
|
||||
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
|
||||
{OpAdd, []int{}, []byte{byte(OpAdd)}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
if len(instruction) != len(tt.expected) {
|
||||
t.Errorf("instruction has wrong length. want=%d, got=%d",
|
||||
len(tt.expected), len(instruction))
|
||||
}
|
||||
|
||||
for i, b := range tt.expected {
|
||||
if instruction[i] != tt.expected[i] {
|
||||
t.Errorf("wrong byte at pos %d. want=%d, got=%d",
|
||||
i, b, instruction[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionsString(t *testing.T) {
|
||||
instructions := []Instructions{
|
||||
Make(OpAdd),
|
||||
Make(OpConstant, 2),
|
||||
Make(OpConstant, 65535),
|
||||
}
|
||||
|
||||
expected := `0000 OpAdd
|
||||
0001 OpConstant 2
|
||||
0004 OpConstant 65535
|
||||
`
|
||||
|
||||
concatted := Instructions{}
|
||||
for _, ins := range instructions {
|
||||
concatted = append(concatted, ins...)
|
||||
}
|
||||
|
||||
if concatted.String() != expected {
|
||||
t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q",
|
||||
expected, concatted.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadOperands(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
bytesRead int
|
||||
}{
|
||||
{OpConstant, []int{65535}, 2},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
def, err := Lookup(byte(tt.op))
|
||||
if err != nil {
|
||||
t.Fatalf("definition not found: %q\n", err)
|
||||
}
|
||||
|
||||
operandsRead, n := ReadOperands(def, instruction[1:])
|
||||
if n != tt.bytesRead {
|
||||
t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
|
||||
}
|
||||
|
||||
for i, want := range tt.operands {
|
||||
if operandsRead[i] != want {
|
||||
t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
wcig_code_1_2/03/src/monkey/compiler/compiler.go
Normal file
140
wcig_code_1_2/03/src/monkey/compiler/compiler.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
instructions code.Instructions
|
||||
constants []object.Object
|
||||
}
|
||||
|
||||
func New() *Compiler {
|
||||
return &Compiler{
|
||||
instructions: code.Instructions{},
|
||||
constants: []object.Object{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) Compile(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Program:
|
||||
for _, s := range node.Statements {
|
||||
err := c.Compile(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
err := c.Compile(node.Expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(code.OpPop)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
if node.Operator == "<" {
|
||||
err := c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(code.OpGreaterThan)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "+":
|
||||
c.emit(code.OpAdd)
|
||||
case "-":
|
||||
c.emit(code.OpSub)
|
||||
case "*":
|
||||
c.emit(code.OpMul)
|
||||
case "/":
|
||||
c.emit(code.OpDiv)
|
||||
case ">":
|
||||
c.emit(code.OpGreaterThan)
|
||||
case "==":
|
||||
c.emit(code.OpEqual)
|
||||
case "!=":
|
||||
c.emit(code.OpNotEqual)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
|
||||
case *ast.IntegerLiteral:
|
||||
integer := &object.Integer{Value: node.Value}
|
||||
c.emit(code.OpConstant, c.addConstant(integer))
|
||||
|
||||
case *ast.Boolean:
|
||||
if node.Value {
|
||||
c.emit(code.OpTrue)
|
||||
} else {
|
||||
c.emit(code.OpFalse)
|
||||
}
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
err := c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "!":
|
||||
c.emit(code.OpBang)
|
||||
case "-":
|
||||
c.emit(code.OpMinus)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) Bytecode() *Bytecode {
|
||||
return &Bytecode{
|
||||
Instructions: c.instructions,
|
||||
Constants: c.constants,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addConstant(obj object.Object) int {
|
||||
c.constants = append(c.constants, obj)
|
||||
return len(c.constants) - 1
|
||||
}
|
||||
|
||||
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
|
||||
ins := code.Make(op, operands...)
|
||||
pos := c.addInstruction(ins)
|
||||
return pos
|
||||
}
|
||||
|
||||
func (c *Compiler) addInstruction(ins []byte) int {
|
||||
posNewInstruction := len(c.instructions)
|
||||
c.instructions = append(c.instructions, ins...)
|
||||
return posNewInstruction
|
||||
}
|
||||
|
||||
type Bytecode struct {
|
||||
Instructions code.Instructions
|
||||
Constants []object.Object
|
||||
}
|
||||
277
wcig_code_1_2/03/src/monkey/compiler/compiler_test.go
Normal file
277
wcig_code_1_2/03/src/monkey/compiler/compiler_test.go
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "1 + 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpAdd),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1; 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpPop),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 - 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpSub),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 * 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpMul),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "2 / 1",
|
||||
expectedConstants: []interface{}{2, 1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpDiv),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "-1",
|
||||
expectedConstants: []interface{}{1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpMinus),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBooleanExpressions(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "true",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 > 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpGreaterThan),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 < 2",
|
||||
expectedConstants: []interface{}{2, 1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpGreaterThan),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 == 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 != 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpNotEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "true == false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "true != false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpNotEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "!true",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpBang),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
type compilerTestCase struct {
|
||||
input string
|
||||
expectedConstants []interface{}
|
||||
expectedInstructions []code.Instructions
|
||||
}
|
||||
|
||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
compiler := New()
|
||||
err := compiler.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
bytecode := compiler.Bytecode()
|
||||
|
||||
err = testInstructions(tt.expectedInstructions, bytecode.Instructions)
|
||||
if err != nil {
|
||||
t.Fatalf("testInstructions failed: %s", err)
|
||||
}
|
||||
|
||||
err = testConstants(t, tt.expectedConstants, bytecode.Constants)
|
||||
if err != nil {
|
||||
t.Fatalf("testConstants failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testInstructions(
|
||||
expected []code.Instructions,
|
||||
actual code.Instructions,
|
||||
) error {
|
||||
concatted := concatInstructions(expected)
|
||||
|
||||
if len(actual) != len(concatted) {
|
||||
return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q",
|
||||
concatted, actual)
|
||||
}
|
||||
|
||||
for i, ins := range concatted {
|
||||
if actual[i] != ins {
|
||||
return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q",
|
||||
i, concatted, actual)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func concatInstructions(s []code.Instructions) code.Instructions {
|
||||
out := code.Instructions{}
|
||||
|
||||
for _, ins := range s {
|
||||
out = append(out, ins...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func testConstants(
|
||||
t *testing.T,
|
||||
expected []interface{},
|
||||
actual []object.Object,
|
||||
) error {
|
||||
if len(expected) != len(actual) {
|
||||
return fmt.Errorf("wrong number of constants. got=%d, want=%d",
|
||||
len(actual), len(expected))
|
||||
}
|
||||
|
||||
for i, constant := range expected {
|
||||
switch constant := constant.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(constant), actual[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("constant %d - testIntegerObject failed: %s",
|
||||
i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
wcig_code_1_2/03/src/monkey/evaluator/builtins.go
Normal file
117
wcig_code_1_2/03/src/monkey/evaluator/builtins.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"len": &object.Builtin{Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
|
||||
switch arg := args[0].(type) {
|
||||
case *object.Array:
|
||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
||||
case *object.String:
|
||||
return &object.Integer{Value: int64(len(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
},
|
||||
},
|
||||
"puts": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"first": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"last": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"rest": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]object.Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &object.Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"push": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]object.Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &object.Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
}
|
||||
442
wcig_code_1_2/03/src/monkey/evaluator/evaluator.go
Normal file
442
wcig_code_1_2/03/src/monkey/evaluator/evaluator.go
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var (
|
||||
NULL = &object.Null{}
|
||||
TRUE = &object.Boolean{Value: true}
|
||||
FALSE = &object.Boolean{Value: false}
|
||||
)
|
||||
|
||||
func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
switch node := node.(type) {
|
||||
|
||||
// Statements
|
||||
case *ast.Program:
|
||||
return evalProgram(node, env)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
return evalBlockStatement(node, env)
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
return Eval(node.Expression, env)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
val := Eval(node.ReturnValue, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
return &object.ReturnValue{Value: val}
|
||||
|
||||
case *ast.LetStatement:
|
||||
val := Eval(node.Value, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
env.Set(node.Name.Value, val)
|
||||
|
||||
// Expressions
|
||||
case *ast.IntegerLiteral:
|
||||
return &object.Integer{Value: node.Value}
|
||||
|
||||
case *ast.StringLiteral:
|
||||
return &object.String{Value: node.Value}
|
||||
|
||||
case *ast.Boolean:
|
||||
return nativeBoolToBooleanObject(node.Value)
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
return evalPrefixExpression(node.Operator, right)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
|
||||
return evalInfixExpression(node.Operator, left, right)
|
||||
|
||||
case *ast.IfExpression:
|
||||
return evalIfExpression(node, env)
|
||||
|
||||
case *ast.Identifier:
|
||||
return evalIdentifier(node, env)
|
||||
|
||||
case *ast.FunctionLiteral:
|
||||
params := node.Parameters
|
||||
body := node.Body
|
||||
return &object.Function{Parameters: params, Env: env, Body: body}
|
||||
|
||||
case *ast.CallExpression:
|
||||
function := Eval(node.Function, env)
|
||||
if isError(function) {
|
||||
return function
|
||||
}
|
||||
|
||||
args := evalExpressions(node.Arguments, env)
|
||||
if len(args) == 1 && isError(args[0]) {
|
||||
return args[0]
|
||||
}
|
||||
|
||||
return applyFunction(function, args)
|
||||
|
||||
case *ast.ArrayLiteral:
|
||||
elements := evalExpressions(node.Elements, env)
|
||||
if len(elements) == 1 && isError(elements[0]) {
|
||||
return elements[0]
|
||||
}
|
||||
return &object.Array{Elements: elements}
|
||||
|
||||
case *ast.IndexExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
index := Eval(node.Index, env)
|
||||
if isError(index) {
|
||||
return index
|
||||
}
|
||||
return evalIndexExpression(left, index)
|
||||
|
||||
case *ast.HashLiteral:
|
||||
return evalHashLiteral(node, env)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range program.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
switch result := result.(type) {
|
||||
case *object.ReturnValue:
|
||||
return result.Value
|
||||
case *object.Error:
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func evalBlockStatement(
|
||||
block *ast.BlockStatement,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range block.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
if result != nil {
|
||||
rt := result.Type()
|
||||
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return TRUE
|
||||
}
|
||||
return FALSE
|
||||
}
|
||||
|
||||
func evalPrefixExpression(operator string, right object.Object) object.Object {
|
||||
switch operator {
|
||||
case "!":
|
||||
return evalBangOperatorExpression(right)
|
||||
case "-":
|
||||
return evalMinusPrefixOperatorExpression(right)
|
||||
default:
|
||||
return newError("unknown operator: %s%s", operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
|
||||
return evalIntegerInfixExpression(operator, left, right)
|
||||
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
|
||||
return evalStringInfixExpression(operator, left, right)
|
||||
case operator == "==":
|
||||
return nativeBoolToBooleanObject(left == right)
|
||||
case operator == "!=":
|
||||
return nativeBoolToBooleanObject(left != right)
|
||||
case left.Type() != right.Type():
|
||||
return newError("type mismatch: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalBangOperatorExpression(right object.Object) object.Object {
|
||||
switch right {
|
||||
case TRUE:
|
||||
return FALSE
|
||||
case FALSE:
|
||||
return TRUE
|
||||
case NULL:
|
||||
return TRUE
|
||||
default:
|
||||
return FALSE
|
||||
}
|
||||
}
|
||||
|
||||
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
|
||||
if right.Type() != object.INTEGER_OBJ {
|
||||
return newError("unknown operator: -%s", right.Type())
|
||||
}
|
||||
|
||||
value := right.(*object.Integer).Value
|
||||
return &object.Integer{Value: -value}
|
||||
}
|
||||
|
||||
func evalIntegerInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
leftVal := left.(*object.Integer).Value
|
||||
rightVal := right.(*object.Integer).Value
|
||||
|
||||
switch operator {
|
||||
case "+":
|
||||
return &object.Integer{Value: leftVal + rightVal}
|
||||
case "-":
|
||||
return &object.Integer{Value: leftVal - rightVal}
|
||||
case "*":
|
||||
return &object.Integer{Value: leftVal * rightVal}
|
||||
case "/":
|
||||
return &object.Integer{Value: leftVal / rightVal}
|
||||
case "<":
|
||||
return nativeBoolToBooleanObject(leftVal < rightVal)
|
||||
case ">":
|
||||
return nativeBoolToBooleanObject(leftVal > rightVal)
|
||||
case "==":
|
||||
return nativeBoolToBooleanObject(leftVal == rightVal)
|
||||
case "!=":
|
||||
return nativeBoolToBooleanObject(leftVal != rightVal)
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalStringInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
if operator != "+" {
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
|
||||
leftVal := left.(*object.String).Value
|
||||
rightVal := right.(*object.String).Value
|
||||
return &object.String{Value: leftVal + rightVal}
|
||||
}
|
||||
|
||||
func evalIfExpression(
|
||||
ie *ast.IfExpression,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
condition := Eval(ie.Condition, env)
|
||||
if isError(condition) {
|
||||
return condition
|
||||
}
|
||||
|
||||
if isTruthy(condition) {
|
||||
return Eval(ie.Consequence, env)
|
||||
} else if ie.Alternative != nil {
|
||||
return Eval(ie.Alternative, env)
|
||||
} else {
|
||||
return NULL
|
||||
}
|
||||
}
|
||||
|
||||
func evalIdentifier(
|
||||
node *ast.Identifier,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
if val, ok := env.Get(node.Value); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
if builtin, ok := builtins[node.Value]; ok {
|
||||
return builtin
|
||||
}
|
||||
|
||||
return newError("identifier not found: " + node.Value)
|
||||
}
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj {
|
||||
case NULL:
|
||||
return false
|
||||
case TRUE:
|
||||
return true
|
||||
case FALSE:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func newError(format string, a ...interface{}) *object.Error {
|
||||
return &object.Error{Message: fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
func isError(obj object.Object) bool {
|
||||
if obj != nil {
|
||||
return obj.Type() == object.ERROR_OBJ
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func evalExpressions(
|
||||
exps []ast.Expression,
|
||||
env *object.Environment,
|
||||
) []object.Object {
|
||||
var result []object.Object
|
||||
|
||||
for _, e := range exps {
|
||||
evaluated := Eval(e, env)
|
||||
if isError(evaluated) {
|
||||
return []object.Object{evaluated}
|
||||
}
|
||||
result = append(result, evaluated)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func applyFunction(fn object.Object, args []object.Object) object.Object {
|
||||
switch fn := fn.(type) {
|
||||
|
||||
case *object.Function:
|
||||
extendedEnv := extendFunctionEnv(fn, args)
|
||||
evaluated := Eval(fn.Body, extendedEnv)
|
||||
return unwrapReturnValue(evaluated)
|
||||
|
||||
case *object.Builtin:
|
||||
return fn.Fn(args...)
|
||||
|
||||
default:
|
||||
return newError("not a function: %s", fn.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func extendFunctionEnv(
|
||||
fn *object.Function,
|
||||
args []object.Object,
|
||||
) *object.Environment {
|
||||
env := object.NewEnclosedEnvironment(fn.Env)
|
||||
|
||||
for paramIdx, param := range fn.Parameters {
|
||||
env.Set(param.Value, args[paramIdx])
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func unwrapReturnValue(obj object.Object) object.Object {
|
||||
if returnValue, ok := obj.(*object.ReturnValue); ok {
|
||||
return returnValue.Value
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func evalIndexExpression(left, index object.Object) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return evalArrayIndexExpression(left, index)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return evalHashIndexExpression(left, index)
|
||||
default:
|
||||
return newError("index operator not supported: %s", left.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalArrayIndexExpression(array, index object.Object) object.Object {
|
||||
arrayObject := array.(*object.Array)
|
||||
idx := index.(*object.Integer).Value
|
||||
max := int64(len(arrayObject.Elements) - 1)
|
||||
|
||||
if idx < 0 || idx > max {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return arrayObject.Elements[idx]
|
||||
}
|
||||
|
||||
func evalHashLiteral(
|
||||
node *ast.HashLiteral,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
|
||||
for keyNode, valueNode := range node.Pairs {
|
||||
key := Eval(keyNode, env)
|
||||
if isError(key) {
|
||||
return key
|
||||
}
|
||||
|
||||
hashKey, ok := key.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", key.Type())
|
||||
}
|
||||
|
||||
value := Eval(valueNode, env)
|
||||
if isError(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
hashed := hashKey.HashKey()
|
||||
pairs[hashed] = object.HashPair{Key: key, Value: value}
|
||||
}
|
||||
|
||||
return &object.Hash{Pairs: pairs}
|
||||
}
|
||||
|
||||
func evalHashIndexExpression(hash, index object.Object) object.Object {
|
||||
hashObject := hash.(*object.Hash)
|
||||
|
||||
key, ok := index.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
|
||||
pair, ok := hashObject.Pairs[key.HashKey()]
|
||||
if !ok {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return pair.Value
|
||||
}
|
||||
629
wcig_code_1_2/03/src/monkey/evaluator/evaluator_test.go
Normal file
629
wcig_code_1_2/03/src/monkey/evaluator/evaluator_test.go
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvalIntegerExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"5", 5},
|
||||
{"10", 10},
|
||||
{"-5", -5},
|
||||
{"-10", -10},
|
||||
{"5 + 5 + 5 + 5 - 10", 10},
|
||||
{"2 * 2 * 2 * 2 * 2", 32},
|
||||
{"-50 + 100 + -50", 0},
|
||||
{"5 * 2 + 10", 20},
|
||||
{"5 + 2 * 10", 25},
|
||||
{"20 + 2 * -10", 0},
|
||||
{"50 / 2 * 2 + 10", 60},
|
||||
{"2 * (5 + 10)", 30},
|
||||
{"3 * 3 * 3 + 10", 37},
|
||||
{"3 * (3 * 3) + 10", 37},
|
||||
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalBooleanExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1 < 2", true},
|
||||
{"1 > 2", false},
|
||||
{"1 < 1", false},
|
||||
{"1 > 1", false},
|
||||
{"1 == 1", true},
|
||||
{"1 != 1", false},
|
||||
{"1 == 2", false},
|
||||
{"1 != 2", true},
|
||||
{"true == true", true},
|
||||
{"false == false", true},
|
||||
{"true == false", false},
|
||||
{"true != false", true},
|
||||
{"false != true", true},
|
||||
{"(1 < 2) == true", true},
|
||||
{"(1 < 2) == false", false},
|
||||
{"(1 > 2) == true", false},
|
||||
{"(1 > 2) == false", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBangOperator(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"!true", false},
|
||||
{"!false", true},
|
||||
{"!5", false},
|
||||
{"!!true", true},
|
||||
{"!!false", false},
|
||||
{"!!5", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfElseExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"if (true) { 10 }", 10},
|
||||
{"if (false) { 10 }", nil},
|
||||
{"if (1) { 10 }", 10},
|
||||
{"if (1 < 2) { 10 }", 10},
|
||||
{"if (1 > 2) { 10 }", nil},
|
||||
{"if (1 > 2) { 10 } else { 20 }", 20},
|
||||
{"if (1 < 2) { 10 } else { 20 }", 10},
|
||||
}
|
||||
|
||||
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 TestReturnStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"return 10;", 10},
|
||||
{"return 10; 9;", 10},
|
||||
{"return 2 * 5; 9;", 10},
|
||||
{"9; return 2 * 5; 9;", 10},
|
||||
{"if (10 > 1) { return 10; }", 10},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
return x;
|
||||
x + 10;
|
||||
};
|
||||
f(10);`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
let result = x + 10;
|
||||
return result;
|
||||
return 10;
|
||||
};
|
||||
f(10);`,
|
||||
20,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedMessage string
|
||||
}{
|
||||
{
|
||||
"5 + true;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5 + true; 5;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"-true",
|
||||
"unknown operator: -BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false + true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5; true + false; 5",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`"Hello" - "World"`,
|
||||
"unknown operator: STRING - STRING",
|
||||
},
|
||||
{
|
||||
"if (10 > 1) { true + false; }",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return true + false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"foobar",
|
||||
"identifier not found: foobar",
|
||||
},
|
||||
{
|
||||
`{"name": "Monkey"}[fn(x) { x }];`,
|
||||
"unusable as hash key: FUNCTION",
|
||||
},
|
||||
{
|
||||
`999[1]`,
|
||||
"index operator not supported: INTEGER",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("no error object returned. got=%T(%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if errObj.Message != tt.expectedMessage {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
tt.expectedMessage, errObj.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLetStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let a = 5; a;", 5},
|
||||
{"let a = 5 * 5; a;", 25},
|
||||
{"let a = 5; let b = a; b;", 5},
|
||||
{"let a = 5; let b = a; let c = a + b + 5; c;", 15},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionObject(t *testing.T) {
|
||||
input := "fn(x) { x + 2; };"
|
||||
|
||||
evaluated := testEval(input)
|
||||
fn, ok := evaluated.(*object.Function)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(fn.Parameters) != 1 {
|
||||
t.Fatalf("function has wrong parameters. Parameters=%+v",
|
||||
fn.Parameters)
|
||||
}
|
||||
|
||||
if fn.Parameters[0].String() != "x" {
|
||||
t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
|
||||
}
|
||||
|
||||
expectedBody := "(x + 2)"
|
||||
|
||||
if fn.Body.String() != expectedBody {
|
||||
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionApplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let identity = fn(x) { x; }; identity(5);", 5},
|
||||
{"let identity = fn(x) { return x; }; identity(5);", 5},
|
||||
{"let double = fn(x) { x * 2; }; double(5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
|
||||
{"fn(x) { x; }(5)", 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnclosingEnvironments(t *testing.T) {
|
||||
input := `
|
||||
let first = 10;
|
||||
let second = 10;
|
||||
let third = 10;
|
||||
|
||||
let ourFunction = fn(first) {
|
||||
let second = 20;
|
||||
|
||||
first + second + third;
|
||||
};
|
||||
|
||||
ourFunction(20) + first + second;`
|
||||
|
||||
testIntegerObject(t, testEval(input), 70)
|
||||
}
|
||||
|
||||
func TestClosures(t *testing.T) {
|
||||
input := `
|
||||
let newAdder = fn(x) {
|
||||
fn(y) { x + y };
|
||||
};
|
||||
|
||||
let addTwo = newAdder(2);
|
||||
addTwo(2);`
|
||||
|
||||
testIntegerObject(t, testEval(input), 4)
|
||||
}
|
||||
|
||||
func TestStringLiteral(t *testing.T) {
|
||||
input := `"Hello World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConcatenation(t *testing.T) {
|
||||
input := `"Hello" + " " + "World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinFunctions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{`len("")`, 0},
|
||||
{`len("four")`, 4},
|
||||
{`len("hello world")`, 11},
|
||||
{`len(1)`, "argument to `len` not supported, got INTEGER"},
|
||||
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
|
||||
{`len([1, 2, 3])`, 3},
|
||||
{`len([])`, 0},
|
||||
{`puts("hello", "world!")`, nil},
|
||||
{`first([1, 2, 3])`, 1},
|
||||
{`first([])`, nil},
|
||||
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
|
||||
{`last([1, 2, 3])`, 3},
|
||||
{`last([])`, nil},
|
||||
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
|
||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||
{`rest([])`, nil},
|
||||
{`push([], 1)`, []int{1}},
|
||||
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
switch expected := tt.expected.(type) {
|
||||
case int:
|
||||
testIntegerObject(t, evaluated, int64(expected))
|
||||
case nil:
|
||||
testNullObject(t, evaluated)
|
||||
case string:
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("object is not Error. got=%T (%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
if errObj.Message != expected {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
expected, errObj.Message)
|
||||
}
|
||||
case []int:
|
||||
array, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(array.Elements) != len(expected) {
|
||||
t.Errorf("wrong num of elements. want=%d, got=%d",
|
||||
len(expected), len(array.Elements))
|
||||
continue
|
||||
}
|
||||
|
||||
for i, expectedElem := range expected {
|
||||
testIntegerObject(t, array.Elements[i], int64(expectedElem))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayLiterals(t *testing.T) {
|
||||
input := "[1, 2 * 2, 3 + 3]"
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(result.Elements) != 3 {
|
||||
t.Fatalf("array has wrong num of elements. got=%d",
|
||||
len(result.Elements))
|
||||
}
|
||||
|
||||
testIntegerObject(t, result.Elements[0], 1)
|
||||
testIntegerObject(t, result.Elements[1], 4)
|
||||
testIntegerObject(t, result.Elements[2], 6)
|
||||
}
|
||||
|
||||
func TestArrayIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"[1, 2, 3][0]",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][2]",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let i = 0; [1][i];",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1 + 1];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[2];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
|
||||
6,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][3]",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][-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 TestHashLiterals(t *testing.T) {
|
||||
input := `let two = "two";
|
||||
{
|
||||
"one": 10 - 9,
|
||||
two: 1 + 1,
|
||||
"thr" + "ee": 6 / 2,
|
||||
4: 4,
|
||||
true: 5,
|
||||
false: 6
|
||||
}`
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Hash)
|
||||
if !ok {
|
||||
t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
expected := map[object.HashKey]int64{
|
||||
(&object.String{Value: "one"}).HashKey(): 1,
|
||||
(&object.String{Value: "two"}).HashKey(): 2,
|
||||
(&object.String{Value: "three"}).HashKey(): 3,
|
||||
(&object.Integer{Value: 4}).HashKey(): 4,
|
||||
TRUE.HashKey(): 5,
|
||||
FALSE.HashKey(): 6,
|
||||
}
|
||||
|
||||
if len(result.Pairs) != len(expected) {
|
||||
t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
|
||||
}
|
||||
|
||||
for expectedKey, expectedValue := range expected {
|
||||
pair, ok := result.Pairs[expectedKey]
|
||||
if !ok {
|
||||
t.Errorf("no pair for given key in Pairs")
|
||||
}
|
||||
|
||||
testIntegerObject(t, pair.Value, expectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
`{"foo": 5}["foo"]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{"foo": 5}["bar"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`let key = "foo"; {"foo": 5}[key]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{}["foo"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`{5: 5}[5]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{true: 5}[true]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{false: 5}[false]`,
|
||||
5,
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
program := p.ParseProgram()
|
||||
env := object.NewEnvironment()
|
||||
|
||||
return Eval(program, env)
|
||||
}
|
||||
|
||||
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
|
||||
result, ok := obj.(*object.Integer)
|
||||
if !ok {
|
||||
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
|
||||
result, ok := obj.(*object.Boolean)
|
||||
if !ok {
|
||||
t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%t, want=%t",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func testNullObject(t *testing.T, obj object.Object) bool {
|
||||
if obj != NULL {
|
||||
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
3
wcig_code_1_2/03/src/monkey/go.mod
Normal file
3
wcig_code_1_2/03/src/monkey/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module monkey
|
||||
|
||||
go 1.14
|
||||
157
wcig_code_1_2/03/src/monkey/lexer/lexer.go
Normal file
157
wcig_code_1_2/03/src/monkey/lexer/lexer.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package lexer
|
||||
|
||||
import "monkey/token"
|
||||
|
||||
type Lexer struct {
|
||||
input string
|
||||
position int // current position in input (points to current char)
|
||||
readPosition int // current reading position in input (after current char)
|
||||
ch byte // current char under examination
|
||||
}
|
||||
|
||||
func New(input string) *Lexer {
|
||||
l := &Lexer{input: input}
|
||||
l.readChar()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() token.Token {
|
||||
var tok token.Token
|
||||
|
||||
l.skipWhitespace()
|
||||
|
||||
switch l.ch {
|
||||
case '=':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.ASSIGN, l.ch)
|
||||
}
|
||||
case '+':
|
||||
tok = newToken(token.PLUS, l.ch)
|
||||
case '-':
|
||||
tok = newToken(token.MINUS, l.ch)
|
||||
case '!':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.NOT_EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.BANG, l.ch)
|
||||
}
|
||||
case '/':
|
||||
tok = newToken(token.SLASH, l.ch)
|
||||
case '*':
|
||||
tok = newToken(token.ASTERISK, l.ch)
|
||||
case '<':
|
||||
tok = newToken(token.LT, l.ch)
|
||||
case '>':
|
||||
tok = newToken(token.GT, l.ch)
|
||||
case ';':
|
||||
tok = newToken(token.SEMICOLON, l.ch)
|
||||
case ':':
|
||||
tok = newToken(token.COLON, l.ch)
|
||||
case ',':
|
||||
tok = newToken(token.COMMA, l.ch)
|
||||
case '{':
|
||||
tok = newToken(token.LBRACE, l.ch)
|
||||
case '}':
|
||||
tok = newToken(token.RBRACE, l.ch)
|
||||
case '(':
|
||||
tok = newToken(token.LPAREN, l.ch)
|
||||
case ')':
|
||||
tok = newToken(token.RPAREN, l.ch)
|
||||
case '"':
|
||||
tok.Type = token.STRING
|
||||
tok.Literal = l.readString()
|
||||
case '[':
|
||||
tok = newToken(token.LBRACKET, l.ch)
|
||||
case ']':
|
||||
tok = newToken(token.RBRACKET, l.ch)
|
||||
case 0:
|
||||
tok.Literal = ""
|
||||
tok.Type = token.EOF
|
||||
default:
|
||||
if isLetter(l.ch) {
|
||||
tok.Literal = l.readIdentifier()
|
||||
tok.Type = token.LookupIdent(tok.Literal)
|
||||
return tok
|
||||
} else if isDigit(l.ch) {
|
||||
tok.Type = token.INT
|
||||
tok.Literal = l.readNumber()
|
||||
return tok
|
||||
} else {
|
||||
tok = newToken(token.ILLEGAL, l.ch)
|
||||
}
|
||||
}
|
||||
|
||||
l.readChar()
|
||||
return tok
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
|
||||
l.readChar()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readChar() {
|
||||
if l.readPosition >= len(l.input) {
|
||||
l.ch = 0
|
||||
} else {
|
||||
l.ch = l.input[l.readPosition]
|
||||
}
|
||||
l.position = l.readPosition
|
||||
l.readPosition += 1
|
||||
}
|
||||
|
||||
func (l *Lexer) peekChar() byte {
|
||||
if l.readPosition >= len(l.input) {
|
||||
return 0
|
||||
} else {
|
||||
return l.input[l.readPosition]
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readIdentifier() string {
|
||||
position := l.position
|
||||
for isLetter(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readNumber() string {
|
||||
position := l.position
|
||||
for isDigit(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readString() string {
|
||||
position := l.position + 1
|
||||
for {
|
||||
l.readChar()
|
||||
if l.ch == '"' || l.ch == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func isLetter(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
func newToken(tokenType token.TokenType, ch byte) token.Token {
|
||||
return token.Token{Type: tokenType, Literal: string(ch)}
|
||||
}
|
||||
143
wcig_code_1_2/03/src/monkey/lexer/lexer_test.go
Normal file
143
wcig_code_1_2/03/src/monkey/lexer/lexer_test.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package lexer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"monkey/token"
|
||||
)
|
||||
|
||||
func TestNextToken(t *testing.T) {
|
||||
input := `let five = 5;
|
||||
let ten = 10;
|
||||
|
||||
let add = fn(x, y) {
|
||||
x + y;
|
||||
};
|
||||
|
||||
let result = add(five, ten);
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
"foobar"
|
||||
"foo bar"
|
||||
[1, 2];
|
||||
{"foo": "bar"}
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
expectedType token.TokenType
|
||||
expectedLiteral string
|
||||
}{
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "five"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "ten"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "add"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.FUNCTION, "fn"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "x"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "y"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.IDENT, "x"},
|
||||
{token.PLUS, "+"},
|
||||
{token.IDENT, "y"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "result"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.IDENT, "add"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "five"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "ten"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.BANG, "!"},
|
||||
{token.MINUS, "-"},
|
||||
{token.SLASH, "/"},
|
||||
{token.ASTERISK, "*"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.GT, ">"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.IF, "if"},
|
||||
{token.LPAREN, "("},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.TRUE, "true"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.ELSE, "else"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.FALSE, "false"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.INT, "10"},
|
||||
{token.EQ, "=="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "10"},
|
||||
{token.NOT_EQ, "!="},
|
||||
{token.INT, "9"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.STRING, "foobar"},
|
||||
{token.STRING, "foo bar"},
|
||||
{token.LBRACKET, "["},
|
||||
{token.INT, "1"},
|
||||
{token.COMMA, ","},
|
||||
{token.INT, "2"},
|
||||
{token.RBRACKET, "]"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.STRING, "foo"},
|
||||
{token.COLON, ":"},
|
||||
{token.STRING, "bar"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.EOF, ""},
|
||||
}
|
||||
|
||||
l := New(input)
|
||||
|
||||
for i, tt := range tests {
|
||||
tok := l.NextToken()
|
||||
|
||||
if tok.Type != tt.expectedType {
|
||||
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
|
||||
i, tt.expectedType, tok.Type)
|
||||
}
|
||||
|
||||
if tok.Literal != tt.expectedLiteral {
|
||||
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
|
||||
i, tt.expectedLiteral, tok.Literal)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
wcig_code_1_2/03/src/monkey/main.go
Normal file
19
wcig_code_1_2/03/src/monkey/main.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/repl"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func main() {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Hello %s! This is the Monkey programming language!\n",
|
||||
user.Username)
|
||||
fmt.Printf("Feel free to type in commands\n")
|
||||
repl.Start(os.Stdin, os.Stdout)
|
||||
}
|
||||
30
wcig_code_1_2/03/src/monkey/object/environment.go
Normal file
30
wcig_code_1_2/03/src/monkey/object/environment.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package object
|
||||
|
||||
func NewEnclosedEnvironment(outer *Environment) *Environment {
|
||||
env := NewEnvironment()
|
||||
env.outer = outer
|
||||
return env
|
||||
}
|
||||
|
||||
func NewEnvironment() *Environment {
|
||||
s := make(map[string]Object)
|
||||
return &Environment{store: s, outer: nil}
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
store map[string]Object
|
||||
outer *Environment
|
||||
}
|
||||
|
||||
func (e *Environment) Get(name string) (Object, bool) {
|
||||
obj, ok := e.store[name]
|
||||
if !ok && e.outer != nil {
|
||||
obj, ok = e.outer.Get(name)
|
||||
}
|
||||
return obj, ok
|
||||
}
|
||||
|
||||
func (e *Environment) Set(name string, val Object) Object {
|
||||
e.store[name] = val
|
||||
return val
|
||||
}
|
||||
182
wcig_code_1_2/03/src/monkey/object/object.go
Normal file
182
wcig_code_1_2/03/src/monkey/object/object.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"monkey/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BuiltinFunction func(args ...Object) Object
|
||||
|
||||
type ObjectType string
|
||||
|
||||
const (
|
||||
NULL_OBJ = "NULL"
|
||||
ERROR_OBJ = "ERROR"
|
||||
|
||||
INTEGER_OBJ = "INTEGER"
|
||||
BOOLEAN_OBJ = "BOOLEAN"
|
||||
STRING_OBJ = "STRING"
|
||||
|
||||
RETURN_VALUE_OBJ = "RETURN_VALUE"
|
||||
|
||||
FUNCTION_OBJ = "FUNCTION"
|
||||
BUILTIN_OBJ = "BUILTIN"
|
||||
|
||||
ARRAY_OBJ = "ARRAY"
|
||||
HASH_OBJ = "HASH"
|
||||
)
|
||||
|
||||
type HashKey struct {
|
||||
Type ObjectType
|
||||
Value uint64
|
||||
}
|
||||
|
||||
type Hashable interface {
|
||||
HashKey() HashKey
|
||||
}
|
||||
|
||||
type Object interface {
|
||||
Type() ObjectType
|
||||
Inspect() string
|
||||
}
|
||||
|
||||
type Integer struct {
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
|
||||
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
|
||||
func (i *Integer) HashKey() HashKey {
|
||||
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
|
||||
}
|
||||
|
||||
type Boolean struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
|
||||
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
|
||||
func (b *Boolean) HashKey() HashKey {
|
||||
var value uint64
|
||||
|
||||
if b.Value {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
|
||||
return HashKey{Type: b.Type(), Value: value}
|
||||
}
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (n *Null) Type() ObjectType { return NULL_OBJ }
|
||||
func (n *Null) Inspect() string { return "null" }
|
||||
|
||||
type ReturnValue struct {
|
||||
Value Object
|
||||
}
|
||||
|
||||
func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
|
||||
func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
|
||||
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Type() ObjectType { return ERROR_OBJ }
|
||||
func (e *Error) Inspect() string { return "ERROR: " + e.Message }
|
||||
|
||||
type Function struct {
|
||||
Parameters []*ast.Identifier
|
||||
Body *ast.BlockStatement
|
||||
Env *Environment
|
||||
}
|
||||
|
||||
func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
|
||||
func (f *Function) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range f.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString("fn")
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") {\n")
|
||||
out.WriteString(f.Body.String())
|
||||
out.WriteString("\n}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type String struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (s *String) Type() ObjectType { return STRING_OBJ }
|
||||
func (s *String) Inspect() string { return s.Value }
|
||||
func (s *String) HashKey() HashKey {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(s.Value))
|
||||
|
||||
return HashKey{Type: s.Type(), Value: h.Sum64()}
|
||||
}
|
||||
|
||||
type Builtin struct {
|
||||
Fn BuiltinFunction
|
||||
}
|
||||
|
||||
func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
|
||||
func (b *Builtin) Inspect() string { return "builtin function" }
|
||||
|
||||
type Array struct {
|
||||
Elements []Object
|
||||
}
|
||||
|
||||
func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
|
||||
func (ao *Array) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, e := range ao.Elements {
|
||||
elements = append(elements, e.Inspect())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashPair struct {
|
||||
Key Object
|
||||
Value Object
|
||||
}
|
||||
|
||||
type Hash struct {
|
||||
Pairs map[HashKey]HashPair
|
||||
}
|
||||
|
||||
func (h *Hash) Type() ObjectType { return HASH_OBJ }
|
||||
func (h *Hash) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for _, pair := range h.Pairs {
|
||||
pairs = append(pairs, fmt.Sprintf("%s: %s",
|
||||
pair.Key.Inspect(), pair.Value.Inspect()))
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
60
wcig_code_1_2/03/src/monkey/object/object_test.go
Normal file
60
wcig_code_1_2/03/src/monkey/object/object_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package object
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStringHashKey(t *testing.T) {
|
||||
hello1 := &String{Value: "Hello World"}
|
||||
hello2 := &String{Value: "Hello World"}
|
||||
diff1 := &String{Value: "My name is johnny"}
|
||||
diff2 := &String{Value: "My name is johnny"}
|
||||
|
||||
if hello1.HashKey() != hello2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if diff1.HashKey() != diff2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if hello1.HashKey() == diff1.HashKey() {
|
||||
t.Errorf("strings with different content have same hash keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanHashKey(t *testing.T) {
|
||||
true1 := &Boolean{Value: true}
|
||||
true2 := &Boolean{Value: true}
|
||||
false1 := &Boolean{Value: false}
|
||||
false2 := &Boolean{Value: false}
|
||||
|
||||
if true1.HashKey() != true2.HashKey() {
|
||||
t.Errorf("trues do not have same hash key")
|
||||
}
|
||||
|
||||
if false1.HashKey() != false2.HashKey() {
|
||||
t.Errorf("falses do not have same hash key")
|
||||
}
|
||||
|
||||
if true1.HashKey() == false1.HashKey() {
|
||||
t.Errorf("true has same hash key as false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegerHashKey(t *testing.T) {
|
||||
one1 := &Integer{Value: 1}
|
||||
one2 := &Integer{Value: 1}
|
||||
two1 := &Integer{Value: 2}
|
||||
two2 := &Integer{Value: 2}
|
||||
|
||||
if one1.HashKey() != one2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if two1.HashKey() != two2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if one1.HashKey() == two1.HashKey() {
|
||||
t.Errorf("integers with twoerent content have same hash keys")
|
||||
}
|
||||
}
|
||||
491
wcig_code_1_2/03/src/monkey/parser/parser.go
Normal file
491
wcig_code_1_2/03/src/monkey/parser/parser.go
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/lexer"
|
||||
"monkey/token"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
_ int = iota
|
||||
LOWEST
|
||||
EQUALS // ==
|
||||
LESSGREATER // > or <
|
||||
SUM // +
|
||||
PRODUCT // *
|
||||
PREFIX // -X or !X
|
||||
CALL // myFunction(X)
|
||||
INDEX // array[index]
|
||||
)
|
||||
|
||||
var precedences = map[token.TokenType]int{
|
||||
token.EQ: EQUALS,
|
||||
token.NOT_EQ: EQUALS,
|
||||
token.LT: LESSGREATER,
|
||||
token.GT: LESSGREATER,
|
||||
token.PLUS: SUM,
|
||||
token.MINUS: SUM,
|
||||
token.SLASH: PRODUCT,
|
||||
token.ASTERISK: PRODUCT,
|
||||
token.LPAREN: CALL,
|
||||
token.LBRACKET: INDEX,
|
||||
}
|
||||
|
||||
type (
|
||||
prefixParseFn func() ast.Expression
|
||||
infixParseFn func(ast.Expression) ast.Expression
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
l *lexer.Lexer
|
||||
errors []string
|
||||
|
||||
curToken token.Token
|
||||
peekToken token.Token
|
||||
|
||||
prefixParseFns map[token.TokenType]prefixParseFn
|
||||
infixParseFns map[token.TokenType]infixParseFn
|
||||
}
|
||||
|
||||
func New(l *lexer.Lexer) *Parser {
|
||||
p := &Parser{
|
||||
l: l,
|
||||
errors: []string{},
|
||||
}
|
||||
|
||||
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
|
||||
p.registerPrefix(token.IDENT, p.parseIdentifier)
|
||||
p.registerPrefix(token.INT, p.parseIntegerLiteral)
|
||||
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
||||
p.registerPrefix(token.BANG, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.TRUE, p.parseBoolean)
|
||||
p.registerPrefix(token.FALSE, p.parseBoolean)
|
||||
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
||||
p.registerPrefix(token.IF, p.parseIfExpression)
|
||||
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
||||
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
||||
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
|
||||
|
||||
p.infixParseFns = make(map[token.TokenType]infixParseFn)
|
||||
p.registerInfix(token.PLUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.MINUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.SLASH, p.parseInfixExpression)
|
||||
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
|
||||
p.registerInfix(token.EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.LT, p.parseInfixExpression)
|
||||
p.registerInfix(token.GT, p.parseInfixExpression)
|
||||
|
||||
p.registerInfix(token.LPAREN, p.parseCallExpression)
|
||||
p.registerInfix(token.LBRACKET, p.parseIndexExpression)
|
||||
|
||||
// Read two tokens, so curToken and peekToken are both set
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) nextToken() {
|
||||
p.curToken = p.peekToken
|
||||
p.peekToken = p.l.NextToken()
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(t token.TokenType) bool {
|
||||
return p.curToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) peekTokenIs(t token.TokenType) bool {
|
||||
return p.peekToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) expectPeek(t token.TokenType) bool {
|
||||
if p.peekTokenIs(t) {
|
||||
p.nextToken()
|
||||
return true
|
||||
} else {
|
||||
p.peekError(t)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) Errors() []string {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
func (p *Parser) peekError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("expected next token to be %s, got %s instead",
|
||||
t, p.peekToken.Type)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) noPrefixParseFnError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("no prefix parse function for %s found", t)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) ParseProgram() *ast.Program {
|
||||
program := &ast.Program{}
|
||||
program.Statements = []ast.Statement{}
|
||||
|
||||
for !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
program.Statements = append(program.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() ast.Statement {
|
||||
switch p.curToken.Type {
|
||||
case token.LET:
|
||||
return p.parseLetStatement()
|
||||
case token.RETURN:
|
||||
return p.parseReturnStatement()
|
||||
default:
|
||||
return p.parseExpressionStatement()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseLetStatement() *ast.LetStatement {
|
||||
stmt := &ast.LetStatement{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.IDENT) {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
|
||||
if !p.expectPeek(token.ASSIGN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
|
||||
stmt := &ast.ReturnStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.ReturnValue = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
|
||||
stmt := &ast.ExpressionStatement{Token: p.curToken}
|
||||
|
||||
stmt.Expression = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpression(precedence int) ast.Expression {
|
||||
prefix := p.prefixParseFns[p.curToken.Type]
|
||||
if prefix == nil {
|
||||
p.noPrefixParseFnError(p.curToken.Type)
|
||||
return nil
|
||||
}
|
||||
leftExp := prefix()
|
||||
|
||||
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return leftExp
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
leftExp = infix(leftExp)
|
||||
}
|
||||
|
||||
return leftExp
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() int {
|
||||
if p, ok := precedences[p.peekToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() int {
|
||||
if p, ok := precedences[p.curToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) parseIdentifier() ast.Expression {
|
||||
return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parseIntegerLiteral() ast.Expression {
|
||||
lit := &ast.IntegerLiteral{Token: p.curToken}
|
||||
|
||||
value, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
|
||||
p.errors = append(p.errors, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Value = value
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseStringLiteral() ast.Expression {
|
||||
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parsePrefixExpression() ast.Expression {
|
||||
expression := &ast.PrefixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
expression.Right = p.parseExpression(PREFIX)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
|
||||
expression := &ast.InfixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
precedence := p.curPrecedence()
|
||||
p.nextToken()
|
||||
expression.Right = p.parseExpression(precedence)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBoolean() ast.Expression {
|
||||
return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)}
|
||||
}
|
||||
|
||||
func (p *Parser) parseGroupedExpression() ast.Expression {
|
||||
p.nextToken()
|
||||
|
||||
exp := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseIfExpression() ast.Expression {
|
||||
expression := &ast.IfExpression{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()
|
||||
|
||||
if p.peekTokenIs(token.ELSE) {
|
||||
p.nextToken()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
expression.Alternative = p.parseBlockStatement()
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
|
||||
block := &ast.BlockStatement{Token: p.curToken}
|
||||
block.Statements = []ast.Statement{}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
block.Statements = append(block.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionLiteral() ast.Expression {
|
||||
lit := &ast.FunctionLiteral{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.LPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Parameters = p.parseFunctionParameters()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Body = p.parseBlockStatement()
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionParameters() []*ast.Identifier {
|
||||
identifiers := []*ast.Identifier{}
|
||||
|
||||
if p.peekTokenIs(token.RPAREN) {
|
||||
p.nextToken()
|
||||
return identifiers
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
|
||||
exp := &ast.CallExpression{Token: p.curToken, Function: function}
|
||||
exp.Arguments = p.parseExpressionList(token.RPAREN)
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
|
||||
list := []ast.Expression{}
|
||||
|
||||
if p.peekTokenIs(end) {
|
||||
p.nextToken()
|
||||
return list
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
}
|
||||
|
||||
if !p.expectPeek(end) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (p *Parser) parseArrayLiteral() ast.Expression {
|
||||
array := &ast.ArrayLiteral{Token: p.curToken}
|
||||
|
||||
array.Elements = p.parseExpressionList(token.RBRACKET)
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
|
||||
exp := &ast.IndexExpression{Token: p.curToken, Left: left}
|
||||
|
||||
p.nextToken()
|
||||
exp.Index = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RBRACKET) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseHashLiteral() ast.Expression {
|
||||
hash := &ast.HashLiteral{Token: p.curToken}
|
||||
hash.Pairs = make(map[ast.Expression]ast.Expression)
|
||||
|
||||
for !p.peekTokenIs(token.RBRACE) {
|
||||
p.nextToken()
|
||||
key := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.COLON) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
value := p.parseExpression(LOWEST)
|
||||
|
||||
hash.Pairs[key] = value
|
||||
|
||||
if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
|
||||
p.prefixParseFns[tokenType] = fn
|
||||
}
|
||||
|
||||
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
|
||||
p.infixParseFns[tokenType] = fn
|
||||
}
|
||||
1083
wcig_code_1_2/03/src/monkey/parser/parser_test.go
Normal file
1083
wcig_code_1_2/03/src/monkey/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load diff
32
wcig_code_1_2/03/src/monkey/parser/parser_tracing.go
Normal file
32
wcig_code_1_2/03/src/monkey/parser/parser_tracing.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var traceLevel int = 0
|
||||
|
||||
const traceIdentPlaceholder string = "\t"
|
||||
|
||||
func identLevel() string {
|
||||
return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
|
||||
}
|
||||
|
||||
func tracePrint(fs string) {
|
||||
fmt.Printf("%s%s\n", identLevel(), fs)
|
||||
}
|
||||
|
||||
func incIdent() { traceLevel = traceLevel + 1 }
|
||||
func decIdent() { traceLevel = traceLevel - 1 }
|
||||
|
||||
func trace(msg string) string {
|
||||
incIdent()
|
||||
tracePrint("BEGIN " + msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func untrace(msg string) {
|
||||
tracePrint("END " + msg)
|
||||
decIdent()
|
||||
}
|
||||
75
wcig_code_1_2/03/src/monkey/repl/repl.go
Normal file
75
wcig_code_1_2/03/src/monkey/repl/repl.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package repl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"monkey/compiler"
|
||||
"monkey/lexer"
|
||||
"monkey/parser"
|
||||
"monkey/vm"
|
||||
)
|
||||
|
||||
const PROMPT = ">> "
|
||||
|
||||
func Start(in io.Reader, out io.Writer) {
|
||||
scanner := bufio.NewScanner(in)
|
||||
|
||||
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.New()
|
||||
err := comp.Compile(program)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
machine := vm.New(comp.Bytecode())
|
||||
err = machine.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lastPopped := machine.LastPoppedStackElem()
|
||||
io.WriteString(out, lastPopped.Inspect())
|
||||
io.WriteString(out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
const MONKEY_FACE = ` __,__
|
||||
.--. .-" "-. .--.
|
||||
/ .. \/ .-. .-. \/ .. \
|
||||
| | '| / Y \ |' | |
|
||||
| \ \ \ 0 | 0 / / / |
|
||||
\ '- ,\.-"""""""-./, -' /
|
||||
''-' /_ ^ ^ _\ '-''
|
||||
| \._ _./ |
|
||||
\ \ '~' / /
|
||||
'._ '-=-' _.'
|
||||
'-----'
|
||||
`
|
||||
|
||||
func printParserErrors(out io.Writer, errors []string) {
|
||||
io.WriteString(out, MONKEY_FACE)
|
||||
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
||||
io.WriteString(out, " parser errors:\n")
|
||||
for _, msg := range errors {
|
||||
io.WriteString(out, "\t"+msg+"\n")
|
||||
}
|
||||
}
|
||||
70
wcig_code_1_2/03/src/monkey/token/token.go
Normal file
70
wcig_code_1_2/03/src/monkey/token/token.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package token
|
||||
|
||||
type TokenType string
|
||||
|
||||
const (
|
||||
ILLEGAL = "ILLEGAL"
|
||||
EOF = "EOF"
|
||||
|
||||
// Identifiers + literals
|
||||
IDENT = "IDENT" // add, foobar, x, y, ...
|
||||
INT = "INT" // 1343456
|
||||
STRING = "STRING" // "foobar"
|
||||
|
||||
// Operators
|
||||
ASSIGN = "="
|
||||
PLUS = "+"
|
||||
MINUS = "-"
|
||||
BANG = "!"
|
||||
ASTERISK = "*"
|
||||
SLASH = "/"
|
||||
|
||||
LT = "<"
|
||||
GT = ">"
|
||||
|
||||
EQ = "=="
|
||||
NOT_EQ = "!="
|
||||
|
||||
// Delimiters
|
||||
COMMA = ","
|
||||
SEMICOLON = ";"
|
||||
COLON = ":"
|
||||
|
||||
LPAREN = "("
|
||||
RPAREN = ")"
|
||||
LBRACE = "{"
|
||||
RBRACE = "}"
|
||||
LBRACKET = "["
|
||||
RBRACKET = "]"
|
||||
|
||||
// Keywords
|
||||
FUNCTION = "FUNCTION"
|
||||
LET = "LET"
|
||||
TRUE = "TRUE"
|
||||
FALSE = "FALSE"
|
||||
IF = "IF"
|
||||
ELSE = "ELSE"
|
||||
RETURN = "RETURN"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Literal string
|
||||
}
|
||||
|
||||
var keywords = map[string]TokenType{
|
||||
"fn": FUNCTION,
|
||||
"let": LET,
|
||||
"true": TRUE,
|
||||
"false": FALSE,
|
||||
"if": IF,
|
||||
"else": ELSE,
|
||||
"return": RETURN,
|
||||
}
|
||||
|
||||
func LookupIdent(ident string) TokenType {
|
||||
if tok, ok := keywords[ident]; ok {
|
||||
return tok
|
||||
}
|
||||
return IDENT
|
||||
}
|
||||
219
wcig_code_1_2/03/src/monkey/vm/vm.go
Normal file
219
wcig_code_1_2/03/src/monkey/vm/vm.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/code"
|
||||
"monkey/compiler"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
const StackSize = 2048
|
||||
|
||||
var True = &object.Boolean{Value: true}
|
||||
var False = &object.Boolean{Value: false}
|
||||
|
||||
type VM struct {
|
||||
constants []object.Object
|
||||
instructions code.Instructions
|
||||
|
||||
stack []object.Object
|
||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||
}
|
||||
|
||||
func New(bytecode *compiler.Bytecode) *VM {
|
||||
return &VM{
|
||||
instructions: bytecode.Instructions,
|
||||
constants: bytecode.Constants,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
sp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) LastPoppedStackElem() object.Object {
|
||||
return vm.stack[vm.sp]
|
||||
}
|
||||
|
||||
func (vm *VM) Run() error {
|
||||
for ip := 0; ip < len(vm.instructions); ip++ {
|
||||
op := code.Opcode(vm.instructions[ip])
|
||||
|
||||
switch op {
|
||||
case code.OpConstant:
|
||||
constIndex := code.ReadUint16(vm.instructions[ip+1:])
|
||||
ip += 2
|
||||
|
||||
err := vm.push(vm.constants[constIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpPop:
|
||||
vm.pop()
|
||||
|
||||
case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv:
|
||||
err := vm.executeBinaryOperation(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpTrue:
|
||||
err := vm.push(True)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpFalse:
|
||||
err := vm.push(False)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpEqual, code.OpNotEqual, code.OpGreaterThan:
|
||||
err := vm.executeComparison(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpBang:
|
||||
err := vm.executeBangOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpMinus:
|
||||
err := vm.executeMinusOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) push(o object.Object) error {
|
||||
if vm.sp >= StackSize {
|
||||
return fmt.Errorf("stack overflow")
|
||||
}
|
||||
|
||||
vm.stack[vm.sp] = o
|
||||
vm.sp++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) pop() object.Object {
|
||||
o := vm.stack[vm.sp-1]
|
||||
vm.sp--
|
||||
return o
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryOperation(op code.Opcode) error {
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
leftType := left.Type()
|
||||
rightType := right.Type()
|
||||
|
||||
if leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ {
|
||||
return vm.executeBinaryIntegerOperation(op, left, right)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported types for binary operation: %s %s",
|
||||
leftType, rightType)
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryIntegerOperation(
|
||||
op code.Opcode,
|
||||
left, right object.Object,
|
||||
) error {
|
||||
leftValue := left.(*object.Integer).Value
|
||||
rightValue := right.(*object.Integer).Value
|
||||
|
||||
var result int64
|
||||
|
||||
switch op {
|
||||
case code.OpAdd:
|
||||
result = leftValue + rightValue
|
||||
case code.OpSub:
|
||||
result = leftValue - rightValue
|
||||
case code.OpMul:
|
||||
result = leftValue * rightValue
|
||||
case code.OpDiv:
|
||||
result = leftValue / rightValue
|
||||
default:
|
||||
return fmt.Errorf("unknown integer operator: %d", op)
|
||||
}
|
||||
|
||||
return vm.push(&object.Integer{Value: result})
|
||||
}
|
||||
|
||||
func (vm *VM) executeComparison(op code.Opcode) error {
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ {
|
||||
return vm.executeIntegerComparison(op, left, right)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case code.OpEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(right == left))
|
||||
case code.OpNotEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(right != left))
|
||||
default:
|
||||
return fmt.Errorf("unknown operator: %d (%s %s)",
|
||||
op, left.Type(), right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeIntegerComparison(
|
||||
op code.Opcode,
|
||||
left, right object.Object,
|
||||
) error {
|
||||
leftValue := left.(*object.Integer).Value
|
||||
rightValue := right.(*object.Integer).Value
|
||||
|
||||
switch op {
|
||||
case code.OpEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(rightValue == leftValue))
|
||||
case code.OpNotEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(rightValue != leftValue))
|
||||
case code.OpGreaterThan:
|
||||
return vm.push(nativeBoolToBooleanObject(leftValue > rightValue))
|
||||
default:
|
||||
return fmt.Errorf("unknown operator: %d", op)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeBangOperator() error {
|
||||
operand := vm.pop()
|
||||
|
||||
switch operand {
|
||||
case True:
|
||||
return vm.push(False)
|
||||
case False:
|
||||
return vm.push(True)
|
||||
default:
|
||||
return vm.push(False)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeMinusOperator() error {
|
||||
operand := vm.pop()
|
||||
|
||||
if operand.Type() != object.INTEGER_OBJ {
|
||||
return fmt.Errorf("unsupported type for negation: %s", operand.Type())
|
||||
}
|
||||
|
||||
value := operand.(*object.Integer).Value
|
||||
return vm.push(&object.Integer{Value: -value})
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
}
|
||||
154
wcig_code_1_2/03/src/monkey/vm/vm_test.go
Normal file
154
wcig_code_1_2/03/src/monkey/vm/vm_test.go
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/compiler"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"1", 1},
|
||||
{"2", 2},
|
||||
{"1 + 2", 3},
|
||||
{"1 - 2", -1},
|
||||
{"1 * 2", 2},
|
||||
{"4 / 2", 2},
|
||||
{"50 / 2 * 2 + 10 - 5", 55},
|
||||
{"5 * (2 + 10)", 60},
|
||||
{"5 + 5 + 5 + 5 - 10", 10},
|
||||
{"2 * 2 * 2 * 2 * 2", 32},
|
||||
{"5 * 2 + 10", 20},
|
||||
{"5 + 2 * 10", 25},
|
||||
{"5 * (2 + 10)", 60},
|
||||
{"-5", -5},
|
||||
{"-10", -10},
|
||||
{"-50 + 100 + -50", 0},
|
||||
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBooleanExpressions(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1 < 2", true},
|
||||
{"1 > 2", false},
|
||||
{"1 < 1", false},
|
||||
{"1 > 1", false},
|
||||
{"1 == 1", true},
|
||||
{"1 != 1", false},
|
||||
{"1 == 2", false},
|
||||
{"1 != 2", true},
|
||||
{"true == true", true},
|
||||
{"false == false", true},
|
||||
{"true == false", false},
|
||||
{"true != false", true},
|
||||
{"false != true", true},
|
||||
{"(1 < 2) == true", true},
|
||||
{"(1 < 2) == false", false},
|
||||
{"(1 > 2) == true", false},
|
||||
{"(1 > 2) == false", true},
|
||||
{"!true", false},
|
||||
{"!false", true},
|
||||
{"!5", false},
|
||||
{"!!true", true},
|
||||
{"!!false", false},
|
||||
{"!!5", true},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
type vmTestCase struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}
|
||||
|
||||
func runVmTests(t *testing.T, tests []vmTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
comp := compiler.New()
|
||||
err := comp.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
vm := New(comp.Bytecode())
|
||||
err = vm.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("vm error: %s", err)
|
||||
}
|
||||
|
||||
stackElem := vm.LastPoppedStackElem()
|
||||
|
||||
testExpectedObject(t, tt.expected, stackElem)
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testExpectedObject(
|
||||
t *testing.T,
|
||||
expected interface{},
|
||||
actual object.Object,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
switch expected := expected.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(expected), actual)
|
||||
if err != nil {
|
||||
t.Errorf("testIntegerObject failed: %s", err)
|
||||
}
|
||||
|
||||
case bool:
|
||||
err := testBooleanObject(bool(expected), actual)
|
||||
if err != nil {
|
||||
t.Errorf("testBooleanObject failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testBooleanObject(expected bool, actual object.Object) error {
|
||||
result, ok := actual.(*object.Boolean)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Boolean. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%t, want=%t",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1
wcig_code_1_2/04/.envrc
Normal file
1
wcig_code_1_2/04/.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
export GOPATH=$(pwd)
|
||||
339
wcig_code_1_2/04/src/monkey/ast/ast.go
Normal file
339
wcig_code_1_2/04/src/monkey/ast/ast.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"monkey/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The base Node interface
|
||||
type Node interface {
|
||||
TokenLiteral() string
|
||||
String() string
|
||||
}
|
||||
|
||||
// All statement nodes implement this
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
// All expression nodes implement this
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (p *Program) TokenLiteral() string {
|
||||
if len(p.Statements) > 0 {
|
||||
return p.Statements[0].TokenLiteral()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range p.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Statements
|
||||
type LetStatement struct {
|
||||
Token token.Token // the token.LET token
|
||||
Name *Identifier
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (ls *LetStatement) statementNode() {}
|
||||
func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
|
||||
func (ls *LetStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(ls.TokenLiteral() + " ")
|
||||
out.WriteString(ls.Name.String())
|
||||
out.WriteString(" = ")
|
||||
|
||||
if ls.Value != nil {
|
||||
out.WriteString(ls.Value.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ReturnStatement struct {
|
||||
Token token.Token // the 'return' token
|
||||
ReturnValue Expression
|
||||
}
|
||||
|
||||
func (rs *ReturnStatement) statementNode() {}
|
||||
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
|
||||
func (rs *ReturnStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(rs.TokenLiteral() + " ")
|
||||
|
||||
if rs.ReturnValue != nil {
|
||||
out.WriteString(rs.ReturnValue.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ExpressionStatement struct {
|
||||
Token token.Token // the first token of the expression
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (es *ExpressionStatement) statementNode() {}
|
||||
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
|
||||
func (es *ExpressionStatement) String() string {
|
||||
if es.Expression != nil {
|
||||
return es.Expression.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BlockStatement struct {
|
||||
Token token.Token // the { token
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (bs *BlockStatement) statementNode() {}
|
||||
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
|
||||
func (bs *BlockStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range bs.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Expressions
|
||||
type Identifier struct {
|
||||
Token token.Token // the token.IDENT token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (i *Identifier) expressionNode() {}
|
||||
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
|
||||
func (i *Identifier) String() string { return i.Value }
|
||||
|
||||
type Boolean struct {
|
||||
Token token.Token
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) expressionNode() {}
|
||||
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
|
||||
func (b *Boolean) String() string { return b.Token.Literal }
|
||||
|
||||
type IntegerLiteral struct {
|
||||
Token token.Token
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (il *IntegerLiteral) expressionNode() {}
|
||||
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
|
||||
func (il *IntegerLiteral) String() string { return il.Token.Literal }
|
||||
|
||||
type PrefixExpression struct {
|
||||
Token token.Token // The prefix token, e.g. !
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
|
||||
func (pe *PrefixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(pe.Operator)
|
||||
out.WriteString(pe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type InfixExpression struct {
|
||||
Token token.Token // The operator token, e.g. +
|
||||
Left Expression
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (oe *InfixExpression) expressionNode() {}
|
||||
func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
|
||||
func (oe *InfixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(oe.Left.String())
|
||||
out.WriteString(" " + oe.Operator + " ")
|
||||
out.WriteString(oe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IfExpression struct {
|
||||
Token token.Token // The 'if' token
|
||||
Condition Expression
|
||||
Consequence *BlockStatement
|
||||
Alternative *BlockStatement
|
||||
}
|
||||
|
||||
func (ie *IfExpression) expressionNode() {}
|
||||
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IfExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("if")
|
||||
out.WriteString(ie.Condition.String())
|
||||
out.WriteString(" ")
|
||||
out.WriteString(ie.Consequence.String())
|
||||
|
||||
if ie.Alternative != nil {
|
||||
out.WriteString("else ")
|
||||
out.WriteString(ie.Alternative.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type FunctionLiteral struct {
|
||||
Token token.Token // The 'fn' token
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fl *FunctionLiteral) expressionNode() {}
|
||||
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
|
||||
func (fl *FunctionLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range fl.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString(fl.TokenLiteral())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") ")
|
||||
out.WriteString(fl.Body.String())
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type CallExpression struct {
|
||||
Token token.Token // The '(' token
|
||||
Function Expression // Identifier or FunctionLiteral
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (ce *CallExpression) expressionNode() {}
|
||||
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
|
||||
func (ce *CallExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
args := []string{}
|
||||
for _, a := range ce.Arguments {
|
||||
args = append(args, a.String())
|
||||
}
|
||||
|
||||
out.WriteString(ce.Function.String())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(args, ", "))
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type StringLiteral struct {
|
||||
Token token.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) expressionNode() {}
|
||||
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
|
||||
func (sl *StringLiteral) String() string { return sl.Token.Literal }
|
||||
|
||||
type ArrayLiteral struct {
|
||||
Token token.Token // the '[' token
|
||||
Elements []Expression
|
||||
}
|
||||
|
||||
func (al *ArrayLiteral) expressionNode() {}
|
||||
func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal }
|
||||
func (al *ArrayLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, el := range al.Elements {
|
||||
elements = append(elements, el.String())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IndexExpression struct {
|
||||
Token token.Token // The [ token
|
||||
Left Expression
|
||||
Index Expression
|
||||
}
|
||||
|
||||
func (ie *IndexExpression) expressionNode() {}
|
||||
func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IndexExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(ie.Left.String())
|
||||
out.WriteString("[")
|
||||
out.WriteString(ie.Index.String())
|
||||
out.WriteString("])")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashLiteral struct {
|
||||
Token token.Token // the '{' token
|
||||
Pairs map[Expression]Expression
|
||||
}
|
||||
|
||||
func (hl *HashLiteral) expressionNode() {}
|
||||
func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal }
|
||||
func (hl *HashLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for key, value := range hl.Pairs {
|
||||
pairs = append(pairs, key.String()+":"+value.String())
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
28
wcig_code_1_2/04/src/monkey/ast/ast_test.go
Normal file
28
wcig_code_1_2/04/src/monkey/ast/ast_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"monkey/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
program := &Program{
|
||||
Statements: []Statement{
|
||||
&LetStatement{
|
||||
Token: token.Token{Type: token.LET, Literal: "let"},
|
||||
Name: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
|
||||
Value: "myVar",
|
||||
},
|
||||
Value: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
|
||||
Value: "anotherVar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if program.String() != "let myVar = anotherVar;" {
|
||||
t.Errorf("program.String() wrong. got=%q", program.String())
|
||||
}
|
||||
}
|
||||
165
wcig_code_1_2/04/src/monkey/code/code.go
Normal file
165
wcig_code_1_2/04/src/monkey/code/code.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Instructions []byte
|
||||
|
||||
func (ins Instructions) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
i := 0
|
||||
for i < len(ins) {
|
||||
def, err := Lookup(ins[i])
|
||||
if err != nil {
|
||||
fmt.Fprintf(&out, "ERROR: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
operands, read := ReadOperands(def, ins[i+1:])
|
||||
|
||||
fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
|
||||
|
||||
i += 1 + read
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
|
||||
operandCount := len(def.OperandWidths)
|
||||
|
||||
if len(operands) != operandCount {
|
||||
return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n",
|
||||
len(operands), operandCount)
|
||||
}
|
||||
|
||||
switch operandCount {
|
||||
case 0:
|
||||
return def.Name
|
||||
case 1:
|
||||
return fmt.Sprintf("%s %d", def.Name, operands[0])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
|
||||
}
|
||||
|
||||
type Opcode byte
|
||||
|
||||
const (
|
||||
OpConstant Opcode = iota
|
||||
|
||||
OpAdd
|
||||
|
||||
OpPop
|
||||
|
||||
OpSub
|
||||
OpMul
|
||||
OpDiv
|
||||
|
||||
OpTrue
|
||||
OpFalse
|
||||
|
||||
OpEqual
|
||||
OpNotEqual
|
||||
OpGreaterThan
|
||||
|
||||
OpMinus
|
||||
OpBang
|
||||
|
||||
OpJumpNotTruthy
|
||||
OpJump
|
||||
|
||||
OpNull
|
||||
)
|
||||
|
||||
type Definition struct {
|
||||
Name string
|
||||
OperandWidths []int
|
||||
}
|
||||
|
||||
var definitions = map[Opcode]*Definition{
|
||||
OpConstant: {"OpConstant", []int{2}},
|
||||
|
||||
OpAdd: {"OpAdd", []int{}},
|
||||
|
||||
OpPop: {"OpPop", []int{}},
|
||||
|
||||
OpSub: {"OpSub", []int{}},
|
||||
OpMul: {"OpMul", []int{}},
|
||||
OpDiv: {"OpDiv", []int{}},
|
||||
|
||||
OpTrue: {"OpTrue", []int{}},
|
||||
OpFalse: {"OpFalse", []int{}},
|
||||
|
||||
OpEqual: {"OpEqual", []int{}},
|
||||
OpNotEqual: {"OpNotEqual", []int{}},
|
||||
OpGreaterThan: {"OpGreaterThan", []int{}},
|
||||
|
||||
OpMinus: {"OpMinus", []int{}},
|
||||
OpBang: {"OpBang", []int{}},
|
||||
|
||||
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
|
||||
OpJump: {"OpJump", []int{2}},
|
||||
|
||||
OpNull: {"OpNull", []int{}},
|
||||
}
|
||||
|
||||
func Lookup(op byte) (*Definition, error) {
|
||||
def, ok := definitions[Opcode(op)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("opcode %d undefined", op)
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func Make(op Opcode, operands ...int) []byte {
|
||||
def, ok := definitions[op]
|
||||
if !ok {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
instructionLen := 1
|
||||
for _, w := range def.OperandWidths {
|
||||
instructionLen += w
|
||||
}
|
||||
|
||||
instruction := make([]byte, instructionLen)
|
||||
instruction[0] = byte(op)
|
||||
|
||||
offset := 1
|
||||
for i, o := range operands {
|
||||
width := def.OperandWidths[i]
|
||||
switch width {
|
||||
case 2:
|
||||
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
|
||||
}
|
||||
offset += width
|
||||
}
|
||||
|
||||
return instruction
|
||||
}
|
||||
|
||||
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
|
||||
operands := make([]int, len(def.OperandWidths))
|
||||
offset := 0
|
||||
|
||||
for i, width := range def.OperandWidths {
|
||||
switch width {
|
||||
case 2:
|
||||
operands[i] = int(ReadUint16(ins[offset:]))
|
||||
}
|
||||
|
||||
offset += width
|
||||
}
|
||||
|
||||
return operands, offset
|
||||
}
|
||||
|
||||
func ReadUint16(ins Instructions) uint16 {
|
||||
return binary.BigEndian.Uint16(ins)
|
||||
}
|
||||
83
wcig_code_1_2/04/src/monkey/code/code_test.go
Normal file
83
wcig_code_1_2/04/src/monkey/code/code_test.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package code
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMake(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
expected []byte
|
||||
}{
|
||||
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
|
||||
{OpAdd, []int{}, []byte{byte(OpAdd)}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
if len(instruction) != len(tt.expected) {
|
||||
t.Errorf("instruction has wrong length. want=%d, got=%d",
|
||||
len(tt.expected), len(instruction))
|
||||
}
|
||||
|
||||
for i, b := range tt.expected {
|
||||
if instruction[i] != tt.expected[i] {
|
||||
t.Errorf("wrong byte at pos %d. want=%d, got=%d",
|
||||
i, b, instruction[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionsString(t *testing.T) {
|
||||
instructions := []Instructions{
|
||||
Make(OpAdd),
|
||||
Make(OpConstant, 2),
|
||||
Make(OpConstant, 65535),
|
||||
}
|
||||
|
||||
expected := `0000 OpAdd
|
||||
0001 OpConstant 2
|
||||
0004 OpConstant 65535
|
||||
`
|
||||
|
||||
concatted := Instructions{}
|
||||
for _, ins := range instructions {
|
||||
concatted = append(concatted, ins...)
|
||||
}
|
||||
|
||||
if concatted.String() != expected {
|
||||
t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q",
|
||||
expected, concatted.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadOperands(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
bytesRead int
|
||||
}{
|
||||
{OpConstant, []int{65535}, 2},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
def, err := Lookup(byte(tt.op))
|
||||
if err != nil {
|
||||
t.Fatalf("definition not found: %q\n", err)
|
||||
}
|
||||
|
||||
operandsRead, n := ReadOperands(def, instruction[1:])
|
||||
if n != tt.bytesRead {
|
||||
t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
|
||||
}
|
||||
|
||||
for i, want := range tt.operands {
|
||||
if operandsRead[i] != want {
|
||||
t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
wcig_code_1_2/04/src/monkey/compiler/compiler.go
Normal file
231
wcig_code_1_2/04/src/monkey/compiler/compiler.go
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
instructions code.Instructions
|
||||
constants []object.Object
|
||||
|
||||
lastInstruction EmittedInstruction
|
||||
previousInstruction EmittedInstruction
|
||||
}
|
||||
|
||||
func New() *Compiler {
|
||||
return &Compiler{
|
||||
instructions: code.Instructions{},
|
||||
constants: []object.Object{},
|
||||
lastInstruction: EmittedInstruction{},
|
||||
previousInstruction: EmittedInstruction{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) Compile(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Program:
|
||||
for _, s := range node.Statements {
|
||||
err := c.Compile(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
err := c.Compile(node.Expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(code.OpPop)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
if node.Operator == "<" {
|
||||
err := c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(code.OpGreaterThan)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "+":
|
||||
c.emit(code.OpAdd)
|
||||
case "-":
|
||||
c.emit(code.OpSub)
|
||||
case "*":
|
||||
c.emit(code.OpMul)
|
||||
case "/":
|
||||
c.emit(code.OpDiv)
|
||||
case ">":
|
||||
c.emit(code.OpGreaterThan)
|
||||
case "==":
|
||||
c.emit(code.OpEqual)
|
||||
case "!=":
|
||||
c.emit(code.OpNotEqual)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
|
||||
case *ast.IntegerLiteral:
|
||||
integer := &object.Integer{Value: node.Value}
|
||||
c.emit(code.OpConstant, c.addConstant(integer))
|
||||
|
||||
case *ast.Boolean:
|
||||
if node.Value {
|
||||
c.emit(code.OpTrue)
|
||||
} else {
|
||||
c.emit(code.OpFalse)
|
||||
}
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
err := c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "!":
|
||||
c.emit(code.OpBang)
|
||||
case "-":
|
||||
c.emit(code.OpMinus)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
|
||||
case *ast.IfExpression:
|
||||
err := c.Compile(node.Condition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Emit an `OpJumpNotTruthy` with a bogus value
|
||||
jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999)
|
||||
|
||||
err = c.Compile(node.Consequence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.lastInstructionIsPop() {
|
||||
c.removeLastPop()
|
||||
}
|
||||
|
||||
// Emit an `OpJump` with a bogus value
|
||||
jumpPos := c.emit(code.OpJump, 9999)
|
||||
|
||||
afterConsequencePos := len(c.instructions)
|
||||
c.changeOperand(jumpNotTruthyPos, afterConsequencePos)
|
||||
|
||||
if node.Alternative == nil {
|
||||
c.emit(code.OpNull)
|
||||
} else {
|
||||
err := c.Compile(node.Alternative)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.lastInstructionIsPop() {
|
||||
c.removeLastPop()
|
||||
}
|
||||
}
|
||||
|
||||
afterAlternativePos := len(c.instructions)
|
||||
c.changeOperand(jumpPos, afterAlternativePos)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
for _, s := range node.Statements {
|
||||
err := c.Compile(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) Bytecode() *Bytecode {
|
||||
return &Bytecode{
|
||||
Instructions: c.instructions,
|
||||
Constants: c.constants,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addConstant(obj object.Object) int {
|
||||
c.constants = append(c.constants, obj)
|
||||
return len(c.constants) - 1
|
||||
}
|
||||
|
||||
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
|
||||
ins := code.Make(op, operands...)
|
||||
pos := c.addInstruction(ins)
|
||||
|
||||
c.setLastInstruction(op, pos)
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func (c *Compiler) addInstruction(ins []byte) int {
|
||||
posNewInstruction := len(c.instructions)
|
||||
c.instructions = append(c.instructions, ins...)
|
||||
return posNewInstruction
|
||||
}
|
||||
|
||||
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
|
||||
previous := c.lastInstruction
|
||||
last := EmittedInstruction{Opcode: op, Position: pos}
|
||||
|
||||
c.previousInstruction = previous
|
||||
c.lastInstruction = last
|
||||
}
|
||||
|
||||
func (c *Compiler) lastInstructionIsPop() bool {
|
||||
return c.lastInstruction.Opcode == code.OpPop
|
||||
}
|
||||
|
||||
func (c *Compiler) removeLastPop() {
|
||||
c.instructions = c.instructions[:c.lastInstruction.Position]
|
||||
c.lastInstruction = c.previousInstruction
|
||||
}
|
||||
|
||||
func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) {
|
||||
for i := 0; i < len(newInstruction); i++ {
|
||||
c.instructions[pos+i] = newInstruction[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) changeOperand(opPos int, operand int) {
|
||||
op := code.Opcode(c.instructions[opPos])
|
||||
newInstruction := code.Make(op, operand)
|
||||
|
||||
c.replaceInstruction(opPos, newInstruction)
|
||||
}
|
||||
|
||||
type Bytecode struct {
|
||||
Instructions code.Instructions
|
||||
Constants []object.Object
|
||||
}
|
||||
|
||||
type EmittedInstruction struct {
|
||||
Opcode code.Opcode
|
||||
Position int
|
||||
}
|
||||
332
wcig_code_1_2/04/src/monkey/compiler/compiler_test.go
Normal file
332
wcig_code_1_2/04/src/monkey/compiler/compiler_test.go
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "1 + 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpAdd),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1; 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpPop),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 - 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpSub),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 * 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpMul),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "2 / 1",
|
||||
expectedConstants: []interface{}{2, 1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpDiv),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "-1",
|
||||
expectedConstants: []interface{}{1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpMinus),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBooleanExpressions(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "true",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 > 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpGreaterThan),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 < 2",
|
||||
expectedConstants: []interface{}{2, 1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpGreaterThan),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 == 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 != 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpNotEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "true == false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "true != false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpNotEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "!true",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpBang),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestConditionals(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: `
|
||||
if (true) { 10 }; 3333;
|
||||
`,
|
||||
expectedConstants: []interface{}{10, 3333},
|
||||
expectedInstructions: []code.Instructions{
|
||||
// 0000
|
||||
code.Make(code.OpTrue),
|
||||
// 0001
|
||||
code.Make(code.OpJumpNotTruthy, 10),
|
||||
// 0004
|
||||
code.Make(code.OpConstant, 0),
|
||||
// 0007
|
||||
code.Make(code.OpJump, 11),
|
||||
// 0010
|
||||
code.Make(code.OpNull),
|
||||
// 0011
|
||||
code.Make(code.OpPop),
|
||||
// 0012
|
||||
code.Make(code.OpConstant, 1),
|
||||
// 0015
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `
|
||||
if (true) { 10 } else { 20 }; 3333;
|
||||
`,
|
||||
expectedConstants: []interface{}{10, 20, 3333},
|
||||
expectedInstructions: []code.Instructions{
|
||||
// 0000
|
||||
code.Make(code.OpTrue),
|
||||
// 0001
|
||||
code.Make(code.OpJumpNotTruthy, 10),
|
||||
// 0004
|
||||
code.Make(code.OpConstant, 0),
|
||||
// 0007
|
||||
code.Make(code.OpJump, 13),
|
||||
// 0010
|
||||
code.Make(code.OpConstant, 1),
|
||||
// 0013
|
||||
code.Make(code.OpPop),
|
||||
// 0014
|
||||
code.Make(code.OpConstant, 2),
|
||||
// 0017
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
type compilerTestCase struct {
|
||||
input string
|
||||
expectedConstants []interface{}
|
||||
expectedInstructions []code.Instructions
|
||||
}
|
||||
|
||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
compiler := New()
|
||||
err := compiler.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
bytecode := compiler.Bytecode()
|
||||
|
||||
err = testInstructions(tt.expectedInstructions, bytecode.Instructions)
|
||||
if err != nil {
|
||||
t.Fatalf("testInstructions failed: %s", err)
|
||||
}
|
||||
|
||||
err = testConstants(t, tt.expectedConstants, bytecode.Constants)
|
||||
if err != nil {
|
||||
t.Fatalf("testConstants failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testInstructions(
|
||||
expected []code.Instructions,
|
||||
actual code.Instructions,
|
||||
) error {
|
||||
concatted := concatInstructions(expected)
|
||||
|
||||
if len(actual) != len(concatted) {
|
||||
return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q",
|
||||
concatted, actual)
|
||||
}
|
||||
|
||||
for i, ins := range concatted {
|
||||
if actual[i] != ins {
|
||||
return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q",
|
||||
i, concatted, actual)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func concatInstructions(s []code.Instructions) code.Instructions {
|
||||
out := code.Instructions{}
|
||||
|
||||
for _, ins := range s {
|
||||
out = append(out, ins...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func testConstants(
|
||||
t *testing.T,
|
||||
expected []interface{},
|
||||
actual []object.Object,
|
||||
) error {
|
||||
if len(expected) != len(actual) {
|
||||
return fmt.Errorf("wrong number of constants. got=%d, want=%d",
|
||||
len(actual), len(expected))
|
||||
}
|
||||
|
||||
for i, constant := range expected {
|
||||
switch constant := constant.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(constant), actual[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("constant %d - testIntegerObject failed: %s",
|
||||
i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
wcig_code_1_2/04/src/monkey/evaluator/builtins.go
Normal file
117
wcig_code_1_2/04/src/monkey/evaluator/builtins.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"len": &object.Builtin{Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
|
||||
switch arg := args[0].(type) {
|
||||
case *object.Array:
|
||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
||||
case *object.String:
|
||||
return &object.Integer{Value: int64(len(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
},
|
||||
},
|
||||
"puts": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"first": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"last": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"rest": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]object.Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &object.Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"push": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]object.Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &object.Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
}
|
||||
442
wcig_code_1_2/04/src/monkey/evaluator/evaluator.go
Normal file
442
wcig_code_1_2/04/src/monkey/evaluator/evaluator.go
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var (
|
||||
NULL = &object.Null{}
|
||||
TRUE = &object.Boolean{Value: true}
|
||||
FALSE = &object.Boolean{Value: false}
|
||||
)
|
||||
|
||||
func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
switch node := node.(type) {
|
||||
|
||||
// Statements
|
||||
case *ast.Program:
|
||||
return evalProgram(node, env)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
return evalBlockStatement(node, env)
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
return Eval(node.Expression, env)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
val := Eval(node.ReturnValue, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
return &object.ReturnValue{Value: val}
|
||||
|
||||
case *ast.LetStatement:
|
||||
val := Eval(node.Value, env)
|
||||
if isError(val) {
|
||||
return val
|
||||
}
|
||||
env.Set(node.Name.Value, val)
|
||||
|
||||
// Expressions
|
||||
case *ast.IntegerLiteral:
|
||||
return &object.Integer{Value: node.Value}
|
||||
|
||||
case *ast.StringLiteral:
|
||||
return &object.String{Value: node.Value}
|
||||
|
||||
case *ast.Boolean:
|
||||
return nativeBoolToBooleanObject(node.Value)
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
return evalPrefixExpression(node.Operator, right)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
|
||||
right := Eval(node.Right, env)
|
||||
if isError(right) {
|
||||
return right
|
||||
}
|
||||
|
||||
return evalInfixExpression(node.Operator, left, right)
|
||||
|
||||
case *ast.IfExpression:
|
||||
return evalIfExpression(node, env)
|
||||
|
||||
case *ast.Identifier:
|
||||
return evalIdentifier(node, env)
|
||||
|
||||
case *ast.FunctionLiteral:
|
||||
params := node.Parameters
|
||||
body := node.Body
|
||||
return &object.Function{Parameters: params, Env: env, Body: body}
|
||||
|
||||
case *ast.CallExpression:
|
||||
function := Eval(node.Function, env)
|
||||
if isError(function) {
|
||||
return function
|
||||
}
|
||||
|
||||
args := evalExpressions(node.Arguments, env)
|
||||
if len(args) == 1 && isError(args[0]) {
|
||||
return args[0]
|
||||
}
|
||||
|
||||
return applyFunction(function, args)
|
||||
|
||||
case *ast.ArrayLiteral:
|
||||
elements := evalExpressions(node.Elements, env)
|
||||
if len(elements) == 1 && isError(elements[0]) {
|
||||
return elements[0]
|
||||
}
|
||||
return &object.Array{Elements: elements}
|
||||
|
||||
case *ast.IndexExpression:
|
||||
left := Eval(node.Left, env)
|
||||
if isError(left) {
|
||||
return left
|
||||
}
|
||||
index := Eval(node.Index, env)
|
||||
if isError(index) {
|
||||
return index
|
||||
}
|
||||
return evalIndexExpression(left, index)
|
||||
|
||||
case *ast.HashLiteral:
|
||||
return evalHashLiteral(node, env)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range program.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
switch result := result.(type) {
|
||||
case *object.ReturnValue:
|
||||
return result.Value
|
||||
case *object.Error:
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func evalBlockStatement(
|
||||
block *ast.BlockStatement,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
var result object.Object
|
||||
|
||||
for _, statement := range block.Statements {
|
||||
result = Eval(statement, env)
|
||||
|
||||
if result != nil {
|
||||
rt := result.Type()
|
||||
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return TRUE
|
||||
}
|
||||
return FALSE
|
||||
}
|
||||
|
||||
func evalPrefixExpression(operator string, right object.Object) object.Object {
|
||||
switch operator {
|
||||
case "!":
|
||||
return evalBangOperatorExpression(right)
|
||||
case "-":
|
||||
return evalMinusPrefixOperatorExpression(right)
|
||||
default:
|
||||
return newError("unknown operator: %s%s", operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
|
||||
return evalIntegerInfixExpression(operator, left, right)
|
||||
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
|
||||
return evalStringInfixExpression(operator, left, right)
|
||||
case operator == "==":
|
||||
return nativeBoolToBooleanObject(left == right)
|
||||
case operator == "!=":
|
||||
return nativeBoolToBooleanObject(left != right)
|
||||
case left.Type() != right.Type():
|
||||
return newError("type mismatch: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalBangOperatorExpression(right object.Object) object.Object {
|
||||
switch right {
|
||||
case TRUE:
|
||||
return FALSE
|
||||
case FALSE:
|
||||
return TRUE
|
||||
case NULL:
|
||||
return TRUE
|
||||
default:
|
||||
return FALSE
|
||||
}
|
||||
}
|
||||
|
||||
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
|
||||
if right.Type() != object.INTEGER_OBJ {
|
||||
return newError("unknown operator: -%s", right.Type())
|
||||
}
|
||||
|
||||
value := right.(*object.Integer).Value
|
||||
return &object.Integer{Value: -value}
|
||||
}
|
||||
|
||||
func evalIntegerInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
leftVal := left.(*object.Integer).Value
|
||||
rightVal := right.(*object.Integer).Value
|
||||
|
||||
switch operator {
|
||||
case "+":
|
||||
return &object.Integer{Value: leftVal + rightVal}
|
||||
case "-":
|
||||
return &object.Integer{Value: leftVal - rightVal}
|
||||
case "*":
|
||||
return &object.Integer{Value: leftVal * rightVal}
|
||||
case "/":
|
||||
return &object.Integer{Value: leftVal / rightVal}
|
||||
case "<":
|
||||
return nativeBoolToBooleanObject(leftVal < rightVal)
|
||||
case ">":
|
||||
return nativeBoolToBooleanObject(leftVal > rightVal)
|
||||
case "==":
|
||||
return nativeBoolToBooleanObject(leftVal == rightVal)
|
||||
case "!=":
|
||||
return nativeBoolToBooleanObject(leftVal != rightVal)
|
||||
default:
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalStringInfixExpression(
|
||||
operator string,
|
||||
left, right object.Object,
|
||||
) object.Object {
|
||||
if operator != "+" {
|
||||
return newError("unknown operator: %s %s %s",
|
||||
left.Type(), operator, right.Type())
|
||||
}
|
||||
|
||||
leftVal := left.(*object.String).Value
|
||||
rightVal := right.(*object.String).Value
|
||||
return &object.String{Value: leftVal + rightVal}
|
||||
}
|
||||
|
||||
func evalIfExpression(
|
||||
ie *ast.IfExpression,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
condition := Eval(ie.Condition, env)
|
||||
if isError(condition) {
|
||||
return condition
|
||||
}
|
||||
|
||||
if isTruthy(condition) {
|
||||
return Eval(ie.Consequence, env)
|
||||
} else if ie.Alternative != nil {
|
||||
return Eval(ie.Alternative, env)
|
||||
} else {
|
||||
return NULL
|
||||
}
|
||||
}
|
||||
|
||||
func evalIdentifier(
|
||||
node *ast.Identifier,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
if val, ok := env.Get(node.Value); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
if builtin, ok := builtins[node.Value]; ok {
|
||||
return builtin
|
||||
}
|
||||
|
||||
return newError("identifier not found: " + node.Value)
|
||||
}
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj {
|
||||
case NULL:
|
||||
return false
|
||||
case TRUE:
|
||||
return true
|
||||
case FALSE:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func newError(format string, a ...interface{}) *object.Error {
|
||||
return &object.Error{Message: fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
func isError(obj object.Object) bool {
|
||||
if obj != nil {
|
||||
return obj.Type() == object.ERROR_OBJ
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func evalExpressions(
|
||||
exps []ast.Expression,
|
||||
env *object.Environment,
|
||||
) []object.Object {
|
||||
var result []object.Object
|
||||
|
||||
for _, e := range exps {
|
||||
evaluated := Eval(e, env)
|
||||
if isError(evaluated) {
|
||||
return []object.Object{evaluated}
|
||||
}
|
||||
result = append(result, evaluated)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func applyFunction(fn object.Object, args []object.Object) object.Object {
|
||||
switch fn := fn.(type) {
|
||||
|
||||
case *object.Function:
|
||||
extendedEnv := extendFunctionEnv(fn, args)
|
||||
evaluated := Eval(fn.Body, extendedEnv)
|
||||
return unwrapReturnValue(evaluated)
|
||||
|
||||
case *object.Builtin:
|
||||
return fn.Fn(args...)
|
||||
|
||||
default:
|
||||
return newError("not a function: %s", fn.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func extendFunctionEnv(
|
||||
fn *object.Function,
|
||||
args []object.Object,
|
||||
) *object.Environment {
|
||||
env := object.NewEnclosedEnvironment(fn.Env)
|
||||
|
||||
for paramIdx, param := range fn.Parameters {
|
||||
env.Set(param.Value, args[paramIdx])
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func unwrapReturnValue(obj object.Object) object.Object {
|
||||
if returnValue, ok := obj.(*object.ReturnValue); ok {
|
||||
return returnValue.Value
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func evalIndexExpression(left, index object.Object) object.Object {
|
||||
switch {
|
||||
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
|
||||
return evalArrayIndexExpression(left, index)
|
||||
case left.Type() == object.HASH_OBJ:
|
||||
return evalHashIndexExpression(left, index)
|
||||
default:
|
||||
return newError("index operator not supported: %s", left.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func evalArrayIndexExpression(array, index object.Object) object.Object {
|
||||
arrayObject := array.(*object.Array)
|
||||
idx := index.(*object.Integer).Value
|
||||
max := int64(len(arrayObject.Elements) - 1)
|
||||
|
||||
if idx < 0 || idx > max {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return arrayObject.Elements[idx]
|
||||
}
|
||||
|
||||
func evalHashLiteral(
|
||||
node *ast.HashLiteral,
|
||||
env *object.Environment,
|
||||
) object.Object {
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
|
||||
for keyNode, valueNode := range node.Pairs {
|
||||
key := Eval(keyNode, env)
|
||||
if isError(key) {
|
||||
return key
|
||||
}
|
||||
|
||||
hashKey, ok := key.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", key.Type())
|
||||
}
|
||||
|
||||
value := Eval(valueNode, env)
|
||||
if isError(value) {
|
||||
return value
|
||||
}
|
||||
|
||||
hashed := hashKey.HashKey()
|
||||
pairs[hashed] = object.HashPair{Key: key, Value: value}
|
||||
}
|
||||
|
||||
return &object.Hash{Pairs: pairs}
|
||||
}
|
||||
|
||||
func evalHashIndexExpression(hash, index object.Object) object.Object {
|
||||
hashObject := hash.(*object.Hash)
|
||||
|
||||
key, ok := index.(object.Hashable)
|
||||
if !ok {
|
||||
return newError("unusable as hash key: %s", index.Type())
|
||||
}
|
||||
|
||||
pair, ok := hashObject.Pairs[key.HashKey()]
|
||||
if !ok {
|
||||
return NULL
|
||||
}
|
||||
|
||||
return pair.Value
|
||||
}
|
||||
629
wcig_code_1_2/04/src/monkey/evaluator/evaluator_test.go
Normal file
629
wcig_code_1_2/04/src/monkey/evaluator/evaluator_test.go
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvalIntegerExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"5", 5},
|
||||
{"10", 10},
|
||||
{"-5", -5},
|
||||
{"-10", -10},
|
||||
{"5 + 5 + 5 + 5 - 10", 10},
|
||||
{"2 * 2 * 2 * 2 * 2", 32},
|
||||
{"-50 + 100 + -50", 0},
|
||||
{"5 * 2 + 10", 20},
|
||||
{"5 + 2 * 10", 25},
|
||||
{"20 + 2 * -10", 0},
|
||||
{"50 / 2 * 2 + 10", 60},
|
||||
{"2 * (5 + 10)", 30},
|
||||
{"3 * 3 * 3 + 10", 37},
|
||||
{"3 * (3 * 3) + 10", 37},
|
||||
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalBooleanExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1 < 2", true},
|
||||
{"1 > 2", false},
|
||||
{"1 < 1", false},
|
||||
{"1 > 1", false},
|
||||
{"1 == 1", true},
|
||||
{"1 != 1", false},
|
||||
{"1 == 2", false},
|
||||
{"1 != 2", true},
|
||||
{"true == true", true},
|
||||
{"false == false", true},
|
||||
{"true == false", false},
|
||||
{"true != false", true},
|
||||
{"false != true", true},
|
||||
{"(1 < 2) == true", true},
|
||||
{"(1 < 2) == false", false},
|
||||
{"(1 > 2) == true", false},
|
||||
{"(1 > 2) == false", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBangOperator(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"!true", false},
|
||||
{"!false", true},
|
||||
{"!5", false},
|
||||
{"!!true", true},
|
||||
{"!!false", false},
|
||||
{"!!5", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testBooleanObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfElseExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"if (true) { 10 }", 10},
|
||||
{"if (false) { 10 }", nil},
|
||||
{"if (1) { 10 }", 10},
|
||||
{"if (1 < 2) { 10 }", 10},
|
||||
{"if (1 > 2) { 10 }", nil},
|
||||
{"if (1 > 2) { 10 } else { 20 }", 20},
|
||||
{"if (1 < 2) { 10 } else { 20 }", 10},
|
||||
}
|
||||
|
||||
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 TestReturnStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"return 10;", 10},
|
||||
{"return 10; 9;", 10},
|
||||
{"return 2 * 5; 9;", 10},
|
||||
{"9; return 2 * 5; 9;", 10},
|
||||
{"if (10 > 1) { return 10; }", 10},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
return x;
|
||||
x + 10;
|
||||
};
|
||||
f(10);`,
|
||||
10,
|
||||
},
|
||||
{
|
||||
`
|
||||
let f = fn(x) {
|
||||
let result = x + 10;
|
||||
return result;
|
||||
return 10;
|
||||
};
|
||||
f(10);`,
|
||||
20,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
testIntegerObject(t, evaluated, tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedMessage string
|
||||
}{
|
||||
{
|
||||
"5 + true;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5 + true; 5;",
|
||||
"type mismatch: INTEGER + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"-true",
|
||||
"unknown operator: -BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"true + false + true + false;",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"5; true + false; 5",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`"Hello" - "World"`,
|
||||
"unknown operator: STRING - STRING",
|
||||
},
|
||||
{
|
||||
"if (10 > 1) { true + false; }",
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
`
|
||||
if (10 > 1) {
|
||||
if (10 > 1) {
|
||||
return true + false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
`,
|
||||
"unknown operator: BOOLEAN + BOOLEAN",
|
||||
},
|
||||
{
|
||||
"foobar",
|
||||
"identifier not found: foobar",
|
||||
},
|
||||
{
|
||||
`{"name": "Monkey"}[fn(x) { x }];`,
|
||||
"unusable as hash key: FUNCTION",
|
||||
},
|
||||
{
|
||||
`999[1]`,
|
||||
"index operator not supported: INTEGER",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("no error object returned. got=%T(%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if errObj.Message != tt.expectedMessage {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
tt.expectedMessage, errObj.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLetStatements(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let a = 5; a;", 5},
|
||||
{"let a = 5 * 5; a;", 25},
|
||||
{"let a = 5; let b = a; b;", 5},
|
||||
{"let a = 5; let b = a; let c = a + b + 5; c;", 15},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionObject(t *testing.T) {
|
||||
input := "fn(x) { x + 2; };"
|
||||
|
||||
evaluated := testEval(input)
|
||||
fn, ok := evaluated.(*object.Function)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(fn.Parameters) != 1 {
|
||||
t.Fatalf("function has wrong parameters. Parameters=%+v",
|
||||
fn.Parameters)
|
||||
}
|
||||
|
||||
if fn.Parameters[0].String() != "x" {
|
||||
t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
|
||||
}
|
||||
|
||||
expectedBody := "(x + 2)"
|
||||
|
||||
if fn.Body.String() != expectedBody {
|
||||
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionApplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected int64
|
||||
}{
|
||||
{"let identity = fn(x) { x; }; identity(5);", 5},
|
||||
{"let identity = fn(x) { return x; }; identity(5);", 5},
|
||||
{"let double = fn(x) { x * 2; }; double(5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
|
||||
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
|
||||
{"fn(x) { x; }(5)", 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testIntegerObject(t, testEval(tt.input), tt.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnclosingEnvironments(t *testing.T) {
|
||||
input := `
|
||||
let first = 10;
|
||||
let second = 10;
|
||||
let third = 10;
|
||||
|
||||
let ourFunction = fn(first) {
|
||||
let second = 20;
|
||||
|
||||
first + second + third;
|
||||
};
|
||||
|
||||
ourFunction(20) + first + second;`
|
||||
|
||||
testIntegerObject(t, testEval(input), 70)
|
||||
}
|
||||
|
||||
func TestClosures(t *testing.T) {
|
||||
input := `
|
||||
let newAdder = fn(x) {
|
||||
fn(y) { x + y };
|
||||
};
|
||||
|
||||
let addTwo = newAdder(2);
|
||||
addTwo(2);`
|
||||
|
||||
testIntegerObject(t, testEval(input), 4)
|
||||
}
|
||||
|
||||
func TestStringLiteral(t *testing.T) {
|
||||
input := `"Hello World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConcatenation(t *testing.T) {
|
||||
input := `"Hello" + " " + "World!"`
|
||||
|
||||
evaluated := testEval(input)
|
||||
str, ok := evaluated.(*object.String)
|
||||
if !ok {
|
||||
t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if str.Value != "Hello World!" {
|
||||
t.Errorf("String has wrong value. got=%q", str.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinFunctions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{`len("")`, 0},
|
||||
{`len("four")`, 4},
|
||||
{`len("hello world")`, 11},
|
||||
{`len(1)`, "argument to `len` not supported, got INTEGER"},
|
||||
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
|
||||
{`len([1, 2, 3])`, 3},
|
||||
{`len([])`, 0},
|
||||
{`puts("hello", "world!")`, nil},
|
||||
{`first([1, 2, 3])`, 1},
|
||||
{`first([])`, nil},
|
||||
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER"},
|
||||
{`last([1, 2, 3])`, 3},
|
||||
{`last([])`, nil},
|
||||
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER"},
|
||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||
{`rest([])`, nil},
|
||||
{`push([], 1)`, []int{1}},
|
||||
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
evaluated := testEval(tt.input)
|
||||
|
||||
switch expected := tt.expected.(type) {
|
||||
case int:
|
||||
testIntegerObject(t, evaluated, int64(expected))
|
||||
case nil:
|
||||
testNullObject(t, evaluated)
|
||||
case string:
|
||||
errObj, ok := evaluated.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("object is not Error. got=%T (%+v)",
|
||||
evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
if errObj.Message != expected {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q",
|
||||
expected, errObj.Message)
|
||||
}
|
||||
case []int:
|
||||
array, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(array.Elements) != len(expected) {
|
||||
t.Errorf("wrong num of elements. want=%d, got=%d",
|
||||
len(expected), len(array.Elements))
|
||||
continue
|
||||
}
|
||||
|
||||
for i, expectedElem := range expected {
|
||||
testIntegerObject(t, array.Elements[i], int64(expectedElem))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayLiterals(t *testing.T) {
|
||||
input := "[1, 2 * 2, 3 + 3]"
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Array)
|
||||
if !ok {
|
||||
t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
if len(result.Elements) != 3 {
|
||||
t.Fatalf("array has wrong num of elements. got=%d",
|
||||
len(result.Elements))
|
||||
}
|
||||
|
||||
testIntegerObject(t, result.Elements[0], 1)
|
||||
testIntegerObject(t, result.Elements[1], 4)
|
||||
testIntegerObject(t, result.Elements[2], 6)
|
||||
}
|
||||
|
||||
func TestArrayIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"[1, 2, 3][0]",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][2]",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let i = 0; [1][i];",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][1 + 1];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[2];",
|
||||
3,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
|
||||
6,
|
||||
},
|
||||
{
|
||||
"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][3]",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"[1, 2, 3][-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 TestHashLiterals(t *testing.T) {
|
||||
input := `let two = "two";
|
||||
{
|
||||
"one": 10 - 9,
|
||||
two: 1 + 1,
|
||||
"thr" + "ee": 6 / 2,
|
||||
4: 4,
|
||||
true: 5,
|
||||
false: 6
|
||||
}`
|
||||
|
||||
evaluated := testEval(input)
|
||||
result, ok := evaluated.(*object.Hash)
|
||||
if !ok {
|
||||
t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
|
||||
}
|
||||
|
||||
expected := map[object.HashKey]int64{
|
||||
(&object.String{Value: "one"}).HashKey(): 1,
|
||||
(&object.String{Value: "two"}).HashKey(): 2,
|
||||
(&object.String{Value: "three"}).HashKey(): 3,
|
||||
(&object.Integer{Value: 4}).HashKey(): 4,
|
||||
TRUE.HashKey(): 5,
|
||||
FALSE.HashKey(): 6,
|
||||
}
|
||||
|
||||
if len(result.Pairs) != len(expected) {
|
||||
t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
|
||||
}
|
||||
|
||||
for expectedKey, expectedValue := range expected {
|
||||
pair, ok := result.Pairs[expectedKey]
|
||||
if !ok {
|
||||
t.Errorf("no pair for given key in Pairs")
|
||||
}
|
||||
|
||||
testIntegerObject(t, pair.Value, expectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashIndexExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
`{"foo": 5}["foo"]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{"foo": 5}["bar"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`let key = "foo"; {"foo": 5}[key]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{}["foo"]`,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`{5: 5}[5]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{true: 5}[true]`,
|
||||
5,
|
||||
},
|
||||
{
|
||||
`{false: 5}[false]`,
|
||||
5,
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
program := p.ParseProgram()
|
||||
env := object.NewEnvironment()
|
||||
|
||||
return Eval(program, env)
|
||||
}
|
||||
|
||||
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
|
||||
result, ok := obj.(*object.Integer)
|
||||
if !ok {
|
||||
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
|
||||
result, ok := obj.(*object.Boolean)
|
||||
if !ok {
|
||||
t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
if result.Value != expected {
|
||||
t.Errorf("object has wrong value. got=%t, want=%t",
|
||||
result.Value, expected)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func testNullObject(t *testing.T, obj object.Object) bool {
|
||||
if obj != NULL {
|
||||
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
3
wcig_code_1_2/04/src/monkey/go.mod
Normal file
3
wcig_code_1_2/04/src/monkey/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module monkey
|
||||
|
||||
go 1.14
|
||||
157
wcig_code_1_2/04/src/monkey/lexer/lexer.go
Normal file
157
wcig_code_1_2/04/src/monkey/lexer/lexer.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package lexer
|
||||
|
||||
import "monkey/token"
|
||||
|
||||
type Lexer struct {
|
||||
input string
|
||||
position int // current position in input (points to current char)
|
||||
readPosition int // current reading position in input (after current char)
|
||||
ch byte // current char under examination
|
||||
}
|
||||
|
||||
func New(input string) *Lexer {
|
||||
l := &Lexer{input: input}
|
||||
l.readChar()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() token.Token {
|
||||
var tok token.Token
|
||||
|
||||
l.skipWhitespace()
|
||||
|
||||
switch l.ch {
|
||||
case '=':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.ASSIGN, l.ch)
|
||||
}
|
||||
case '+':
|
||||
tok = newToken(token.PLUS, l.ch)
|
||||
case '-':
|
||||
tok = newToken(token.MINUS, l.ch)
|
||||
case '!':
|
||||
if l.peekChar() == '=' {
|
||||
ch := l.ch
|
||||
l.readChar()
|
||||
literal := string(ch) + string(l.ch)
|
||||
tok = token.Token{Type: token.NOT_EQ, Literal: literal}
|
||||
} else {
|
||||
tok = newToken(token.BANG, l.ch)
|
||||
}
|
||||
case '/':
|
||||
tok = newToken(token.SLASH, l.ch)
|
||||
case '*':
|
||||
tok = newToken(token.ASTERISK, l.ch)
|
||||
case '<':
|
||||
tok = newToken(token.LT, l.ch)
|
||||
case '>':
|
||||
tok = newToken(token.GT, l.ch)
|
||||
case ';':
|
||||
tok = newToken(token.SEMICOLON, l.ch)
|
||||
case ':':
|
||||
tok = newToken(token.COLON, l.ch)
|
||||
case ',':
|
||||
tok = newToken(token.COMMA, l.ch)
|
||||
case '{':
|
||||
tok = newToken(token.LBRACE, l.ch)
|
||||
case '}':
|
||||
tok = newToken(token.RBRACE, l.ch)
|
||||
case '(':
|
||||
tok = newToken(token.LPAREN, l.ch)
|
||||
case ')':
|
||||
tok = newToken(token.RPAREN, l.ch)
|
||||
case '"':
|
||||
tok.Type = token.STRING
|
||||
tok.Literal = l.readString()
|
||||
case '[':
|
||||
tok = newToken(token.LBRACKET, l.ch)
|
||||
case ']':
|
||||
tok = newToken(token.RBRACKET, l.ch)
|
||||
case 0:
|
||||
tok.Literal = ""
|
||||
tok.Type = token.EOF
|
||||
default:
|
||||
if isLetter(l.ch) {
|
||||
tok.Literal = l.readIdentifier()
|
||||
tok.Type = token.LookupIdent(tok.Literal)
|
||||
return tok
|
||||
} else if isDigit(l.ch) {
|
||||
tok.Type = token.INT
|
||||
tok.Literal = l.readNumber()
|
||||
return tok
|
||||
} else {
|
||||
tok = newToken(token.ILLEGAL, l.ch)
|
||||
}
|
||||
}
|
||||
|
||||
l.readChar()
|
||||
return tok
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
|
||||
l.readChar()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readChar() {
|
||||
if l.readPosition >= len(l.input) {
|
||||
l.ch = 0
|
||||
} else {
|
||||
l.ch = l.input[l.readPosition]
|
||||
}
|
||||
l.position = l.readPosition
|
||||
l.readPosition += 1
|
||||
}
|
||||
|
||||
func (l *Lexer) peekChar() byte {
|
||||
if l.readPosition >= len(l.input) {
|
||||
return 0
|
||||
} else {
|
||||
return l.input[l.readPosition]
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readIdentifier() string {
|
||||
position := l.position
|
||||
for isLetter(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readNumber() string {
|
||||
position := l.position
|
||||
for isDigit(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func (l *Lexer) readString() string {
|
||||
position := l.position + 1
|
||||
for {
|
||||
l.readChar()
|
||||
if l.ch == '"' || l.ch == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return l.input[position:l.position]
|
||||
}
|
||||
|
||||
func isLetter(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
func newToken(tokenType token.TokenType, ch byte) token.Token {
|
||||
return token.Token{Type: tokenType, Literal: string(ch)}
|
||||
}
|
||||
143
wcig_code_1_2/04/src/monkey/lexer/lexer_test.go
Normal file
143
wcig_code_1_2/04/src/monkey/lexer/lexer_test.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package lexer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"monkey/token"
|
||||
)
|
||||
|
||||
func TestNextToken(t *testing.T) {
|
||||
input := `let five = 5;
|
||||
let ten = 10;
|
||||
|
||||
let add = fn(x, y) {
|
||||
x + y;
|
||||
};
|
||||
|
||||
let result = add(five, ten);
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
"foobar"
|
||||
"foo bar"
|
||||
[1, 2];
|
||||
{"foo": "bar"}
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
expectedType token.TokenType
|
||||
expectedLiteral string
|
||||
}{
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "five"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "ten"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "add"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.FUNCTION, "fn"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "x"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "y"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.IDENT, "x"},
|
||||
{token.PLUS, "+"},
|
||||
{token.IDENT, "y"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LET, "let"},
|
||||
{token.IDENT, "result"},
|
||||
{token.ASSIGN, "="},
|
||||
{token.IDENT, "add"},
|
||||
{token.LPAREN, "("},
|
||||
{token.IDENT, "five"},
|
||||
{token.COMMA, ","},
|
||||
{token.IDENT, "ten"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.BANG, "!"},
|
||||
{token.MINUS, "-"},
|
||||
{token.SLASH, "/"},
|
||||
{token.ASTERISK, "*"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.GT, ">"},
|
||||
{token.INT, "5"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.IF, "if"},
|
||||
{token.LPAREN, "("},
|
||||
{token.INT, "5"},
|
||||
{token.LT, "<"},
|
||||
{token.INT, "10"},
|
||||
{token.RPAREN, ")"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.TRUE, "true"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.ELSE, "else"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.RETURN, "return"},
|
||||
{token.FALSE, "false"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.INT, "10"},
|
||||
{token.EQ, "=="},
|
||||
{token.INT, "10"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.INT, "10"},
|
||||
{token.NOT_EQ, "!="},
|
||||
{token.INT, "9"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.STRING, "foobar"},
|
||||
{token.STRING, "foo bar"},
|
||||
{token.LBRACKET, "["},
|
||||
{token.INT, "1"},
|
||||
{token.COMMA, ","},
|
||||
{token.INT, "2"},
|
||||
{token.RBRACKET, "]"},
|
||||
{token.SEMICOLON, ";"},
|
||||
{token.LBRACE, "{"},
|
||||
{token.STRING, "foo"},
|
||||
{token.COLON, ":"},
|
||||
{token.STRING, "bar"},
|
||||
{token.RBRACE, "}"},
|
||||
{token.EOF, ""},
|
||||
}
|
||||
|
||||
l := New(input)
|
||||
|
||||
for i, tt := range tests {
|
||||
tok := l.NextToken()
|
||||
|
||||
if tok.Type != tt.expectedType {
|
||||
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
|
||||
i, tt.expectedType, tok.Type)
|
||||
}
|
||||
|
||||
if tok.Literal != tt.expectedLiteral {
|
||||
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
|
||||
i, tt.expectedLiteral, tok.Literal)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
wcig_code_1_2/04/src/monkey/main.go
Normal file
19
wcig_code_1_2/04/src/monkey/main.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/repl"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func main() {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Hello %s! This is the Monkey programming language!\n",
|
||||
user.Username)
|
||||
fmt.Printf("Feel free to type in commands\n")
|
||||
repl.Start(os.Stdin, os.Stdout)
|
||||
}
|
||||
30
wcig_code_1_2/04/src/monkey/object/environment.go
Normal file
30
wcig_code_1_2/04/src/monkey/object/environment.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package object
|
||||
|
||||
func NewEnclosedEnvironment(outer *Environment) *Environment {
|
||||
env := NewEnvironment()
|
||||
env.outer = outer
|
||||
return env
|
||||
}
|
||||
|
||||
func NewEnvironment() *Environment {
|
||||
s := make(map[string]Object)
|
||||
return &Environment{store: s, outer: nil}
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
store map[string]Object
|
||||
outer *Environment
|
||||
}
|
||||
|
||||
func (e *Environment) Get(name string) (Object, bool) {
|
||||
obj, ok := e.store[name]
|
||||
if !ok && e.outer != nil {
|
||||
obj, ok = e.outer.Get(name)
|
||||
}
|
||||
return obj, ok
|
||||
}
|
||||
|
||||
func (e *Environment) Set(name string, val Object) Object {
|
||||
e.store[name] = val
|
||||
return val
|
||||
}
|
||||
182
wcig_code_1_2/04/src/monkey/object/object.go
Normal file
182
wcig_code_1_2/04/src/monkey/object/object.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"monkey/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BuiltinFunction func(args ...Object) Object
|
||||
|
||||
type ObjectType string
|
||||
|
||||
const (
|
||||
NULL_OBJ = "NULL"
|
||||
ERROR_OBJ = "ERROR"
|
||||
|
||||
INTEGER_OBJ = "INTEGER"
|
||||
BOOLEAN_OBJ = "BOOLEAN"
|
||||
STRING_OBJ = "STRING"
|
||||
|
||||
RETURN_VALUE_OBJ = "RETURN_VALUE"
|
||||
|
||||
FUNCTION_OBJ = "FUNCTION"
|
||||
BUILTIN_OBJ = "BUILTIN"
|
||||
|
||||
ARRAY_OBJ = "ARRAY"
|
||||
HASH_OBJ = "HASH"
|
||||
)
|
||||
|
||||
type HashKey struct {
|
||||
Type ObjectType
|
||||
Value uint64
|
||||
}
|
||||
|
||||
type Hashable interface {
|
||||
HashKey() HashKey
|
||||
}
|
||||
|
||||
type Object interface {
|
||||
Type() ObjectType
|
||||
Inspect() string
|
||||
}
|
||||
|
||||
type Integer struct {
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
|
||||
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
|
||||
func (i *Integer) HashKey() HashKey {
|
||||
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
|
||||
}
|
||||
|
||||
type Boolean struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
|
||||
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
|
||||
func (b *Boolean) HashKey() HashKey {
|
||||
var value uint64
|
||||
|
||||
if b.Value {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
|
||||
return HashKey{Type: b.Type(), Value: value}
|
||||
}
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (n *Null) Type() ObjectType { return NULL_OBJ }
|
||||
func (n *Null) Inspect() string { return "null" }
|
||||
|
||||
type ReturnValue struct {
|
||||
Value Object
|
||||
}
|
||||
|
||||
func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
|
||||
func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
|
||||
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Type() ObjectType { return ERROR_OBJ }
|
||||
func (e *Error) Inspect() string { return "ERROR: " + e.Message }
|
||||
|
||||
type Function struct {
|
||||
Parameters []*ast.Identifier
|
||||
Body *ast.BlockStatement
|
||||
Env *Environment
|
||||
}
|
||||
|
||||
func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
|
||||
func (f *Function) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range f.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString("fn")
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") {\n")
|
||||
out.WriteString(f.Body.String())
|
||||
out.WriteString("\n}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type String struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (s *String) Type() ObjectType { return STRING_OBJ }
|
||||
func (s *String) Inspect() string { return s.Value }
|
||||
func (s *String) HashKey() HashKey {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(s.Value))
|
||||
|
||||
return HashKey{Type: s.Type(), Value: h.Sum64()}
|
||||
}
|
||||
|
||||
type Builtin struct {
|
||||
Fn BuiltinFunction
|
||||
}
|
||||
|
||||
func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
|
||||
func (b *Builtin) Inspect() string { return "builtin function" }
|
||||
|
||||
type Array struct {
|
||||
Elements []Object
|
||||
}
|
||||
|
||||
func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
|
||||
func (ao *Array) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, e := range ao.Elements {
|
||||
elements = append(elements, e.Inspect())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashPair struct {
|
||||
Key Object
|
||||
Value Object
|
||||
}
|
||||
|
||||
type Hash struct {
|
||||
Pairs map[HashKey]HashPair
|
||||
}
|
||||
|
||||
func (h *Hash) Type() ObjectType { return HASH_OBJ }
|
||||
func (h *Hash) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for _, pair := range h.Pairs {
|
||||
pairs = append(pairs, fmt.Sprintf("%s: %s",
|
||||
pair.Key.Inspect(), pair.Value.Inspect()))
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
60
wcig_code_1_2/04/src/monkey/object/object_test.go
Normal file
60
wcig_code_1_2/04/src/monkey/object/object_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package object
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStringHashKey(t *testing.T) {
|
||||
hello1 := &String{Value: "Hello World"}
|
||||
hello2 := &String{Value: "Hello World"}
|
||||
diff1 := &String{Value: "My name is johnny"}
|
||||
diff2 := &String{Value: "My name is johnny"}
|
||||
|
||||
if hello1.HashKey() != hello2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if diff1.HashKey() != diff2.HashKey() {
|
||||
t.Errorf("strings with same content have different hash keys")
|
||||
}
|
||||
|
||||
if hello1.HashKey() == diff1.HashKey() {
|
||||
t.Errorf("strings with different content have same hash keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanHashKey(t *testing.T) {
|
||||
true1 := &Boolean{Value: true}
|
||||
true2 := &Boolean{Value: true}
|
||||
false1 := &Boolean{Value: false}
|
||||
false2 := &Boolean{Value: false}
|
||||
|
||||
if true1.HashKey() != true2.HashKey() {
|
||||
t.Errorf("trues do not have same hash key")
|
||||
}
|
||||
|
||||
if false1.HashKey() != false2.HashKey() {
|
||||
t.Errorf("falses do not have same hash key")
|
||||
}
|
||||
|
||||
if true1.HashKey() == false1.HashKey() {
|
||||
t.Errorf("true has same hash key as false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegerHashKey(t *testing.T) {
|
||||
one1 := &Integer{Value: 1}
|
||||
one2 := &Integer{Value: 1}
|
||||
two1 := &Integer{Value: 2}
|
||||
two2 := &Integer{Value: 2}
|
||||
|
||||
if one1.HashKey() != one2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if two1.HashKey() != two2.HashKey() {
|
||||
t.Errorf("integers with same content have twoerent hash keys")
|
||||
}
|
||||
|
||||
if one1.HashKey() == two1.HashKey() {
|
||||
t.Errorf("integers with twoerent content have same hash keys")
|
||||
}
|
||||
}
|
||||
491
wcig_code_1_2/04/src/monkey/parser/parser.go
Normal file
491
wcig_code_1_2/04/src/monkey/parser/parser.go
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/lexer"
|
||||
"monkey/token"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
_ int = iota
|
||||
LOWEST
|
||||
EQUALS // ==
|
||||
LESSGREATER // > or <
|
||||
SUM // +
|
||||
PRODUCT // *
|
||||
PREFIX // -X or !X
|
||||
CALL // myFunction(X)
|
||||
INDEX // array[index]
|
||||
)
|
||||
|
||||
var precedences = map[token.TokenType]int{
|
||||
token.EQ: EQUALS,
|
||||
token.NOT_EQ: EQUALS,
|
||||
token.LT: LESSGREATER,
|
||||
token.GT: LESSGREATER,
|
||||
token.PLUS: SUM,
|
||||
token.MINUS: SUM,
|
||||
token.SLASH: PRODUCT,
|
||||
token.ASTERISK: PRODUCT,
|
||||
token.LPAREN: CALL,
|
||||
token.LBRACKET: INDEX,
|
||||
}
|
||||
|
||||
type (
|
||||
prefixParseFn func() ast.Expression
|
||||
infixParseFn func(ast.Expression) ast.Expression
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
l *lexer.Lexer
|
||||
errors []string
|
||||
|
||||
curToken token.Token
|
||||
peekToken token.Token
|
||||
|
||||
prefixParseFns map[token.TokenType]prefixParseFn
|
||||
infixParseFns map[token.TokenType]infixParseFn
|
||||
}
|
||||
|
||||
func New(l *lexer.Lexer) *Parser {
|
||||
p := &Parser{
|
||||
l: l,
|
||||
errors: []string{},
|
||||
}
|
||||
|
||||
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
|
||||
p.registerPrefix(token.IDENT, p.parseIdentifier)
|
||||
p.registerPrefix(token.INT, p.parseIntegerLiteral)
|
||||
p.registerPrefix(token.STRING, p.parseStringLiteral)
|
||||
p.registerPrefix(token.BANG, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
|
||||
p.registerPrefix(token.TRUE, p.parseBoolean)
|
||||
p.registerPrefix(token.FALSE, p.parseBoolean)
|
||||
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
|
||||
p.registerPrefix(token.IF, p.parseIfExpression)
|
||||
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
|
||||
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
|
||||
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
|
||||
|
||||
p.infixParseFns = make(map[token.TokenType]infixParseFn)
|
||||
p.registerInfix(token.PLUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.MINUS, p.parseInfixExpression)
|
||||
p.registerInfix(token.SLASH, p.parseInfixExpression)
|
||||
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
|
||||
p.registerInfix(token.EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
|
||||
p.registerInfix(token.LT, p.parseInfixExpression)
|
||||
p.registerInfix(token.GT, p.parseInfixExpression)
|
||||
|
||||
p.registerInfix(token.LPAREN, p.parseCallExpression)
|
||||
p.registerInfix(token.LBRACKET, p.parseIndexExpression)
|
||||
|
||||
// Read two tokens, so curToken and peekToken are both set
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) nextToken() {
|
||||
p.curToken = p.peekToken
|
||||
p.peekToken = p.l.NextToken()
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(t token.TokenType) bool {
|
||||
return p.curToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) peekTokenIs(t token.TokenType) bool {
|
||||
return p.peekToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) expectPeek(t token.TokenType) bool {
|
||||
if p.peekTokenIs(t) {
|
||||
p.nextToken()
|
||||
return true
|
||||
} else {
|
||||
p.peekError(t)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) Errors() []string {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
func (p *Parser) peekError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("expected next token to be %s, got %s instead",
|
||||
t, p.peekToken.Type)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) noPrefixParseFnError(t token.TokenType) {
|
||||
msg := fmt.Sprintf("no prefix parse function for %s found", t)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) ParseProgram() *ast.Program {
|
||||
program := &ast.Program{}
|
||||
program.Statements = []ast.Statement{}
|
||||
|
||||
for !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
program.Statements = append(program.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() ast.Statement {
|
||||
switch p.curToken.Type {
|
||||
case token.LET:
|
||||
return p.parseLetStatement()
|
||||
case token.RETURN:
|
||||
return p.parseReturnStatement()
|
||||
default:
|
||||
return p.parseExpressionStatement()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseLetStatement() *ast.LetStatement {
|
||||
stmt := &ast.LetStatement{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.IDENT) {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
|
||||
if !p.expectPeek(token.ASSIGN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
|
||||
stmt := &ast.ReturnStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.ReturnValue = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
|
||||
stmt := &ast.ExpressionStatement{Token: p.curToken}
|
||||
|
||||
stmt.Expression = p.parseExpression(LOWEST)
|
||||
|
||||
if p.peekTokenIs(token.SEMICOLON) {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpression(precedence int) ast.Expression {
|
||||
prefix := p.prefixParseFns[p.curToken.Type]
|
||||
if prefix == nil {
|
||||
p.noPrefixParseFnError(p.curToken.Type)
|
||||
return nil
|
||||
}
|
||||
leftExp := prefix()
|
||||
|
||||
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return leftExp
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
leftExp = infix(leftExp)
|
||||
}
|
||||
|
||||
return leftExp
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() int {
|
||||
if p, ok := precedences[p.peekToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() int {
|
||||
if p, ok := precedences[p.curToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) parseIdentifier() ast.Expression {
|
||||
return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parseIntegerLiteral() ast.Expression {
|
||||
lit := &ast.IntegerLiteral{Token: p.curToken}
|
||||
|
||||
value, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
|
||||
p.errors = append(p.errors, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Value = value
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseStringLiteral() ast.Expression {
|
||||
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
|
||||
}
|
||||
|
||||
func (p *Parser) parsePrefixExpression() ast.Expression {
|
||||
expression := &ast.PrefixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
expression.Right = p.parseExpression(PREFIX)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
|
||||
expression := &ast.InfixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Literal,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
precedence := p.curPrecedence()
|
||||
p.nextToken()
|
||||
expression.Right = p.parseExpression(precedence)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBoolean() ast.Expression {
|
||||
return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)}
|
||||
}
|
||||
|
||||
func (p *Parser) parseGroupedExpression() ast.Expression {
|
||||
p.nextToken()
|
||||
|
||||
exp := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseIfExpression() ast.Expression {
|
||||
expression := &ast.IfExpression{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()
|
||||
|
||||
if p.peekTokenIs(token.ELSE) {
|
||||
p.nextToken()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
expression.Alternative = p.parseBlockStatement()
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
|
||||
block := &ast.BlockStatement{Token: p.curToken}
|
||||
block.Statements = []ast.Statement{}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
block.Statements = append(block.Statements, stmt)
|
||||
}
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionLiteral() ast.Expression {
|
||||
lit := &ast.FunctionLiteral{Token: p.curToken}
|
||||
|
||||
if !p.expectPeek(token.LPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Parameters = p.parseFunctionParameters()
|
||||
|
||||
if !p.expectPeek(token.LBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Body = p.parseBlockStatement()
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionParameters() []*ast.Identifier {
|
||||
identifiers := []*ast.Identifier{}
|
||||
|
||||
if p.peekTokenIs(token.RPAREN) {
|
||||
p.nextToken()
|
||||
return identifiers
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
|
||||
identifiers = append(identifiers, ident)
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RPAREN) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
|
||||
exp := &ast.CallExpression{Token: p.curToken, Function: function}
|
||||
exp.Arguments = p.parseExpressionList(token.RPAREN)
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
|
||||
list := []ast.Expression{}
|
||||
|
||||
if p.peekTokenIs(end) {
|
||||
p.nextToken()
|
||||
return list
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
|
||||
for p.peekTokenIs(token.COMMA) {
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
list = append(list, p.parseExpression(LOWEST))
|
||||
}
|
||||
|
||||
if !p.expectPeek(end) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (p *Parser) parseArrayLiteral() ast.Expression {
|
||||
array := &ast.ArrayLiteral{Token: p.curToken}
|
||||
|
||||
array.Elements = p.parseExpressionList(token.RBRACKET)
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
|
||||
exp := &ast.IndexExpression{Token: p.curToken, Left: left}
|
||||
|
||||
p.nextToken()
|
||||
exp.Index = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.RBRACKET) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseHashLiteral() ast.Expression {
|
||||
hash := &ast.HashLiteral{Token: p.curToken}
|
||||
hash.Pairs = make(map[ast.Expression]ast.Expression)
|
||||
|
||||
for !p.peekTokenIs(token.RBRACE) {
|
||||
p.nextToken()
|
||||
key := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(token.COLON) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
value := p.parseExpression(LOWEST)
|
||||
|
||||
hash.Pairs[key] = value
|
||||
|
||||
if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !p.expectPeek(token.RBRACE) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
|
||||
p.prefixParseFns[tokenType] = fn
|
||||
}
|
||||
|
||||
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
|
||||
p.infixParseFns[tokenType] = fn
|
||||
}
|
||||
1083
wcig_code_1_2/04/src/monkey/parser/parser_test.go
Normal file
1083
wcig_code_1_2/04/src/monkey/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load diff
32
wcig_code_1_2/04/src/monkey/parser/parser_tracing.go
Normal file
32
wcig_code_1_2/04/src/monkey/parser/parser_tracing.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var traceLevel int = 0
|
||||
|
||||
const traceIdentPlaceholder string = "\t"
|
||||
|
||||
func identLevel() string {
|
||||
return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
|
||||
}
|
||||
|
||||
func tracePrint(fs string) {
|
||||
fmt.Printf("%s%s\n", identLevel(), fs)
|
||||
}
|
||||
|
||||
func incIdent() { traceLevel = traceLevel + 1 }
|
||||
func decIdent() { traceLevel = traceLevel - 1 }
|
||||
|
||||
func trace(msg string) string {
|
||||
incIdent()
|
||||
tracePrint("BEGIN " + msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func untrace(msg string) {
|
||||
tracePrint("END " + msg)
|
||||
decIdent()
|
||||
}
|
||||
75
wcig_code_1_2/04/src/monkey/repl/repl.go
Normal file
75
wcig_code_1_2/04/src/monkey/repl/repl.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package repl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"monkey/compiler"
|
||||
"monkey/lexer"
|
||||
"monkey/parser"
|
||||
"monkey/vm"
|
||||
)
|
||||
|
||||
const PROMPT = ">> "
|
||||
|
||||
func Start(in io.Reader, out io.Writer) {
|
||||
scanner := bufio.NewScanner(in)
|
||||
|
||||
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.New()
|
||||
err := comp.Compile(program)
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Woops! Compilation failed:\n %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
machine := vm.New(comp.Bytecode())
|
||||
err = machine.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "Woops! Executing bytecode failed:\n %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lastPopped := machine.LastPoppedStackElem()
|
||||
io.WriteString(out, lastPopped.Inspect())
|
||||
io.WriteString(out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
const MONKEY_FACE = ` __,__
|
||||
.--. .-" "-. .--.
|
||||
/ .. \/ .-. .-. \/ .. \
|
||||
| | '| / Y \ |' | |
|
||||
| \ \ \ 0 | 0 / / / |
|
||||
\ '- ,\.-"""""""-./, -' /
|
||||
''-' /_ ^ ^ _\ '-''
|
||||
| \._ _./ |
|
||||
\ \ '~' / /
|
||||
'._ '-=-' _.'
|
||||
'-----'
|
||||
`
|
||||
|
||||
func printParserErrors(out io.Writer, errors []string) {
|
||||
io.WriteString(out, MONKEY_FACE)
|
||||
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
||||
io.WriteString(out, " parser errors:\n")
|
||||
for _, msg := range errors {
|
||||
io.WriteString(out, "\t"+msg+"\n")
|
||||
}
|
||||
}
|
||||
70
wcig_code_1_2/04/src/monkey/token/token.go
Normal file
70
wcig_code_1_2/04/src/monkey/token/token.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package token
|
||||
|
||||
type TokenType string
|
||||
|
||||
const (
|
||||
ILLEGAL = "ILLEGAL"
|
||||
EOF = "EOF"
|
||||
|
||||
// Identifiers + literals
|
||||
IDENT = "IDENT" // add, foobar, x, y, ...
|
||||
INT = "INT" // 1343456
|
||||
STRING = "STRING" // "foobar"
|
||||
|
||||
// Operators
|
||||
ASSIGN = "="
|
||||
PLUS = "+"
|
||||
MINUS = "-"
|
||||
BANG = "!"
|
||||
ASTERISK = "*"
|
||||
SLASH = "/"
|
||||
|
||||
LT = "<"
|
||||
GT = ">"
|
||||
|
||||
EQ = "=="
|
||||
NOT_EQ = "!="
|
||||
|
||||
// Delimiters
|
||||
COMMA = ","
|
||||
SEMICOLON = ";"
|
||||
COLON = ":"
|
||||
|
||||
LPAREN = "("
|
||||
RPAREN = ")"
|
||||
LBRACE = "{"
|
||||
RBRACE = "}"
|
||||
LBRACKET = "["
|
||||
RBRACKET = "]"
|
||||
|
||||
// Keywords
|
||||
FUNCTION = "FUNCTION"
|
||||
LET = "LET"
|
||||
TRUE = "TRUE"
|
||||
FALSE = "FALSE"
|
||||
IF = "IF"
|
||||
ELSE = "ELSE"
|
||||
RETURN = "RETURN"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Literal string
|
||||
}
|
||||
|
||||
var keywords = map[string]TokenType{
|
||||
"fn": FUNCTION,
|
||||
"let": LET,
|
||||
"true": TRUE,
|
||||
"false": FALSE,
|
||||
"if": IF,
|
||||
"else": ELSE,
|
||||
"return": RETURN,
|
||||
}
|
||||
|
||||
func LookupIdent(ident string) TokenType {
|
||||
if tok, ok := keywords[ident]; ok {
|
||||
return tok
|
||||
}
|
||||
return IDENT
|
||||
}
|
||||
256
wcig_code_1_2/04/src/monkey/vm/vm.go
Normal file
256
wcig_code_1_2/04/src/monkey/vm/vm.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/code"
|
||||
"monkey/compiler"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
const StackSize = 2048
|
||||
|
||||
var True = &object.Boolean{Value: true}
|
||||
var False = &object.Boolean{Value: false}
|
||||
var Null = &object.Null{}
|
||||
|
||||
type VM struct {
|
||||
constants []object.Object
|
||||
instructions code.Instructions
|
||||
|
||||
stack []object.Object
|
||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||
}
|
||||
|
||||
func New(bytecode *compiler.Bytecode) *VM {
|
||||
return &VM{
|
||||
instructions: bytecode.Instructions,
|
||||
constants: bytecode.Constants,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
sp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) LastPoppedStackElem() object.Object {
|
||||
return vm.stack[vm.sp]
|
||||
}
|
||||
|
||||
func (vm *VM) Run() error {
|
||||
for ip := 0; ip < len(vm.instructions); ip++ {
|
||||
op := code.Opcode(vm.instructions[ip])
|
||||
|
||||
switch op {
|
||||
case code.OpConstant:
|
||||
constIndex := code.ReadUint16(vm.instructions[ip+1:])
|
||||
ip += 2
|
||||
|
||||
err := vm.push(vm.constants[constIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpPop:
|
||||
vm.pop()
|
||||
|
||||
case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv:
|
||||
err := vm.executeBinaryOperation(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpTrue:
|
||||
err := vm.push(True)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpFalse:
|
||||
err := vm.push(False)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpEqual, code.OpNotEqual, code.OpGreaterThan:
|
||||
err := vm.executeComparison(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpBang:
|
||||
err := vm.executeBangOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpMinus:
|
||||
err := vm.executeMinusOperator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpJump:
|
||||
pos := int(code.ReadUint16(vm.instructions[ip+1:]))
|
||||
ip = pos - 1
|
||||
|
||||
case code.OpJumpNotTruthy:
|
||||
pos := int(code.ReadUint16(vm.instructions[ip+1:]))
|
||||
ip += 2
|
||||
|
||||
condition := vm.pop()
|
||||
if !isTruthy(condition) {
|
||||
ip = pos - 1
|
||||
}
|
||||
|
||||
case code.OpNull:
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) push(o object.Object) error {
|
||||
if vm.sp >= StackSize {
|
||||
return fmt.Errorf("stack overflow")
|
||||
}
|
||||
|
||||
vm.stack[vm.sp] = o
|
||||
vm.sp++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) pop() object.Object {
|
||||
o := vm.stack[vm.sp-1]
|
||||
vm.sp--
|
||||
return o
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryOperation(op code.Opcode) error {
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
leftType := left.Type()
|
||||
rightType := right.Type()
|
||||
|
||||
if leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ {
|
||||
return vm.executeBinaryIntegerOperation(op, left, right)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported types for binary operation: %s %s",
|
||||
leftType, rightType)
|
||||
}
|
||||
|
||||
func (vm *VM) executeBinaryIntegerOperation(
|
||||
op code.Opcode,
|
||||
left, right object.Object,
|
||||
) error {
|
||||
leftValue := left.(*object.Integer).Value
|
||||
rightValue := right.(*object.Integer).Value
|
||||
|
||||
var result int64
|
||||
|
||||
switch op {
|
||||
case code.OpAdd:
|
||||
result = leftValue + rightValue
|
||||
case code.OpSub:
|
||||
result = leftValue - rightValue
|
||||
case code.OpMul:
|
||||
result = leftValue * rightValue
|
||||
case code.OpDiv:
|
||||
result = leftValue / rightValue
|
||||
default:
|
||||
return fmt.Errorf("unknown integer operator: %d", op)
|
||||
}
|
||||
|
||||
return vm.push(&object.Integer{Value: result})
|
||||
}
|
||||
|
||||
func (vm *VM) executeComparison(op code.Opcode) error {
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ {
|
||||
return vm.executeIntegerComparison(op, left, right)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case code.OpEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(right == left))
|
||||
case code.OpNotEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(right != left))
|
||||
default:
|
||||
return fmt.Errorf("unknown operator: %d (%s %s)",
|
||||
op, left.Type(), right.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeIntegerComparison(
|
||||
op code.Opcode,
|
||||
left, right object.Object,
|
||||
) error {
|
||||
leftValue := left.(*object.Integer).Value
|
||||
rightValue := right.(*object.Integer).Value
|
||||
|
||||
switch op {
|
||||
case code.OpEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(rightValue == leftValue))
|
||||
case code.OpNotEqual:
|
||||
return vm.push(nativeBoolToBooleanObject(rightValue != leftValue))
|
||||
case code.OpGreaterThan:
|
||||
return vm.push(nativeBoolToBooleanObject(leftValue > rightValue))
|
||||
default:
|
||||
return fmt.Errorf("unknown operator: %d", op)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeBangOperator() error {
|
||||
operand := vm.pop()
|
||||
|
||||
switch operand {
|
||||
case True:
|
||||
return vm.push(False)
|
||||
case False:
|
||||
return vm.push(True)
|
||||
case Null:
|
||||
return vm.push(True)
|
||||
default:
|
||||
return vm.push(False)
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) executeMinusOperator() error {
|
||||
operand := vm.pop()
|
||||
|
||||
if operand.Type() != object.INTEGER_OBJ {
|
||||
return fmt.Errorf("unsupported type for negation: %s", operand.Type())
|
||||
}
|
||||
|
||||
value := operand.(*object.Integer).Value
|
||||
return vm.push(&object.Integer{Value: -value})
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return True
|
||||
}
|
||||
return False
|
||||
}
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj := obj.(type) {
|
||||
|
||||
case *object.Boolean:
|
||||
return obj.Value
|
||||
|
||||
case *object.Null:
|
||||
return false
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
177
wcig_code_1_2/04/src/monkey/vm/vm_test.go
Normal file
177
wcig_code_1_2/04/src/monkey/vm/vm_test.go
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/compiler"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"1", 1},
|
||||
{"2", 2},
|
||||
{"1 + 2", 3},
|
||||
{"1 - 2", -1},
|
||||
{"1 * 2", 2},
|
||||
{"4 / 2", 2},
|
||||
{"50 / 2 * 2 + 10 - 5", 55},
|
||||
{"5 * (2 + 10)", 60},
|
||||
{"5 + 5 + 5 + 5 - 10", 10},
|
||||
{"2 * 2 * 2 * 2 * 2", 32},
|
||||
{"5 * 2 + 10", 20},
|
||||
{"5 + 2 * 10", 25},
|
||||
{"5 * (2 + 10)", 60},
|
||||
{"-5", -5},
|
||||
{"-10", -10},
|
||||
{"-50 + 100 + -50", 0},
|
||||
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBooleanExpressions(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1 < 2", true},
|
||||
{"1 > 2", false},
|
||||
{"1 < 1", false},
|
||||
{"1 > 1", false},
|
||||
{"1 == 1", true},
|
||||
{"1 != 1", false},
|
||||
{"1 == 2", false},
|
||||
{"1 != 2", true},
|
||||
{"true == true", true},
|
||||
{"false == false", true},
|
||||
{"true == false", false},
|
||||
{"true != false", true},
|
||||
{"false != true", true},
|
||||
{"(1 < 2) == true", true},
|
||||
{"(1 < 2) == false", false},
|
||||
{"(1 > 2) == true", false},
|
||||
{"(1 > 2) == false", true},
|
||||
{"!true", false},
|
||||
{"!false", true},
|
||||
{"!5", false},
|
||||
{"!!true", true},
|
||||
{"!!false", false},
|
||||
{"!!5", true},
|
||||
{"!(if (false) { 5; })", true},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
func TestConditionals(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{"if (true) { 10 }", 10},
|
||||
{"if (true) { 10 } else { 20 }", 10},
|
||||
{"if (false) { 10 } else { 20 } ", 20},
|
||||
{"if (1) { 10 }", 10},
|
||||
{"if (1 < 2) { 10 }", 10},
|
||||
{"if (1 < 2) { 10 } else { 20 }", 10},
|
||||
{"if (1 > 2) { 10 } else { 20 }", 20},
|
||||
{"if (1 > 2) { 10 }", Null},
|
||||
{"if (false) { 10 }", Null},
|
||||
{"if ((if (false) { 10 })) { 10 } else { 20 }", 20},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
type vmTestCase struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}
|
||||
|
||||
func runVmTests(t *testing.T, tests []vmTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
comp := compiler.New()
|
||||
err := comp.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
vm := New(comp.Bytecode())
|
||||
err = vm.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("vm error: %s", err)
|
||||
}
|
||||
|
||||
stackElem := vm.LastPoppedStackElem()
|
||||
|
||||
testExpectedObject(t, tt.expected, stackElem)
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testExpectedObject(
|
||||
t *testing.T,
|
||||
expected interface{},
|
||||
actual object.Object,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
switch expected := expected.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(expected), actual)
|
||||
if err != nil {
|
||||
t.Errorf("testIntegerObject failed: %s", err)
|
||||
}
|
||||
|
||||
case bool:
|
||||
err := testBooleanObject(bool(expected), actual)
|
||||
if err != nil {
|
||||
t.Errorf("testBooleanObject failed: %s", err)
|
||||
}
|
||||
|
||||
case *object.Null:
|
||||
if actual != Null {
|
||||
t.Errorf("object is not Null: %T (%+v)", actual, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testBooleanObject(expected bool, actual object.Object) error {
|
||||
result, ok := actual.(*object.Boolean)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Boolean. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%t, want=%t",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1
wcig_code_1_2/05/.envrc
Normal file
1
wcig_code_1_2/05/.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
export GOPATH=$(pwd)
|
||||
339
wcig_code_1_2/05/src/monkey/ast/ast.go
Normal file
339
wcig_code_1_2/05/src/monkey/ast/ast.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"monkey/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The base Node interface
|
||||
type Node interface {
|
||||
TokenLiteral() string
|
||||
String() string
|
||||
}
|
||||
|
||||
// All statement nodes implement this
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
// All expression nodes implement this
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (p *Program) TokenLiteral() string {
|
||||
if len(p.Statements) > 0 {
|
||||
return p.Statements[0].TokenLiteral()
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range p.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Statements
|
||||
type LetStatement struct {
|
||||
Token token.Token // the token.LET token
|
||||
Name *Identifier
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (ls *LetStatement) statementNode() {}
|
||||
func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }
|
||||
func (ls *LetStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(ls.TokenLiteral() + " ")
|
||||
out.WriteString(ls.Name.String())
|
||||
out.WriteString(" = ")
|
||||
|
||||
if ls.Value != nil {
|
||||
out.WriteString(ls.Value.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ReturnStatement struct {
|
||||
Token token.Token // the 'return' token
|
||||
ReturnValue Expression
|
||||
}
|
||||
|
||||
func (rs *ReturnStatement) statementNode() {}
|
||||
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
|
||||
func (rs *ReturnStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString(rs.TokenLiteral() + " ")
|
||||
|
||||
if rs.ReturnValue != nil {
|
||||
out.WriteString(rs.ReturnValue.String())
|
||||
}
|
||||
|
||||
out.WriteString(";")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type ExpressionStatement struct {
|
||||
Token token.Token // the first token of the expression
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (es *ExpressionStatement) statementNode() {}
|
||||
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
|
||||
func (es *ExpressionStatement) String() string {
|
||||
if es.Expression != nil {
|
||||
return es.Expression.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BlockStatement struct {
|
||||
Token token.Token // the { token
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (bs *BlockStatement) statementNode() {}
|
||||
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
|
||||
func (bs *BlockStatement) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
for _, s := range bs.Statements {
|
||||
out.WriteString(s.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Expressions
|
||||
type Identifier struct {
|
||||
Token token.Token // the token.IDENT token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (i *Identifier) expressionNode() {}
|
||||
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
|
||||
func (i *Identifier) String() string { return i.Value }
|
||||
|
||||
type Boolean struct {
|
||||
Token token.Token
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b *Boolean) expressionNode() {}
|
||||
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
|
||||
func (b *Boolean) String() string { return b.Token.Literal }
|
||||
|
||||
type IntegerLiteral struct {
|
||||
Token token.Token
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (il *IntegerLiteral) expressionNode() {}
|
||||
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
|
||||
func (il *IntegerLiteral) String() string { return il.Token.Literal }
|
||||
|
||||
type PrefixExpression struct {
|
||||
Token token.Token // The prefix token, e.g. !
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
|
||||
func (pe *PrefixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(pe.Operator)
|
||||
out.WriteString(pe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type InfixExpression struct {
|
||||
Token token.Token // The operator token, e.g. +
|
||||
Left Expression
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (oe *InfixExpression) expressionNode() {}
|
||||
func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
|
||||
func (oe *InfixExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(oe.Left.String())
|
||||
out.WriteString(" " + oe.Operator + " ")
|
||||
out.WriteString(oe.Right.String())
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IfExpression struct {
|
||||
Token token.Token // The 'if' token
|
||||
Condition Expression
|
||||
Consequence *BlockStatement
|
||||
Alternative *BlockStatement
|
||||
}
|
||||
|
||||
func (ie *IfExpression) expressionNode() {}
|
||||
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IfExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("if")
|
||||
out.WriteString(ie.Condition.String())
|
||||
out.WriteString(" ")
|
||||
out.WriteString(ie.Consequence.String())
|
||||
|
||||
if ie.Alternative != nil {
|
||||
out.WriteString("else ")
|
||||
out.WriteString(ie.Alternative.String())
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type FunctionLiteral struct {
|
||||
Token token.Token // The 'fn' token
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fl *FunctionLiteral) expressionNode() {}
|
||||
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
|
||||
func (fl *FunctionLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
params := []string{}
|
||||
for _, p := range fl.Parameters {
|
||||
params = append(params, p.String())
|
||||
}
|
||||
|
||||
out.WriteString(fl.TokenLiteral())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(params, ", "))
|
||||
out.WriteString(") ")
|
||||
out.WriteString(fl.Body.String())
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type CallExpression struct {
|
||||
Token token.Token // The '(' token
|
||||
Function Expression // Identifier or FunctionLiteral
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (ce *CallExpression) expressionNode() {}
|
||||
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
|
||||
func (ce *CallExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
args := []string{}
|
||||
for _, a := range ce.Arguments {
|
||||
args = append(args, a.String())
|
||||
}
|
||||
|
||||
out.WriteString(ce.Function.String())
|
||||
out.WriteString("(")
|
||||
out.WriteString(strings.Join(args, ", "))
|
||||
out.WriteString(")")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type StringLiteral struct {
|
||||
Token token.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) expressionNode() {}
|
||||
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
|
||||
func (sl *StringLiteral) String() string { return sl.Token.Literal }
|
||||
|
||||
type ArrayLiteral struct {
|
||||
Token token.Token // the '[' token
|
||||
Elements []Expression
|
||||
}
|
||||
|
||||
func (al *ArrayLiteral) expressionNode() {}
|
||||
func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal }
|
||||
func (al *ArrayLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
for _, el := range al.Elements {
|
||||
elements = append(elements, el.String())
|
||||
}
|
||||
|
||||
out.WriteString("[")
|
||||
out.WriteString(strings.Join(elements, ", "))
|
||||
out.WriteString("]")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type IndexExpression struct {
|
||||
Token token.Token // The [ token
|
||||
Left Expression
|
||||
Index Expression
|
||||
}
|
||||
|
||||
func (ie *IndexExpression) expressionNode() {}
|
||||
func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||
func (ie *IndexExpression) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.WriteString("(")
|
||||
out.WriteString(ie.Left.String())
|
||||
out.WriteString("[")
|
||||
out.WriteString(ie.Index.String())
|
||||
out.WriteString("])")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
type HashLiteral struct {
|
||||
Token token.Token // the '{' token
|
||||
Pairs map[Expression]Expression
|
||||
}
|
||||
|
||||
func (hl *HashLiteral) expressionNode() {}
|
||||
func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal }
|
||||
func (hl *HashLiteral) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
pairs := []string{}
|
||||
for key, value := range hl.Pairs {
|
||||
pairs = append(pairs, key.String()+":"+value.String())
|
||||
}
|
||||
|
||||
out.WriteString("{")
|
||||
out.WriteString(strings.Join(pairs, ", "))
|
||||
out.WriteString("}")
|
||||
|
||||
return out.String()
|
||||
}
|
||||
28
wcig_code_1_2/05/src/monkey/ast/ast_test.go
Normal file
28
wcig_code_1_2/05/src/monkey/ast/ast_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"monkey/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
program := &Program{
|
||||
Statements: []Statement{
|
||||
&LetStatement{
|
||||
Token: token.Token{Type: token.LET, Literal: "let"},
|
||||
Name: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
|
||||
Value: "myVar",
|
||||
},
|
||||
Value: &Identifier{
|
||||
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
|
||||
Value: "anotherVar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if program.String() != "let myVar = anotherVar;" {
|
||||
t.Errorf("program.String() wrong. got=%q", program.String())
|
||||
}
|
||||
}
|
||||
171
wcig_code_1_2/05/src/monkey/code/code.go
Normal file
171
wcig_code_1_2/05/src/monkey/code/code.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
package code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Instructions []byte
|
||||
|
||||
func (ins Instructions) String() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
i := 0
|
||||
for i < len(ins) {
|
||||
def, err := Lookup(ins[i])
|
||||
if err != nil {
|
||||
fmt.Fprintf(&out, "ERROR: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
operands, read := ReadOperands(def, ins[i+1:])
|
||||
|
||||
fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
|
||||
|
||||
i += 1 + read
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
|
||||
operandCount := len(def.OperandWidths)
|
||||
|
||||
if len(operands) != operandCount {
|
||||
return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n",
|
||||
len(operands), operandCount)
|
||||
}
|
||||
|
||||
switch operandCount {
|
||||
case 0:
|
||||
return def.Name
|
||||
case 1:
|
||||
return fmt.Sprintf("%s %d", def.Name, operands[0])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
|
||||
}
|
||||
|
||||
type Opcode byte
|
||||
|
||||
const (
|
||||
OpConstant Opcode = iota
|
||||
|
||||
OpAdd
|
||||
|
||||
OpPop
|
||||
|
||||
OpSub
|
||||
OpMul
|
||||
OpDiv
|
||||
|
||||
OpTrue
|
||||
OpFalse
|
||||
|
||||
OpEqual
|
||||
OpNotEqual
|
||||
OpGreaterThan
|
||||
|
||||
OpMinus
|
||||
OpBang
|
||||
|
||||
OpJumpNotTruthy
|
||||
OpJump
|
||||
|
||||
OpNull
|
||||
|
||||
OpGetGlobal
|
||||
OpSetGlobal
|
||||
)
|
||||
|
||||
type Definition struct {
|
||||
Name string
|
||||
OperandWidths []int
|
||||
}
|
||||
|
||||
var definitions = map[Opcode]*Definition{
|
||||
OpConstant: {"OpConstant", []int{2}},
|
||||
|
||||
OpAdd: {"OpAdd", []int{}},
|
||||
|
||||
OpPop: {"OpPop", []int{}},
|
||||
|
||||
OpSub: {"OpSub", []int{}},
|
||||
OpMul: {"OpMul", []int{}},
|
||||
OpDiv: {"OpDiv", []int{}},
|
||||
|
||||
OpTrue: {"OpTrue", []int{}},
|
||||
OpFalse: {"OpFalse", []int{}},
|
||||
|
||||
OpEqual: {"OpEqual", []int{}},
|
||||
OpNotEqual: {"OpNotEqual", []int{}},
|
||||
OpGreaterThan: {"OpGreaterThan", []int{}},
|
||||
|
||||
OpMinus: {"OpMinus", []int{}},
|
||||
OpBang: {"OpBang", []int{}},
|
||||
|
||||
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
|
||||
OpJump: {"OpJump", []int{2}},
|
||||
|
||||
OpNull: {"OpNull", []int{}},
|
||||
|
||||
OpGetGlobal: {"OpGetGlobal", []int{2}},
|
||||
OpSetGlobal: {"OpSetGlobal", []int{2}},
|
||||
}
|
||||
|
||||
func Lookup(op byte) (*Definition, error) {
|
||||
def, ok := definitions[Opcode(op)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("opcode %d undefined", op)
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func Make(op Opcode, operands ...int) []byte {
|
||||
def, ok := definitions[op]
|
||||
if !ok {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
instructionLen := 1
|
||||
for _, w := range def.OperandWidths {
|
||||
instructionLen += w
|
||||
}
|
||||
|
||||
instruction := make([]byte, instructionLen)
|
||||
instruction[0] = byte(op)
|
||||
|
||||
offset := 1
|
||||
for i, o := range operands {
|
||||
width := def.OperandWidths[i]
|
||||
switch width {
|
||||
case 2:
|
||||
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
|
||||
}
|
||||
offset += width
|
||||
}
|
||||
|
||||
return instruction
|
||||
}
|
||||
|
||||
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
|
||||
operands := make([]int, len(def.OperandWidths))
|
||||
offset := 0
|
||||
|
||||
for i, width := range def.OperandWidths {
|
||||
switch width {
|
||||
case 2:
|
||||
operands[i] = int(ReadUint16(ins[offset:]))
|
||||
}
|
||||
|
||||
offset += width
|
||||
}
|
||||
|
||||
return operands, offset
|
||||
}
|
||||
|
||||
func ReadUint16(ins Instructions) uint16 {
|
||||
return binary.BigEndian.Uint16(ins)
|
||||
}
|
||||
83
wcig_code_1_2/05/src/monkey/code/code_test.go
Normal file
83
wcig_code_1_2/05/src/monkey/code/code_test.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package code
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMake(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
expected []byte
|
||||
}{
|
||||
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
|
||||
{OpAdd, []int{}, []byte{byte(OpAdd)}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
if len(instruction) != len(tt.expected) {
|
||||
t.Errorf("instruction has wrong length. want=%d, got=%d",
|
||||
len(tt.expected), len(instruction))
|
||||
}
|
||||
|
||||
for i, b := range tt.expected {
|
||||
if instruction[i] != tt.expected[i] {
|
||||
t.Errorf("wrong byte at pos %d. want=%d, got=%d",
|
||||
i, b, instruction[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionsString(t *testing.T) {
|
||||
instructions := []Instructions{
|
||||
Make(OpAdd),
|
||||
Make(OpConstant, 2),
|
||||
Make(OpConstant, 65535),
|
||||
}
|
||||
|
||||
expected := `0000 OpAdd
|
||||
0001 OpConstant 2
|
||||
0004 OpConstant 65535
|
||||
`
|
||||
|
||||
concatted := Instructions{}
|
||||
for _, ins := range instructions {
|
||||
concatted = append(concatted, ins...)
|
||||
}
|
||||
|
||||
if concatted.String() != expected {
|
||||
t.Errorf("instructions wrongly formatted.\nwant=%q\ngot=%q",
|
||||
expected, concatted.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadOperands(t *testing.T) {
|
||||
tests := []struct {
|
||||
op Opcode
|
||||
operands []int
|
||||
bytesRead int
|
||||
}{
|
||||
{OpConstant, []int{65535}, 2},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
instruction := Make(tt.op, tt.operands...)
|
||||
|
||||
def, err := Lookup(byte(tt.op))
|
||||
if err != nil {
|
||||
t.Fatalf("definition not found: %q\n", err)
|
||||
}
|
||||
|
||||
operandsRead, n := ReadOperands(def, instruction[1:])
|
||||
if n != tt.bytesRead {
|
||||
t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
|
||||
}
|
||||
|
||||
for i, want := range tt.operands {
|
||||
if operandsRead[i] != want {
|
||||
t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
257
wcig_code_1_2/05/src/monkey/compiler/compiler.go
Normal file
257
wcig_code_1_2/05/src/monkey/compiler/compiler.go
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
instructions code.Instructions
|
||||
constants []object.Object
|
||||
|
||||
lastInstruction EmittedInstruction
|
||||
previousInstruction EmittedInstruction
|
||||
|
||||
symbolTable *SymbolTable
|
||||
}
|
||||
|
||||
func New() *Compiler {
|
||||
return &Compiler{
|
||||
instructions: code.Instructions{},
|
||||
constants: []object.Object{},
|
||||
lastInstruction: EmittedInstruction{},
|
||||
previousInstruction: EmittedInstruction{},
|
||||
symbolTable: NewSymbolTable(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewWithState(s *SymbolTable, constants []object.Object) *Compiler {
|
||||
compiler := New()
|
||||
compiler.symbolTable = s
|
||||
compiler.constants = constants
|
||||
return compiler
|
||||
}
|
||||
|
||||
func (c *Compiler) Compile(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Program:
|
||||
for _, s := range node.Statements {
|
||||
err := c.Compile(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
err := c.Compile(node.Expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(code.OpPop)
|
||||
|
||||
case *ast.InfixExpression:
|
||||
if node.Operator == "<" {
|
||||
err := c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(code.OpGreaterThan)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.Compile(node.Left)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "+":
|
||||
c.emit(code.OpAdd)
|
||||
case "-":
|
||||
c.emit(code.OpSub)
|
||||
case "*":
|
||||
c.emit(code.OpMul)
|
||||
case "/":
|
||||
c.emit(code.OpDiv)
|
||||
case ">":
|
||||
c.emit(code.OpGreaterThan)
|
||||
case "==":
|
||||
c.emit(code.OpEqual)
|
||||
case "!=":
|
||||
c.emit(code.OpNotEqual)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
|
||||
case *ast.IntegerLiteral:
|
||||
integer := &object.Integer{Value: node.Value}
|
||||
c.emit(code.OpConstant, c.addConstant(integer))
|
||||
|
||||
case *ast.Boolean:
|
||||
if node.Value {
|
||||
c.emit(code.OpTrue)
|
||||
} else {
|
||||
c.emit(code.OpFalse)
|
||||
}
|
||||
|
||||
case *ast.PrefixExpression:
|
||||
err := c.Compile(node.Right)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "!":
|
||||
c.emit(code.OpBang)
|
||||
case "-":
|
||||
c.emit(code.OpMinus)
|
||||
default:
|
||||
return fmt.Errorf("unknown operator %s", node.Operator)
|
||||
}
|
||||
|
||||
case *ast.IfExpression:
|
||||
err := c.Compile(node.Condition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Emit an `OpJumpNotTruthy` with a bogus value
|
||||
jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999)
|
||||
|
||||
err = c.Compile(node.Consequence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.lastInstructionIsPop() {
|
||||
c.removeLastPop()
|
||||
}
|
||||
|
||||
// Emit an `OpJump` with a bogus value
|
||||
jumpPos := c.emit(code.OpJump, 9999)
|
||||
|
||||
afterConsequencePos := len(c.instructions)
|
||||
c.changeOperand(jumpNotTruthyPos, afterConsequencePos)
|
||||
|
||||
if node.Alternative == nil {
|
||||
c.emit(code.OpNull)
|
||||
} else {
|
||||
err := c.Compile(node.Alternative)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.lastInstructionIsPop() {
|
||||
c.removeLastPop()
|
||||
}
|
||||
}
|
||||
|
||||
afterAlternativePos := len(c.instructions)
|
||||
c.changeOperand(jumpPos, afterAlternativePos)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
for _, s := range node.Statements {
|
||||
err := c.Compile(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.LetStatement:
|
||||
err := c.Compile(node.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
symbol := c.symbolTable.Define(node.Name.Value)
|
||||
c.emit(code.OpSetGlobal, symbol.Index)
|
||||
|
||||
case *ast.Identifier:
|
||||
symbol, ok := c.symbolTable.Resolve(node.Value)
|
||||
if !ok {
|
||||
return fmt.Errorf("undefined variable %s", node.Value)
|
||||
}
|
||||
c.emit(code.OpGetGlobal, symbol.Index)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) Bytecode() *Bytecode {
|
||||
return &Bytecode{
|
||||
Instructions: c.instructions,
|
||||
Constants: c.constants,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addConstant(obj object.Object) int {
|
||||
c.constants = append(c.constants, obj)
|
||||
return len(c.constants) - 1
|
||||
}
|
||||
|
||||
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
|
||||
ins := code.Make(op, operands...)
|
||||
pos := c.addInstruction(ins)
|
||||
|
||||
c.setLastInstruction(op, pos)
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
func (c *Compiler) addInstruction(ins []byte) int {
|
||||
posNewInstruction := len(c.instructions)
|
||||
c.instructions = append(c.instructions, ins...)
|
||||
return posNewInstruction
|
||||
}
|
||||
|
||||
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
|
||||
previous := c.lastInstruction
|
||||
last := EmittedInstruction{Opcode: op, Position: pos}
|
||||
|
||||
c.previousInstruction = previous
|
||||
c.lastInstruction = last
|
||||
}
|
||||
|
||||
func (c *Compiler) lastInstructionIsPop() bool {
|
||||
return c.lastInstruction.Opcode == code.OpPop
|
||||
}
|
||||
|
||||
func (c *Compiler) removeLastPop() {
|
||||
c.instructions = c.instructions[:c.lastInstruction.Position]
|
||||
c.lastInstruction = c.previousInstruction
|
||||
}
|
||||
|
||||
func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) {
|
||||
for i := 0; i < len(newInstruction); i++ {
|
||||
c.instructions[pos+i] = newInstruction[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) changeOperand(opPos int, operand int) {
|
||||
op := code.Opcode(c.instructions[opPos])
|
||||
newInstruction := code.Make(op, operand)
|
||||
|
||||
c.replaceInstruction(opPos, newInstruction)
|
||||
}
|
||||
|
||||
type Bytecode struct {
|
||||
Instructions code.Instructions
|
||||
Constants []object.Object
|
||||
}
|
||||
|
||||
type EmittedInstruction struct {
|
||||
Opcode code.Opcode
|
||||
Position int
|
||||
}
|
||||
381
wcig_code_1_2/05/src/monkey/compiler/compiler_test.go
Normal file
381
wcig_code_1_2/05/src/monkey/compiler/compiler_test.go
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/lexer"
|
||||
"monkey/object"
|
||||
"monkey/parser"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "1 + 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpAdd),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1; 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpPop),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 - 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpSub),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 * 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpMul),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "2 / 1",
|
||||
expectedConstants: []interface{}{2, 1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpDiv),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "-1",
|
||||
expectedConstants: []interface{}{1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpMinus),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBooleanExpressions(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: "true",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 > 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpGreaterThan),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 < 2",
|
||||
expectedConstants: []interface{}{2, 1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpGreaterThan),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 == 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "1 != 2",
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpNotEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "true == false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "true != false",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpFalse),
|
||||
code.Make(code.OpNotEqual),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "!true",
|
||||
expectedConstants: []interface{}{},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpTrue),
|
||||
code.Make(code.OpBang),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestConditionals(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: `
|
||||
if (true) { 10 }; 3333;
|
||||
`,
|
||||
expectedConstants: []interface{}{10, 3333},
|
||||
expectedInstructions: []code.Instructions{
|
||||
// 0000
|
||||
code.Make(code.OpTrue),
|
||||
// 0001
|
||||
code.Make(code.OpJumpNotTruthy, 10),
|
||||
// 0004
|
||||
code.Make(code.OpConstant, 0),
|
||||
// 0007
|
||||
code.Make(code.OpJump, 11),
|
||||
// 0010
|
||||
code.Make(code.OpNull),
|
||||
// 0011
|
||||
code.Make(code.OpPop),
|
||||
// 0012
|
||||
code.Make(code.OpConstant, 1),
|
||||
// 0015
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `
|
||||
if (true) { 10 } else { 20 }; 3333;
|
||||
`,
|
||||
expectedConstants: []interface{}{10, 20, 3333},
|
||||
expectedInstructions: []code.Instructions{
|
||||
// 0000
|
||||
code.Make(code.OpTrue),
|
||||
// 0001
|
||||
code.Make(code.OpJumpNotTruthy, 10),
|
||||
// 0004
|
||||
code.Make(code.OpConstant, 0),
|
||||
// 0007
|
||||
code.Make(code.OpJump, 13),
|
||||
// 0010
|
||||
code.Make(code.OpConstant, 1),
|
||||
// 0013
|
||||
code.Make(code.OpPop),
|
||||
// 0014
|
||||
code.Make(code.OpConstant, 2),
|
||||
// 0017
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestGlobalLetStatements(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: `
|
||||
let one = 1;
|
||||
let two = 2;
|
||||
`,
|
||||
expectedConstants: []interface{}{1, 2},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpSetGlobal, 0),
|
||||
code.Make(code.OpConstant, 1),
|
||||
code.Make(code.OpSetGlobal, 1),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `
|
||||
let one = 1;
|
||||
one;
|
||||
`,
|
||||
expectedConstants: []interface{}{1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpSetGlobal, 0),
|
||||
code.Make(code.OpGetGlobal, 0),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `
|
||||
let one = 1;
|
||||
let two = one;
|
||||
two;
|
||||
`,
|
||||
expectedConstants: []interface{}{1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpSetGlobal, 0),
|
||||
code.Make(code.OpGetGlobal, 0),
|
||||
code.Make(code.OpSetGlobal, 1),
|
||||
code.Make(code.OpGetGlobal, 1),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
type compilerTestCase struct {
|
||||
input string
|
||||
expectedConstants []interface{}
|
||||
expectedInstructions []code.Instructions
|
||||
}
|
||||
|
||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||
t.Helper()
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
compiler := New()
|
||||
err := compiler.Compile(program)
|
||||
if err != nil {
|
||||
t.Fatalf("compiler error: %s", err)
|
||||
}
|
||||
|
||||
bytecode := compiler.Bytecode()
|
||||
|
||||
err = testInstructions(tt.expectedInstructions, bytecode.Instructions)
|
||||
if err != nil {
|
||||
t.Fatalf("testInstructions failed: %s", err)
|
||||
}
|
||||
|
||||
err = testConstants(t, tt.expectedConstants, bytecode.Constants)
|
||||
if err != nil {
|
||||
t.Fatalf("testConstants failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
return p.ParseProgram()
|
||||
}
|
||||
|
||||
func testInstructions(
|
||||
expected []code.Instructions,
|
||||
actual code.Instructions,
|
||||
) error {
|
||||
concatted := concatInstructions(expected)
|
||||
|
||||
if len(actual) != len(concatted) {
|
||||
return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q",
|
||||
concatted, actual)
|
||||
}
|
||||
|
||||
for i, ins := range concatted {
|
||||
if actual[i] != ins {
|
||||
return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q",
|
||||
i, concatted, actual)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func concatInstructions(s []code.Instructions) code.Instructions {
|
||||
out := code.Instructions{}
|
||||
|
||||
for _, ins := range s {
|
||||
out = append(out, ins...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func testConstants(
|
||||
t *testing.T,
|
||||
expected []interface{},
|
||||
actual []object.Object,
|
||||
) error {
|
||||
if len(expected) != len(actual) {
|
||||
return fmt.Errorf("wrong number of constants. got=%d, want=%d",
|
||||
len(actual), len(expected))
|
||||
}
|
||||
|
||||
for i, constant := range expected {
|
||||
switch constant := constant.(type) {
|
||||
case int:
|
||||
err := testIntegerObject(int64(constant), actual[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("constant %d - testIntegerObject failed: %s",
|
||||
i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) error {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not Integer. got=%T (%+v)",
|
||||
actual, actual)
|
||||
}
|
||||
|
||||
if result.Value != expected {
|
||||
return fmt.Errorf("object has wrong value. got=%d, want=%d",
|
||||
result.Value, expected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
35
wcig_code_1_2/05/src/monkey/compiler/symbol_table.go
Normal file
35
wcig_code_1_2/05/src/monkey/compiler/symbol_table.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package compiler
|
||||
|
||||
type SymbolScope string
|
||||
|
||||
const (
|
||||
GlobalScope SymbolScope = "GLOBAL"
|
||||
)
|
||||
|
||||
type Symbol struct {
|
||||
Name string
|
||||
Scope SymbolScope
|
||||
Index int
|
||||
}
|
||||
|
||||
type SymbolTable struct {
|
||||
store map[string]Symbol
|
||||
numDefinitions int
|
||||
}
|
||||
|
||||
func NewSymbolTable() *SymbolTable {
|
||||
s := make(map[string]Symbol)
|
||||
return &SymbolTable{store: s}
|
||||
}
|
||||
|
||||
func (s *SymbolTable) Define(name string) Symbol {
|
||||
symbol := Symbol{Name: name, Index: s.numDefinitions, Scope: GlobalScope}
|
||||
s.store[name] = symbol
|
||||
s.numDefinitions++
|
||||
return symbol
|
||||
}
|
||||
|
||||
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
||||
obj, ok := s.store[name]
|
||||
return obj, ok
|
||||
}
|
||||
45
wcig_code_1_2/05/src/monkey/compiler/symbol_table_test.go
Normal file
45
wcig_code_1_2/05/src/monkey/compiler/symbol_table_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package compiler
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDefine(t *testing.T) {
|
||||
expected := map[string]Symbol{
|
||||
"a": Symbol{Name: "a", Scope: GlobalScope, Index: 0},
|
||||
"b": Symbol{Name: "b", Scope: GlobalScope, Index: 1},
|
||||
}
|
||||
|
||||
global := NewSymbolTable()
|
||||
|
||||
a := global.Define("a")
|
||||
if a != expected["a"] {
|
||||
t.Errorf("expected a=%+v, got=%+v", expected["a"], a)
|
||||
}
|
||||
|
||||
b := global.Define("b")
|
||||
if b != expected["b"] {
|
||||
t.Errorf("expected b=%+v, got=%+v", expected["b"], b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGlobal(t *testing.T) {
|
||||
global := NewSymbolTable()
|
||||
global.Define("a")
|
||||
global.Define("b")
|
||||
|
||||
expected := []Symbol{
|
||||
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
|
||||
Symbol{Name: "b", Scope: GlobalScope, Index: 1},
|
||||
}
|
||||
|
||||
for _, sym := range expected {
|
||||
result, ok := global.Resolve(sym.Name)
|
||||
if !ok {
|
||||
t.Errorf("name %s not resolvable", sym.Name)
|
||||
continue
|
||||
}
|
||||
if result != sym {
|
||||
t.Errorf("expected %s to resolve to %+v, got=%+v",
|
||||
sym.Name, sym, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
117
wcig_code_1_2/05/src/monkey/evaluator/builtins.go
Normal file
117
wcig_code_1_2/05/src/monkey/evaluator/builtins.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"monkey/object"
|
||||
)
|
||||
|
||||
var builtins = map[string]*object.Builtin{
|
||||
"len": &object.Builtin{Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
|
||||
switch arg := args[0].(type) {
|
||||
case *object.Array:
|
||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
||||
case *object.String:
|
||||
return &object.Integer{Value: int64(len(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
},
|
||||
},
|
||||
"puts": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"first": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"last": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"rest": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]object.Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &object.Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"push": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]object.Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &object.Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue