Created
February 14, 2019 14:47
-
-
Save prologic/b35cd061bb2fc2d9e458037840ceb0cb to your computer and use it in GitHub Desktop.
Object Model for Monkey
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/ast/ast.go b/ast/ast.go | |
index e67346c..ea3ab30 100644 | |
--- a/ast/ast.go | |
+++ b/ast/ast.go | |
@@ -187,6 +187,19 @@ func (i *Identifier) TokenLiteral() string { return i.Token.Literal } | |
// String returns a stringified version of the AST for debugging | |
func (i *Identifier) String() string { return i.Value } | |
+// Object represents a object value | |
+type Object struct { | |
+ Token token.Token | |
+} | |
+ | |
+func (o *Object) expressionNode() {} | |
+ | |
+// TokenLiteral prints the literal value of the token associated with this node | |
+func (o *Object) TokenLiteral() string { return o.Token.Literal } | |
+ | |
+// String returns a stringified version of the AST for debugging | |
+func (o *Object) String() string { return o.Token.Literal } | |
+ | |
// Null represents a null value | |
type Null struct { | |
Token token.Token | |
diff --git a/eval/eval.go b/eval/eval.go | |
index eff6dbf..5bbac04 100644 | |
--- a/eval/eval.go | |
+++ b/eval/eval.go | |
@@ -20,6 +20,9 @@ var ( | |
// NULL is a cached Null object | |
NULL = &object.Null{} | |
+ | |
+ // OBJECT is a cached ObjectObject object | |
+ OBJECT = object.NewObject() | |
) | |
func fromNativeBoolean(input bool) *object.Boolean { | |
@@ -75,6 +78,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { | |
return fromNativeBoolean(node.Value) | |
case *ast.Null: | |
return NULL | |
+ case *ast.Object: | |
+ return OBJECT | |
case *ast.PrefixExpression: | |
right := Eval(node.Right, env) | |
@@ -168,6 +173,16 @@ func Eval(node ast.Node, env *object.Environment) object.Object { | |
} else { | |
return newError("cannot index hash with %T", key) | |
} | |
+ } else if obj, ok := obj.(*object.ObjectObject); ok { | |
+ key := Eval(ie.Index, env) | |
+ if isError(key) { | |
+ return key | |
+ } | |
+ if hashKey, ok := key.(object.Hashable); ok { | |
+ obj.SetItem(hashKey, value) | |
+ } else { | |
+ return newError("cannot index hash with %T", key) | |
+ } | |
} else { | |
return newError("object type %T does not support item assignment", obj) | |
} | |
@@ -448,7 +463,23 @@ func evalExpressions( | |
func applyFunction(fn object.Object, args []object.Object) object.Object { | |
switch fn := fn.(type) { | |
+ case *object.ObjectObject: | |
+ obj := fn.New(fn.Name) | |
+ if initFn := obj.GetItem(&object.String{Value: "init"}); initFn != nil { | |
+ applyFunction(initFn, args) | |
+ } | |
+ return obj | |
+ | |
+ case *object.Method: | |
+ if result := fn.Method(fn.Self, args...); result != nil { | |
+ return result | |
+ } | |
+ return NULL | |
+ | |
case *object.Function: | |
+ if fn.Parameters[0].Value == "self" { | |
+ args = append([]object.Object{fn.Self}, args...) | |
+ } | |
env := extendFunctionEnv(fn, args) | |
return unwrapReturnValue(Eval(fn.Body, env)) | |
@@ -484,7 +515,7 @@ func unwrapReturnValue(obj object.Object) object.Object { | |
return obj | |
} | |
-func evalIndexAssignmentExpression(left, index, value object.Object) object.Object { | |
+func evalIndexExpression(left, index object.Object) object.Object { | |
switch { | |
case left.Type() == object.STRING && index.Type() == object.INTEGER: | |
return evalStringIndexExpression(left, index) | |
@@ -492,22 +523,22 @@ func evalIndexAssignmentExpression(left, index, value object.Object) object.Obje | |
return evalArrayIndexExpression(left, index) | |
case left.Type() == object.HASH: | |
return evalHashIndexExpression(left, index) | |
+ case left.Type() == object.OBJECT: | |
+ return evalObjectIndexExpression(left, index) | |
default: | |
return newError("index operator not supported: %s", left.Type()) | |
} | |
} | |
-func evalIndexExpression(left, index object.Object) object.Object { | |
- switch { | |
- case left.Type() == object.STRING && index.Type() == object.INTEGER: | |
- return evalStringIndexExpression(left, index) | |
- case left.Type() == object.ARRAY && index.Type() == object.INTEGER: | |
- return evalArrayIndexExpression(left, index) | |
- case left.Type() == object.HASH: | |
- return evalHashIndexExpression(left, index) | |
- default: | |
- return newError("index operator not supported: %s", left.Type()) | |
+func evalObjectIndexExpression(obj, index object.Object) object.Object { | |
+ o := obj.(*object.ObjectObject) | |
+ | |
+ key, ok := index.(object.Hashable) | |
+ if !ok { | |
+ return newError("unusable as hash key: %s", index.Type()) | |
} | |
+ | |
+ return o.GetItem(key) | |
} | |
func evalHashIndexExpression(hash, index object.Object) object.Object { | |
diff --git a/object/object.go b/object/object.go | |
index c68c75d..287aba1 100644 | |
--- a/object/object.go | |
+++ b/object/object.go | |
@@ -15,6 +15,9 @@ import ( | |
) | |
const ( | |
+ // OBJECT is the ObjectObject object type | |
+ OBJECT = "OBJECT" | |
+ | |
// INTEGER is the Integer object type | |
INTEGER = "INTEGER" | |
@@ -36,6 +39,9 @@ const ( | |
// FUNCTION is the Function object type | |
FUNCTION = "FUNCTION" | |
+ // METHOD is the Method object type | |
+ METHOD = "METHOD" | |
+ | |
// COMPILED_FUNCTION is the CompiledFunction object type | |
COMPILED_FUNCTION = "COMPILED_FUNCTION" | |
@@ -67,6 +73,9 @@ type Hashable interface { | |
// BuiltinFunction represents the builtin function type | |
type BuiltinFunction func(args ...Object) Object | |
+// BuiltinMethod represents the builtin function type | |
+type BuiltinMethod func(self *ObjectObject, args ...Object) Object | |
+ | |
// Type represents the type of an object | |
type Type string | |
@@ -78,6 +87,86 @@ type Object interface { | |
Inspect() string | |
} | |
+// ObjectObject is the `object` data type and ancesstor of all objects. | |
+// New objects are created from calling `object.clone()` and setting | |
+// new properties. Instances are created by calling the object as a | |
+// function. e.g: let MyObject = object.clone(); let obj = MyObject() | |
+type ObjectObject struct { | |
+ Name *String | |
+ Parent *ObjectObject | |
+ Members *Hash | |
+} | |
+ | |
+func NewObject() Object { | |
+ obj := &ObjectObject{ | |
+ Name: &String{Value: "Object"}, | |
+ Members: NewHash(), | |
+ } | |
+ obj.Parent = obj | |
+ | |
+ methods := map[string]*Method{ | |
+ "clone": &Method{ | |
+ Name: "clone", | |
+ Method: func(self *ObjectObject, args ...Object) Object { | |
+ if len(args) != 1 { | |
+ return newError("wrong number of arguments. got=%d, want=1", | |
+ len(args)) | |
+ } | |
+ | |
+ if args[0].Type() != STRING { | |
+ return newError( | |
+ "argument to `clone` must be STRING got %s", | |
+ args[0].Type(), | |
+ ) | |
+ } | |
+ | |
+ name, _ := args[0].(*String) | |
+ | |
+ return self.New(name) | |
+ }, | |
+ }, | |
+ } | |
+ | |
+ for name, method := range methods { | |
+ obj.SetItem(&String{Value: name}, method) | |
+ } | |
+ | |
+ return obj | |
+} | |
+ | |
+func (o *ObjectObject) New(name *String) *ObjectObject { | |
+ return &ObjectObject{Name: name, Members: NewHash(), Parent: o} | |
+} | |
+ | |
+func (o *ObjectObject) GetItem(key Hashable) Object { | |
+ value, ok := o.Members.Pairs[key.HashKey()] | |
+ if !ok && o.Parent != o { | |
+ return o.Parent.GetItem(key) | |
+ } | |
+ return value.Value | |
+} | |
+ | |
+func (o *ObjectObject) SetItem(key Hashable, value Object) { | |
+ if method, ok := value.(*Method); ok { | |
+ method.Self = o | |
+ } else if function, ok := value.(*Function); ok { | |
+ function.Self = o | |
+ } | |
+ o.Members.Pairs[key.HashKey()] = HashPair{Key: key.(Object), Value: value} | |
+} | |
+ | |
+func (o *ObjectObject) String() string { | |
+ return o.Inspect() | |
+} | |
+ | |
+// Type returns the type of the object | |
+func (o *ObjectObject) Type() Type { return OBJECT } | |
+ | |
+// Inspect returns a stringified version of the object for debugging | |
+func (o *ObjectObject) Inspect() string { | |
+ return fmt.Sprintf("%s_0x%d", o.Name, &o) | |
+} | |
+ | |
// Integer is the integer type used to represent integer literals and holds | |
// an internal int64 value | |
type Integer struct { | |
@@ -219,6 +308,7 @@ func (cf *CompiledFunction) Inspect() string { | |
type Function struct { | |
Parameters []*ast.Identifier | |
Body *ast.BlockStatement | |
+ Self *ObjectObject | |
Env *Environment | |
} | |
@@ -248,6 +338,27 @@ func (f *Function) Inspect() string { | |
return out.String() | |
} | |
+// Method is the method object type that simply holds a reference to | |
+// a BuiltinMethod type that takes self as a first argument and zero or | |
+// more objects as arguments and returns an object. | |
+type Method struct { | |
+ Name string | |
+ Self *ObjectObject | |
+ Method BuiltinMethod | |
+} | |
+ | |
+func (m *Method) String() string { | |
+ return m.Inspect() | |
+} | |
+ | |
+// Type returns the type of the object | |
+func (m *Method) Type() Type { return METHOD } | |
+ | |
+// Inspect returns a stringified version of the object for debugging | |
+func (m *Method) Inspect() string { | |
+ return fmt.Sprintf("<built-in method %s>", m.Name) | |
+} | |
+ | |
// Builtin is the builtin object type that simply holds a reference to | |
// a BuiltinFunction type that takes zero or more objects as arguments | |
// and returns an object. | |
@@ -359,6 +470,10 @@ type Hash struct { | |
Pairs map[HashKey]HashPair | |
} | |
+func NewHash() *Hash { | |
+ return &Hash{Pairs: make(map[HashKey]HashPair)} | |
+} | |
+ | |
func (h *Hash) String() string { | |
return h.Inspect() | |
} | |
diff --git a/parser/parser.go b/parser/parser.go | |
index 03e9abb..47eb804 100644 | |
--- a/parser/parser.go | |
+++ b/parser/parser.go | |
@@ -71,6 +71,7 @@ func New(l *lexer.Lexer) *Parser { | |
p.registerPrefix(token.TRUE, p.parseBoolean) | |
p.registerPrefix(token.FALSE, p.parseBoolean) | |
p.registerPrefix(token.NULL, p.parseNull) | |
+ p.registerPrefix(token.OBJECT, p.parseObject) | |
p.registerPrefix(token.LPAREN, p.parseGroupedExpression) | |
p.registerPrefix(token.IF, p.parseIfExpression) | |
p.registerPrefix(token.WHILE, p.parseWhileExpression) | |
@@ -332,6 +333,10 @@ func (p *Parser) parseNull() ast.Expression { | |
return &ast.Null{Token: p.curToken} | |
} | |
+func (p *Parser) parseObject() ast.Expression { | |
+ return &ast.Object{Token: p.curToken} | |
+} | |
+ | |
func (p *Parser) parseGroupedExpression() ast.Expression { | |
p.nextToken() | |
diff --git a/token/token.go b/token/token.go | |
index b6d7698..620b17b 100644 | |
--- a/token/token.go | |
+++ b/token/token.go | |
@@ -96,6 +96,8 @@ const ( | |
FALSE = "FALSE" | |
// NULL the `null` keyword (null) | |
NULL = "NULL" | |
+ // OBJECT the `object` keyword (object) | |
+ OBJECT = "OBJECT" | |
// IF the `if` keyword (if) | |
IF = "IF" | |
// ELSE the `else` keyword (else) | |
@@ -112,6 +114,7 @@ var keywords = map[string]Type{ | |
"true": TRUE, | |
"false": FALSE, | |
"null": NULL, | |
+ "object": OBJECT, | |
"if": IF, | |
"else": ELSE, | |
"return": RETURN, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment