Skip to content

Instantly share code, notes, and snippets.

@prologic
Created February 14, 2019 14:47
Show Gist options
  • Save prologic/b35cd061bb2fc2d9e458037840ceb0cb to your computer and use it in GitHub Desktop.
Save prologic/b35cd061bb2fc2d9e458037840ceb0cb to your computer and use it in GitHub Desktop.
Object Model for Monkey
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