Browse Source

Considering objects with fields.

Beoran 4 years ago
parent
commit
57076218b6
5 changed files with 219 additions and 11 deletions
  1. 49 0
      builtin.go
  2. 16 1
      door.go
  3. 1 1
      map.go
  4. 141 0
      object.go
  5. 12 9
      parser.go

+ 49 - 0
builtin.go

@@ -460,6 +460,51 @@ func comma(vm * VM, val ...Value) []Value {
     }
 }
 
+// Get an object field
+func of(vm * VM, val ... Value) []Value {
+	var object Value
+	var name string
+	_, err := ParseArgs(val, &object, &name)
+	if err != nil {
+		return Fail(err)
+	}
+	target, ok := object.(Object)
+	if !ok {
+		return Fail(vm.Errorf("Target cannot be invoked: %s %v", name, object))
+	}	
+	return Ok(target.Get(name))
+}
+
+// Set an object field
+func _make(vm * VM, val ... Value) []Value {
+	var object Value
+	var name string
+	rest, err := ParseArgs(val, &object, &name)
+	if err != nil {
+		return Fail(err)
+	}	
+	target, ok := object.(Object)
+	if !ok {
+		return Fail(vm.Errorf("Target cannot be invoked: %s %v", name, object))
+	}	
+	return Ok(target.Set(name, rest[0]))
+}
+
+// Call an object method
+func call(vm * VM, val ... Value) []Value {
+	var object Value
+	var name string
+	rest, err := ParseArgs(val, &object, &name)
+	if err != nil {
+		return Fail(err)
+	}
+	target, ok := object.(Object)
+	if !ok {
+		return Fail(vm.Errorf("Target cannot be invoked: %s %v", name, object))
+	}	
+	return target.Invoke(name, rest...)
+}
+
 
 func (vm *VM) RegisterBuiltinTypes() {
 	vm.RegisterTop("Int", IntType)
@@ -539,6 +584,10 @@ func (vm *VM) RegisterBuiltins() {
 			Over("subf", 0, FloatType, IntType),
 			Over("subf", 0, IntType, FloatType))
 
+	vm.RegisterBuiltinWithHelp("of", of, "Gets a field in an Object value").Takes(AnyType, StringType).Returns(AnyType)
+	vm.RegisterBuiltinWithHelp("make", _make, "Sets a field in an Object value").Takes(AnyType, StringType, AnyType).Returns(AnyType)
+	vm.RegisterBuiltinWithHelp("call", call, "Calls a method of an Object value").Takes(AnyType, StringType, AnyType).Returns(AnyType)
+	
 	vm.Alias("*", "mul")
 	vm.Alias("+", "add")
     vm.Alias("/", "div")

+ 16 - 1
door.go

@@ -4,6 +4,7 @@ import "fmt"
 
 // Door is an example of how to wrap a custom type in Muesli.
 type Door struct {
+    *BasicObject
 	name string
 	locked bool
 	opened bool
@@ -44,7 +45,21 @@ func door(vm *VM, val ...Value) [] Value {
 		return Fail(err)
 	}
 	d := &Door{name: name, locked: locked, opened:false}
-	return Ok(d)
+    d.BasicObject = NewBasicObject(d, nil)
+    d.Getter("name", func() Value {
+            return StringValue(d.name)
+        })
+	d.Getter("locked", func() Value {
+            return BoolValue(d.locked)
+        })
+	d.Getter("opened", func() Value {
+            return BoolValue(d.opened)
+        })
+    d.Method("open", func(...Value) []Value { 
+        d.Open()
+        return Ok(d)
+        })
+    return Ok(d)
 }
 
 func (door * Door) Open() {

+ 1 - 1
map.go

@@ -26,7 +26,7 @@ func (val MapValue) String() string {
 
 func NewMapValue(elements map[Value]Value) * MapValue {
 	mv :=  &MapValue{Map:elements}
-	mv.RedispatchCallable = NewRedispatchCallable("List", mv)
+	mv.RedispatchCallable = NewRedispatchCallable("Map", mv)
 	return mv
 }
 

+ 141 - 0
object.go

@@ -0,0 +1,141 @@
+package muesli
+
+type ObjectGetter func() Value
+type ObjectSetter func(Value) Value
+type ObjectMethod func(...Value) []Value
+
+
+// Object is an interface for a Value that has fields 
+// which can be get, set or invoked. The field names must be strings.
+// It is allowed, but not mandatory that names for Get, Set and Invoke
+// are stored separately.   
+type Object interface {
+	Value
+	Get(key string) Value
+	Set(key string, to Value) Value 
+	Invoke(key string, args... Value) []Value
+}
+
+
+
+// BasicObject is strut that implements Object.
+// An object may have a parent it inherit's it's fields and methods from.
+// Include it in your own struct to endow it with getters and setters, 
+// and methods more easily.
+type BasicObject struct {
+    RedispatchCallable
+    Parent * BasicObject
+	Object interface{}
+    Getters map[string] ObjectGetter
+    Setters map[string] ObjectSetter
+    Methods map[string] ObjectMethod
+}
+
+const ObjectType = TypeValue("Object")
+
+func (v BasicObject) Type() TypeValue    { return ObjectType }
+
+func (val BasicObject) String() string {
+	res := "{"
+	sep := ""
+	for k, v := range val.Getters {
+		res = res + sep + k + "=>" + v().String()
+		sep = ", "
+	}
+	res += "}"
+	return res
+}
+
+// Adds a getter to an object
+func (o *BasicObject) Getter(name string, getter ObjectGetter) *BasicObject {
+    o.Getters[name] = getter
+    return o
+}
+
+// Adds a setter to an object
+func (o *BasicObject) Setter(name string, setter ObjectSetter) *BasicObject {
+    o.Setters[name] = setter
+    return o
+}
+
+// Adds a method to an object
+func (o *BasicObject) Method(name string, method ObjectMethod) *BasicObject {
+    o.Methods[name] = method
+    return o
+}
+
+func NewBasicObject(object interface {}, parent *BasicObject) *BasicObject {
+	mv :=  &BasicObject{Object:object, 
+        Parent: parent,
+        Getters: make(map[string] ObjectGetter),
+        Setters: make(map[string] ObjectSetter),
+        Methods: make(map[string] ObjectMethod),
+    }    
+	mv.RedispatchCallable = NewRedispatchCallable("Object", mv)
+	return mv
+}
+
+func (m *BasicObject) Get(key string) Value {
+	var res ObjectGetter
+	var ok bool
+	if res, ok = m.Getters[key] ; !ok {
+        if m.Parent != nil {
+            return m.Parent.Get(key)
+        }
+		return NilValue
+	}
+	return res()
+}
+
+func (m *BasicObject) Set(key string, to Value) Value {
+	var res ObjectSetter
+	var ok bool
+	if res, ok = m.Setters[key] ; !ok {
+        if m.Parent != nil {
+            return m.Parent.Set(key, to)
+        }
+		return NilValue
+	}
+	return res(to)
+}
+
+func (m *BasicObject) Invoke(key string, args... Value) []Value {
+	var res ObjectMethod
+	var ok bool
+	if res, ok = m.Methods[key] ; !ok {
+        if m.Parent != nil {
+            return m.Parent.Invoke(key, args...)
+        }
+		return Fail(NewErrorValuef("No such method %s", key))
+	}
+	return res(args...)
+}
+
+func (from *BasicObject) Convert(to interface{}) error {
+	switch toPtr := to.(type) {
+		case **BasicObject:
+			(*toPtr) = from
+		case *Value:
+			(*toPtr) = from			
+		default:
+			return NewErrorValuef("Cannot convert value %v to %v", from, to)
+	}
+	return nil
+}
+
+// BasicObject implements Object
+var _ Object = &BasicObject{}
+
+
+// Flex can have any field just by setting it, it can be wrapped in 
+// an BasicObject.
+type Flex struct {
+    Fields map [string]Value
+}
+
+
+
+
+
+
+

+ 12 - 9
parser.go

@@ -94,21 +94,24 @@ as follows:
 - A name also evaluates as itself but is specific for direct commands.
 
 
-Simpler syntax without operators but with fields.
+Keeping operations, simplified, adding fields with selectors.
 
 PROGRAM -> STATEMENTS .
 STATEMENTS -> STATEMENT STATEMENTS | .
-STATEMENT -> BLOCK | EXPRESSION eos | eos .
+STATEMENT -> BLOCK | CHAIN eos | eos .
+CHAIN -> EXPRESSION OPERATIONS . 
+OPERATIONS -> LINK | .
+LINK -> operator EXPRESSION .
 EXPRESSION -> COMMAND | SUBSTITUTION |  LITERAL .
-COMMAND -> FIELD PARAMETERS .
+COMMAND -> FIELD PARAMETERS.
+FIELD -> NAME SELECTORS . 
+SELECTORS -> SELECTOR | .
+SELECTOR -> selector FIELD .
 PARAMETERS -> PARAMETER PARAMETERS | .
-PARAMETER -> LITERAL | BLOCK | SUBSTITUTION | FIELD .
-SUBSTITUTION ->  GETTER | SETTER | LIST | PARENTHESIS .
-PARENTHESIS -> closeparen EXPRESSION openparen .
+PARAMETER -> LITERAL | BLOCK |  SUBSTITUTION | FIELD .
+SUBSTITUTION -> GETTER | SETTER | LIST | PARENTHESIS .
+PARENTHESIS -> closeparen CHAIN openparen .
 BLOCK -> openblock STATEMENTS closeblock .
-FIELD -> NAME PROPERTIES .
-PROPERTIES -> PROPERTY PROPERTIES | .
-PROPERTY -> s NAME  | comma NAME .
 LIST -> openlist PARAMETERS closelist .
 LITERAL -> string | int | float .
 NAME -> word | symbol | type .