Просмотр исходного кода

wallet: add an expr engine for fast evaluation of exprs inside render loops

rsx 2 лет назад
Родитель
Сommit
76d1ec7a04

+ 2 - 0
bin/darkwallet/Cargo.toml

@@ -21,6 +21,8 @@ thiserror = "1.0.57"
 smol = "1.3.0"
 atomic_float = "0.1.0"
 
+starlark = "0.12.0"
+
 [dependencies.pyo3]
 version = "0.21.2"
 features = ["auto-initialize"]

+ 22 - 30
bin/darkwallet/gui/__init__.py

@@ -281,40 +281,28 @@ def draw():
         "is_visible", PropertyType.BOOL, PropertySubType.NULL,
         None,
         "is_visible", "Visibility of the layer",
-        False, 1, None, None, []
+        False, False, 1, None, None, []
     )
     api.add_property(layer_id, prop)
     api.set_property_bool(layer_id, "is_visible", 0, True)
 
-    #prop = Property(
-    #    "rect", PropertyType.UINT32, PropertySubType.PIXEL,
-    #    None,
-    #    "layer_rect", "(x, y, w, h) viewport rectangle for current layer",
-    #    False, 4, None, None, []
-    #)
     prop = Property(
-        "rect", PropertyType.STR, PropertySubType.PY_EXPR,
-        ["0", "0", "0", "0"],
+        "rect", PropertyType.UINT32, PropertySubType.PIXEL,
+        None,
         "mesh_rect", "The position and size within the layer",
-        False, 4, None, None, []
+        False, True, 4, None, None, []
     )
     api.add_property(layer_id, prop)
-    ## x
-    #api.set_property_u32(layer_id, "rect", 0, 0)
-    ## y
-    #api.set_property_u32(layer_id, "rect", 1, 0)
-    ## w
-    #api.set_property_u32(layer_id, "rect", 2, int(3838/2))
-    ## h
-    #api.set_property_u32(layer_id, "rect", 3, int(2158/2))
     # x
-    api.set_property_str(layer_id, "rect", 0, "0")
+    api.set_property_u32(layer_id, "rect", 0, 0)
     # y
-    api.set_property_str(layer_id, "rect", 1, "0")
+    api.set_property_u32(layer_id, "rect", 1, 0)
     # w
-    api.set_property_str(layer_id, "rect", 2, "sw/2")
+    #api.set_property_u32(layer_id, "rect", 2, int(3838/2))
+    code = [["as_u32", ["/", ["load", "sw"], ["u32", 2]]]]
+    api.set_property_expr(layer_id, "rect", 2, code)
     # h
-    api.set_property_str(layer_id, "rect", 3, "sh")
+    api.set_property_u32(layer_id, "rect", 3, int(2158/2))
 
     api.link_node(layer_id, win_id)
 
@@ -326,7 +314,7 @@ def draw():
         "data", PropertyType.BUFFER, PropertySubType.NULL,
         None,
         "mesh_data", "The face and vertex data for the mesh",
-        False, 2, None, None, []
+        False, False, 2, None, None, []
     )
     api.add_property(mesh_id, prop)
 
@@ -346,20 +334,24 @@ def draw():
     api.set_property_buf(mesh_id, "data", 1, faces)
 
     prop = Property(
-        "rect", PropertyType.STR, PropertySubType.PY_EXPR,
-        ["0", "0", "0", "0"],
+        "rect", PropertyType.FLOAT32, PropertySubType.PIXEL,
+        None,
         "mesh_rect", "The position and size within the layer",
-        False, 4, None, None, []
+        False, True, 4, None, None, []
     )
     api.add_property(mesh_id, prop)
     # x
-    api.set_property_str(mesh_id, "rect", 0, "10")
+    api.set_property_f32(mesh_id, "rect", 0, 10)
     # y
-    api.set_property_str(mesh_id, "rect", 1, "10")
+    api.set_property_f32(mesh_id, "rect", 1, 10)
     # w
-    api.set_property_str(mesh_id, "rect", 2, "lw - 10")
+    #api.set_property_f32(mesh_id, "rect", 2, 20)
+    code = [["-", ["load", "lw"], ["f32", 10]]]
+    api.set_property_expr(mesh_id, "rect", 2, code)
     # h
-    api.set_property_str(mesh_id, "rect", 3, "lh - 10")
+    #api.set_property_str(mesh_id, "rect", 3, "lh - 10")
+    code = [["-", ["load", "lh"], ["f32", 10]]]
+    api.set_property_f32(mesh_id, "rect", 3, 20)
 
     api.link_node(mesh_id, layer_id)
 

+ 75 - 41
bin/darkwallet/pydrk/api.py

@@ -1,7 +1,6 @@
 import zmq
 from collections import namedtuple
-from . import serial
-from . import exc
+from . import serial, exc, expr
 
 Property = namedtuple("Property", [
     "name",
@@ -11,6 +10,7 @@ Property = namedtuple("Property", [
     "ui_name",
     "desc",
     "is_null_allowed",
+    "is_expr_allowed",
     "array_len",
     "min_val",
     "max_val",
@@ -67,6 +67,7 @@ class PropertyType:
     ENUM = 5
     BUFFER = 6
     SCENE_NODE_ID = 7
+    SEXPR = 8
 
     @staticmethod
     def to_str(prop_type):
@@ -87,13 +88,14 @@ class PropertyType:
                 return "buffer"
             case PropertyType.SCENE_NODE_ID:
                 return "scene_node_id"
+            case PropertyType.SEXPR:
+                return "sexpr"
 
 class PropertySubType:
     NULL = 0
     COLOR = 1
     PIXEL = 2
     RESOURCE_ID = 3
-    PY_EXPR = 4
 
     @staticmethod
     def to_str(prop_type):
@@ -106,8 +108,6 @@ class PropertySubType:
                 return "pixel"
             case PropertySubType.RESOURCE_ID:
                 return "resource_id"
-            case PropertySubType.PY_EXPR:
-                return "py_expr"
 
 class PropertyStatus:
     OK = 0
@@ -127,23 +127,26 @@ class ErrorCode:
     PROPERTY_WRONG_INDEX = 10
     PROPERTY_OUT_OF_RANGE = 11
     PROPERTY_NULL_NOT_ALLOWED = 12
-    PROPERTY_IS_BOUNDED = 13
-    PROPERTY_WRONG_ENUM_ITEM = 14
-    SIGNAL_ALREADY_EXISTS = 15
-    SIGNAL_NOT_FOUND = 16
-    SLOT_NOT_FOUND = 17
-    METHOD_ALREADY_EXISTS = 18
-    METHOD_NOT_FOUND = 19
-    NODES_ARE_LINKED = 20
-    NODES_NOT_LINKED = 21
-    NODE_HAS_PARENTS = 22
-    NODE_HAS_CHILDREN = 23
-    NODE_PARENT_NAME_CONFLICT = 24
-    NODE_CHILD_NAME_CONFLICT = 25
-    NODE_SIBLING_NAME_CONFLICT = 26
-    FILE_NOT_FOUND = 27
-    RESOURCE_NOT_FOUND = 28
-    PY_EVAL_ERR = 29
+    PROPERTY_SEXPR_NOT_ALLOWED = 13
+    PROPERTY_IS_BOUNDED = 14
+    PROPERTY_WRONG_ENUM_ITEM = 15
+    SIGNAL_ALREADY_EXISTS = 16
+    SIGNAL_NOT_FOUND = 17
+    SLOT_NOT_FOUND = 18
+    METHOD_ALREADY_EXISTS = 19
+    METHOD_NOT_FOUND = 20
+    NODES_ARE_LINKED = 21
+    NODES_NOT_LINKED = 22
+    NODE_HAS_PARENTS = 23
+    NODE_HAS_CHILDREN = 24
+    NODE_PARENT_NAME_CONFLICT = 25
+    NODE_CHILD_NAME_CONFLICT = 26
+    NODE_SIBLING_NAME_CONFLICT = 27
+    FILE_NOT_FOUND = 28
+    RESOURCE_NOT_FOUND = 29
+    PY_EVAL_ERR = 30
+    SEXPR_EMPTY = 31
+    SEXPR_GLOBAL_NOT_FOUND = 32
 
     @staticmethod
     def to_str(errc):
@@ -170,6 +173,8 @@ class ErrorCode:
                 return "property_out_of_range"
             case ErrorCode.PROPERTY_NULL_NOT_ALLOWED:
                 return "property_null_not_allowed"
+            case ErrorCode.PROPERTY_SEXPR_NOT_ALLOWED:
+                return "property_sexpr_not_allowed"
             case ErrorCode.PROPERTY_IS_BOUNDED:
                 return "property_is_bounded"
             case ErrorCode.PROPERTY_WRONG_ENUM_ITEM:
@@ -204,6 +209,10 @@ class ErrorCode:
                 return "resource_not_found"
             case ErrorCode.PY_EVAL_ERR:
                 return "py_eval_err"
+            case ErrorCode.SEXPR_EMPTY:
+                return "sexpr_empty"
+            case ErrorCode.SEXPR_GLOBAL_NOT_FOUND:
+                return "sexpr_global_not_found"
 
 def vertex(x, y, r, g, b, a, u, v):
     buf = bytearray()
@@ -265,40 +274,46 @@ class Api:
                 raise exc.PropertyOutOfRange
             case 12:
                 raise exc.PropertyNullNotAllowed
-            case 13:
-                raise exc.PropertyIsBounded
+            case 12:
+                raise exc.PropertySExprNotAllowed
             case 14:
-                raise exc.PropertyWrongEnumItem
+                raise exc.PropertyIsBounded
             case 15:
-                raise exc.SignalAlreadyExists
+                raise exc.PropertyWrongEnumItem
             case 16:
-                raise exc.SignalNotFound
+                raise exc.SignalAlreadyExists
             case 17:
-                raise exc.SlotNotFound
+                raise exc.SignalNotFound
             case 18:
-                raise exc.MethodAlreadyExists
+                raise exc.SlotNotFound
             case 19:
-                raise exc.MethodNotFound
+                raise exc.MethodAlreadyExists
             case 20:
-                raise exc.NodesAreLinked
+                raise exc.MethodNotFound
             case 21:
-                raise exc.NodesNotLinked
+                raise exc.NodesAreLinked
             case 22:
-                raise exc.NodeHasParents
+                raise exc.NodesNotLinked
             case 23:
-                raise exc.NodeHasChildren
+                raise exc.NodeHasParents
             case 24:
-                raise exc.NodeParentNameConflict
+                raise exc.NodeHasChildren
             case 25:
-                raise exc.NodeChildNameConflict
+                raise exc.NodeParentNameConflict
             case 26:
-                raise exc.NodeSiblingNameConflict
+                raise exc.NodeChildNameConflict
             case 27:
-                raise exc.FileNotFound
+                raise exc.NodeSiblingNameConflict
             case 28:
-                raise exc.ResourceNotFound
+                raise exc.FileNotFound
             case 29:
+                raise exc.ResourceNotFound
+            case 30:
                 raise exc.PyEvalErr
+            case 31:
+                raise exc.SExprEmpty
+            case 32:
+                raise exc.SExprGlobalNotFound
         return cursor
 
     def hello(self):
@@ -366,6 +381,8 @@ class Api:
                 serial.decode_str(cur),
                 # is_null_allowed 
                 bool(serial.read_u8(cur)),
+                # is_expr_allowed 
+                bool(serial.read_u8(cur)),
                 # array_len 
                 serial.read_u32(cur),
                 # min_val 
@@ -483,6 +500,7 @@ class Api:
         serial.encode_str(req, prop.ui_name)
         serial.encode_str(req, prop.desc)
         serial.write_u8(req, int(prop.is_null_allowed))
+        serial.write_u8(req, int(prop.is_expr_allowed))
 
         def write_mxx(v, by):
             assert v is not None
@@ -524,6 +542,7 @@ class Api:
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
         serial.write_u32(req, i)
+        serial.write_u8(req, PropertyType.BOOL)
         serial.write_u8(req, int(val))
         self._make_request(Command.SET_PROPERTY_VALUE, req)
 
@@ -532,6 +551,7 @@ class Api:
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
         serial.write_u32(req, i)
+        serial.write_u8(req, PropertyType.UINT32)
         serial.write_u32(req, val)
         self._make_request(Command.SET_PROPERTY_VALUE, req)
 
@@ -540,23 +560,37 @@ class Api:
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
         serial.write_u32(req, i)
+        serial.write_u8(req, PropertyType.FLOAT32)
         serial.write_f32(req, val)
         self._make_request(Command.SET_PROPERTY_VALUE, req)
 
+    def set_property_str(self, node_id, prop_name, i, val):
+        req = bytearray()
+        serial.write_u32(req, node_id)
+        serial.encode_str(req, prop_name)
+        serial.write_u32(req, i)
+        serial.write_u8(req, PropertyType.STR)
+        serial.encode_str(req, val)
+        self._make_request(Command.SET_PROPERTY_VALUE, req)
+
     def set_property_buf(self, node_id, prop_name, i, buf):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
         serial.write_u32(req, i)
+        serial.write_u8(req, PropertyType.BUFFER)
         serial.encode_buf(req, buf)
         self._make_request(Command.SET_PROPERTY_VALUE, req)
 
-    def set_property_str(self, node_id, prop_name, i, val):
+    def set_property_expr(self, node_id, prop_name, i, code):
         req = bytearray()
         serial.write_u32(req, node_id)
         serial.encode_str(req, prop_name)
         serial.write_u32(req, i)
-        serial.encode_str(req, val)
+        serial.write_u8(req, PropertyType.SEXPR)
+        serial.encode_varint(req, len(code))
+        for sexpr in code:
+            expr.encode_expr(req, sexpr)
         self._make_request(Command.SET_PROPERTY_VALUE, req)
 
     def get_signals(self, node_id):

+ 7 - 0
bin/darkwallet/pydrk/exc.py

@@ -22,6 +22,8 @@ class PropertyOutOfRange(Exception):
     pass
 class PropertyNullNotAllowed(Exception):
     pass
+class PropertySExprNotAllowed(Exception):
+    pass
 class PropertyIsBounded(Exception):
     pass
 class PropertyWrongEnumItem(Exception):
@@ -54,3 +56,8 @@ class FileNotFound(Exception):
     pass
 class PyEvalErr(Exception):
     pass
+class SExprEmpty(Exception):
+    pass
+class SExprGlobalNotFound(Exception):
+    pass
+

+ 87 - 0
bin/darkwallet/pydrk/expr.py

@@ -0,0 +1,87 @@
+from . import serial
+
+class Op:
+    NULL = 0
+    ADD = 1
+    SUB = 2
+    MUL = 3
+    DIV = 4
+    CONST_BOOL = 5
+    CONST_UINT_32 = 6
+    CONST_FLOAT_32 = 7
+    CONST_STR = 8
+    LOAD_VAR = 9
+    MIN = 11
+    MAX = 12
+    IS_EQUAL = 13
+    LESS_THAN = 14
+    FLOAT32_TO_UINT32 = 15
+
+    @staticmethod
+    def from_str(op):
+        match op:
+            case "null":
+                return Op.NULL
+            case "+":
+                return Op.ADD
+            case "-":
+                return Op.SUB
+            case "*":
+                return Op.MUL
+            case "/":
+                return Op.DIV
+            case "bool":
+                return Op.CONST_BOOL
+            case "u32":
+                return Op.CONST_UINT_32
+            case "f32":
+                return Op.CONST_FLOAT_32
+            case "str":
+                return Op.CONST_STR
+            case "load":
+                return Op.LOAD_VAR
+            case "min":
+                return Op.MIN
+            case "max":
+                return Op.MAX
+            case "==":
+                return Op.IS_EQUAL
+            case "<":
+                return Op.LESS_THAN
+            case "as_u32":
+                return Op.FLOAT32_TO_UINT32
+
+def encode_expr(by, code):
+    op, args = code[0], code[1:]
+    op = Op.from_str(op)
+    serial.write_u8(by, op)
+    match op:
+        case Op.CONST_BOOL:
+            serial.write_u8(by, int(args[0]))
+        case Op.CONST_UINT_32:
+            serial.write_u32(by, args[0])
+        case Op.CONST_FLOAT_32:
+            serial.write_f32(by, args[0])
+        case Op.CONST_STR:
+            serial.encode_str(by, args[0])
+        case Op.LOAD_VAR:
+            serial.encode_str(by, args[0])
+        case _:
+            for arg in args:
+                encode_expr(by, arg)
+
+# python -m pydrk.expr
+if __name__ == "__main__":
+    code = ["+",
+        ["u32", 5],
+        ["/",
+            ["load", "sw"],
+            ["u32", 2]
+        ]
+    ]
+    code_s = bytearray()
+    encode_expr(code_s, code)
+    assert code_s == bytearray(
+        [1, 6, 5, 0, 0, 0, 4, 9, 2, 115, 119, 6, 2, 0, 0, 0]
+    )
+

+ 2 - 13
bin/darkwallet/pydrk/serial.py

@@ -22,37 +22,26 @@ def write_f32(by, v):
 def encode_varint(by, v):
     if v <= 0xfc:
         write_u8(by, v)
-        return 1
     elif v <= 0xffff:
         write_u8(by, 0xfd)
         write_u16(by, v)
-        return 3
     elif v <= 0xffffffff:
         write_u8(by, 0xfe)
         write_u32(by, v)
-        return 5
     else:
         write_u8(by, 0xff)
         write_u64(by, v)
-        return 9
 
 def encode_str(by, s):
-    l = 0
-    l += encode_varint(by, len(s))
+    encode_varint(by, len(s))
     s_by = s.encode("utf-8")
-    l += len(s_by)
     by += s_by
-    return l
 
 def encode_buf(by, buf):
-    l = 0
-    l += encode_varint(by, len(buf))
-    l += len(buf)
+    encode_varint(by, len(buf))
     by += buf
-    return l
 
 def encode_opt(by, val, write_fn):
-    l = 0
     if val is None:
         write_u8(by, 0)
     else:

+ 26 - 17
bin/darkwallet/src/error.rs

@@ -39,54 +39,63 @@ pub enum Error {
     #[error("Property null not allowed")]
     PropertyNullNotAllowed = 12,
 
+    #[error("Property S-exprs not allowed")]
+    PropertySExprNotAllowed = 13,
+
     #[error("Property array is bounded length")]
-    PropertyIsBounded = 13,
+    PropertyIsBounded = 14,
 
     #[error("Property enum item is invalid")]
-    PropertyWrongEnumItem = 14,
+    PropertyWrongEnumItem = 15,
 
     #[error("Signal already exists")]
-    SignalAlreadyExists = 15,
+    SignalAlreadyExists = 16,
 
     #[error("Signal not found")]
-    SignalNotFound = 16,
+    SignalNotFound = 17,
 
     #[error("Slot not found")]
-    SlotNotFound = 17,
+    SlotNotFound = 18,
 
     #[error("Signal already exists")]
-    MethodAlreadyExists = 18,
+    MethodAlreadyExists = 19,
 
     #[error("Method not found")]
-    MethodNotFound = 19,
+    MethodNotFound = 20,
 
     #[error("Nodes are not linked")]
-    NodesAreLinked = 20,
+    NodesAreLinked = 21,
 
     #[error("Nodes are not linked")]
-    NodesNotLinked = 21,
+    NodesNotLinked = 22,
 
     #[error("Node has parents")]
-    NodeHasParents = 22,
+    NodeHasParents = 23,
 
     #[error("Node has children")]
-    NodeHasChildren = 23,
+    NodeHasChildren = 24,
 
     #[error("Node has a parent with this name")]
-    NodeParentNameConflict = 24,
+    NodeParentNameConflict = 25,
 
     #[error("Node has a child with this name")]
-    NodeChildNameConflict = 25,
+    NodeChildNameConflict = 26,
 
     #[error("Node has a sibling with this name")]
-    NodeSiblingNameConflict = 26,
+    NodeSiblingNameConflict = 27,
 
     #[error("File not found")]
-    FileNotFound = 27,
+    FileNotFound = 28,
 
     #[error("Resource is not found")]
-    ResourceNotFound = 28,
+    ResourceNotFound = 29,
 
     #[error("Python expr eval error")]
-    PyEvalErr = 29,
+    PyEvalErr = 30,
+
+    #[error("Empty S-expr")]
+    SExprEmpty = 31,
+
+    #[error("S-expr global not found")]
+    SExprGlobalNotFound = 32,
 }

+ 421 - 0
bin/darkwallet/src/expr.rs

@@ -0,0 +1,421 @@
+use crate::{
+    error::{Error, Result},
+    //prop::{Property, PropertySubType, PropertyType, PropertySExprValue},
+};
+use darkfi_serial::{Decodable, Encodable, ReadExt, SerialDecodable, SerialEncodable, WriteExt, serialize};
+use std::{io::{Read, Write}, sync::Arc};
+
+#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
+pub enum SExprVal {
+    Null,
+    Bool(bool),
+    Uint32(u32),
+    Float32(f32),
+    Str(String),
+}
+
+impl SExprVal {
+    fn is_null(&self) -> bool {
+        match self {
+            Self::Null => true,
+            _ => false,
+        }
+    }
+
+    fn is_bool(&self) -> bool {
+        match self {
+            Self::Bool(v) => true,
+            _ => false,
+        }
+    }
+
+    fn is_u32(&self) -> bool {
+        match self {
+            Self::Uint32(v) => true,
+            _ => false,
+        }
+    }
+
+    fn is_f32(&self) -> bool {
+        match self {
+            Self::Float32(v) => true,
+            _ => false,
+        }
+    }
+
+    fn is_str(&self) -> bool {
+        match self {
+            Self::Str(v) => true,
+            _ => false,
+        }
+    }
+
+    fn as_bool(&self) -> Result<bool> {
+        match self {
+            Self::Bool(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+
+    pub fn as_u32(&self) -> Result<u32> {
+        match self {
+            Self::Uint32(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+
+    pub fn as_f32(&self) -> Result<f32> {
+        match self {
+            Self::Float32(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+
+    fn as_str(&self) -> Result<String> {
+        match self {
+            Self::Str(v) => Ok(v.clone()),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+
+    pub fn coerce_f32(&self) -> Result<f32> {
+        match self {
+            Self::Uint32(v) => Ok(*v as f32),
+            Self::Float32(v) => Ok(*v),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct SExprMachine<'a> {
+    pub globals: Vec<(String, SExprVal)>,
+    pub stmts: &'a SExprCode,
+}
+
+// Each item is a statement
+pub type SExprCode = Vec<Op>;
+
+#[derive(Debug, PartialEq)]
+pub enum Op {
+    Null,
+    Add((Box<Op>, Box<Op>)),
+    Sub((Box<Op>, Box<Op>)),
+    Mul((Box<Op>, Box<Op>)),
+    Div((Box<Op>, Box<Op>)),
+    ConstBool(bool),
+    ConstUint32(u32),
+    ConstFloat32(f32),
+    ConstStr(String),
+    LoadVar(String),
+    //StoreVar((String, Box<Op>)),
+    Min((Box<Op>, Box<Op>)),
+    Max((Box<Op>, Box<Op>)),
+    IsEqual((Box<Op>, Box<Op>)),
+    LessThan((Box<Op>, Box<Op>)),
+    Float32ToUint32(Box<Op>)
+}
+
+impl<'a> SExprMachine<'a> {
+    pub fn call(&self) -> Result<SExprVal> {
+        if self.stmts.is_empty() {
+            return Ok(SExprVal::Null)
+        }
+        for i in 0..(self.stmts.len() - 1) {
+            self.eval(&self.stmts[i])?;
+        }
+        self.eval(self.stmts.last().unwrap())
+    }
+
+    fn eval(&self, op: &Op) -> Result<SExprVal> {
+        match op {
+            Op::Null => Ok(SExprVal::Null),
+            Op::Add((lhs, rhs)) => self.add(lhs, rhs),
+            Op::Sub((lhs, rhs)) => self.sub(lhs, rhs),
+            Op::Mul((lhs, rhs)) => self.mul(lhs, rhs),
+            Op::Div((lhs, rhs)) => self.div(lhs, rhs),
+            Op::ConstBool(val) => Ok(SExprVal::Bool(*val)),
+            Op::ConstUint32(val) => Ok(SExprVal::Uint32(*val)),
+            Op::ConstFloat32(val) => Ok(SExprVal::Float32(*val)),
+            Op::ConstStr(val) => Ok(SExprVal::Str(val.clone())),
+            Op::LoadVar(var) => self.load_var(var),
+            //Op::StoreVar((var, val)) => self.store_var(var, val),
+            Op::Min((lhs, rhs)) => self.min(lhs, rhs),
+            Op::Max((lhs, rhs)) => self.max(lhs, rhs),
+            Op::IsEqual((lhs, rhs)) => self.is_equal(lhs, rhs),
+            Op::LessThan((lhs, rhs)) => self.less_than(lhs, rhs),
+            Op::Float32ToUint32(val) => self.float32_to_uint32(val),
+        }
+    }
+
+    fn add(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            return Ok(SExprVal::Uint32(lhs.as_u32().unwrap() + rhs.as_u32().unwrap()))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+
+        Ok(SExprVal::Float32(lhs + rhs))
+    }
+    fn sub(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            return Ok(SExprVal::Uint32(lhs.as_u32().unwrap() - rhs.as_u32().unwrap()))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+
+        Ok(SExprVal::Float32(lhs - rhs))
+    }
+    fn mul(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            return Ok(SExprVal::Uint32(lhs.as_u32().unwrap() * rhs.as_u32().unwrap()))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+
+        Ok(SExprVal::Float32(lhs * rhs))
+    }
+    fn div(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        // Always coerce
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+
+        Ok(SExprVal::Float32(lhs / rhs))
+    }
+    fn load_var(&self, var: &str) -> Result<SExprVal> {
+        for (name, val) in &self.globals {
+            if name == var {
+                return Ok(val.clone())
+            }
+        }
+        Err(Error::SExprGlobalNotFound)
+    }
+    //fn store_var(&mut self, var, val) {
+    //}
+    fn min(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            let lhs = lhs.as_u32().unwrap();
+            let rhs = rhs.as_u32().unwrap();
+            let min = if lhs < rhs { lhs } else { rhs };
+            return Ok(SExprVal::Uint32(min))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+        let min = if lhs < rhs { lhs } else { rhs };
+
+        Ok(SExprVal::Float32(min))
+    }
+    fn max(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            let lhs = lhs.as_u32().unwrap();
+            let rhs = rhs.as_u32().unwrap();
+            let max = if lhs > rhs { lhs } else { rhs };
+            return Ok(SExprVal::Uint32(max))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+        let max = if lhs > rhs { lhs } else { rhs };
+
+        Ok(SExprVal::Float32(max))
+    }
+    fn is_equal(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            return Ok(SExprVal::Bool(lhs.as_u32().unwrap() == rhs.as_u32().unwrap()))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+        let is_equal = (lhs - rhs).abs() < f32::EPSILON;
+
+        Ok(SExprVal::Bool(is_equal))
+    }
+    fn less_than(&self, lhs: &Op, rhs: &Op) -> Result<SExprVal> {
+        let lhs = self.eval(lhs)?;
+        let rhs = self.eval(rhs)?;
+
+        if lhs.is_u32() && rhs.is_u32() {
+            return Ok(SExprVal::Bool(lhs.as_u32().unwrap() < rhs.as_u32().unwrap()))
+        }
+
+        let lhs = lhs.coerce_f32()?;
+        let rhs = rhs.coerce_f32()?;
+
+        Ok(SExprVal::Bool(lhs < rhs))
+    }
+    fn float32_to_uint32(&self, val: &Op) -> Result<SExprVal> {
+        let val = self.eval(val)?;
+        if val.is_u32() {
+            return Ok(SExprVal::Uint32(val.as_u32()?))
+        }
+        Ok(SExprVal::Uint32(val.as_f32()? as u32))
+    }
+}
+
+impl Encodable for Op {
+    fn encode<S: Write>(&self, s: &mut S) -> std::result::Result<usize, std::io::Error> {
+        let mut len = 0;
+        match self {
+            Self::Null => {
+                len += 0u8.encode(s)?;
+            }
+            Self::Add((lhs, rhs)) => {
+                len += 1u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::Sub((lhs, rhs)) => {
+                len += 2u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::Mul((lhs, rhs)) => {
+                len += 3u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::Div((lhs, rhs)) => {
+                len += 4u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::ConstBool(val) => {
+                len += 5u8.encode(s)?;
+                len += val.encode(s)?;
+            }
+            Self::ConstUint32(val) => {
+                len += 6u8.encode(s)?;
+                len += val.encode(s)?;
+            }
+            Self::ConstFloat32(val) => {
+                len += 7u8.encode(s)?;
+                len += val.encode(s)?;
+            }
+            Self::ConstStr(val) => {
+                len += 8u8.encode(s)?;
+                len += val.encode(s)?;
+            }
+            Self::LoadVar(var) => {
+                len += 9u8.encode(s)?;
+                len += var.encode(s)?;
+            }
+            // StoreVar
+            Self::Min((lhs, rhs)) => {
+                len += 11u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::Max((lhs, rhs)) => {
+                len += 12u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::IsEqual((lhs, rhs)) => {
+                len += 13u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::LessThan((lhs, rhs)) => {
+                len += 14u8.encode(s)?;
+                len += lhs.encode(s)?;
+                len += rhs.encode(s)?;
+            }
+            Self::Float32ToUint32(val) => {
+                len += 15u8.encode(s)?;
+                len += val.encode(s)?;
+            }
+        }
+        Ok(len)
+    }
+}
+
+impl Decodable for Op {
+    fn decode<D: Read>(d: &mut D) -> std::result::Result<Self, std::io::Error> {
+        let op_type = d.read_u8()?;
+        let self_ = match op_type {
+            0 => Self::Null,
+            1 => Self::Add((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            2 => Self::Sub((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            3 => Self::Mul((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            4 => Self::Div((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            5 => Self::ConstBool(d.read_bool()?),
+            6 => Self::ConstUint32(d.read_u32()?),
+            7 => Self::ConstFloat32(d.read_f32()?),
+            8 => Self::ConstStr(String::decode(d)?),
+            9 => Self::LoadVar(String::decode(d)?),
+            // StoreVar
+            11 => Self::Min((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            12 => Self::Max((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            13 => Self::IsEqual((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            14 => Self::LessThan((Box::new(Self::decode(d)?), Box::new(Self::decode(d)?))),
+            15 => Self::Float32ToUint32(Box::new(Self::decode(d)?)),
+            _ => return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid Op type")),
+        };
+        Ok(self_)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use darkfi_serial::{serialize, deserialize};
+
+    #[test]
+    fn seval() {
+        let machine = SExprMachine {
+            globals: vec![
+                ("sw".to_string(), SExprVal::Uint32(110u32)),
+                ("sh".to_string(), SExprVal::Uint32(4u32)),
+            ],
+            stmts: &vec![Op::Add((
+                Box::new(Op::ConstUint32(5)),
+                Box::new(Op::Div((
+                    Box::new(Op::LoadVar("sw".to_string())),
+                    Box::new(Op::ConstUint32(2)),
+                ))),
+            ))],
+        };
+        assert_eq!(machine.call().unwrap(), SExprVal::Float32(60.));
+    }
+
+    #[test]
+    fn encdec_code() {
+        let code = Op::Add((
+            Box::new(Op::ConstUint32(5)),
+            Box::new(Op::Div((
+                Box::new(Op::LoadVar("sw".to_string())),
+                Box::new(Op::ConstUint32(2)),
+            ))),
+        ));
+
+        let code_s = serialize(&code);
+        let code2 = deserialize::<Op>(&code_s).unwrap();
+        assert_eq!(code, code2);
+    }
+}

+ 72 - 107
bin/darkwallet/src/gfx.rs

@@ -1,20 +1,26 @@
-use darkfi_serial::{Decodable, Encodable, SerialEncodable, SerialDecodable};
+use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
 use fontdue::{
     layout::{CoordinateSystem, GlyphPosition, Layout, LayoutSettings, TextStyle},
     Font, FontSettings,
 };
 use miniquad::*;
+use pyo3::{
+    prelude::*,
+    py_run,
+    types::{IntoPyDict, PyDict},
+    PyClass,
+};
 use std::{
     array::IntoIter,
     fmt,
     io::Cursor,
-    sync::{mpsc, MutexGuard, Arc},
+    sync::{mpsc, Arc, MutexGuard},
     time::{Duration, Instant},
 };
-use pyo3::{prelude::*, types::{PyDict, IntoPyDict}, PyClass, py_run};
 
 use crate::{
     error::{Error, Result},
+    expr::{SExprVal, SExprMachine},
     prop::{Property, PropertySubType, PropertyType},
     scene::{MethodResponseFn, SceneGraph, SceneGraphPtr, SceneNode, SceneNodeId, SceneNodeType},
     shader,
@@ -251,8 +257,16 @@ impl Stage {
             font
         };
 
-        let mut stage =
-            Stage { ctx, pipeline, scene_graph, textures, meshes: ResourceManager::new(), font, method_recvr, method_sender };
+        let mut stage = Stage {
+            ctx,
+            pipeline,
+            scene_graph,
+            textures,
+            meshes: ResourceManager::new(),
+            font,
+            method_recvr,
+            method_sender,
+        };
         stage.setup_scene_graph_window();
         stage
     }
@@ -579,20 +593,14 @@ impl Stage {
             BufferSource::slice(&faces),
         );
 
-        let mesh = Mesh {
-            verts,
-            faces,
-            vertex_buffer,
-            index_buffer
-        };
+        let mesh = Mesh { verts, faces, vertex_buffer, index_buffer };
 
         let mesh_id = self.meshes.alloc(mesh);
 
         let mut scene_graph = self.scene_graph.lock().unwrap();
         let node = scene_graph.add_node(node_name, SceneNodeType::RenderMesh);
 
-        let mut prop =
-            Property::new("mesh_id", PropertyType::Uint32, PropertySubType::ResourceId);
+        let mut prop = Property::new("mesh_id", PropertyType::Uint32, PropertySubType::ResourceId);
         prop.set_u32(0, mesh_id).unwrap();
         node.add_property(prop)?;
 
@@ -634,7 +642,8 @@ struct RenderContext<'a> {
 
 impl<'a> RenderContext<'a> {
     fn render_window(&mut self) {
-        for layer in self.scene_graph
+        for layer in self
+            .scene_graph
             .lookup_node("/window")
             .expect("no window attached!")
             .get_children(&[SceneNodeType::RenderLayer])
@@ -650,55 +659,35 @@ impl<'a> RenderContext<'a> {
         if prop.array_len != 4 {
             return Err(Error::PropertyWrongLen)
         }
-        match prop.typ {
-            PropertyType::Uint32 => {
-                if prop.subtype != PropertySubType::Null {
-                    return Err(Error::PropertyWrongSubType)
-                }
-                Ok(Rectangle {
-                    x: prop.get_u32(0)? as i32,
-                    y: prop.get_u32(1)? as i32,
-                    w: prop.get_u32(2)? as i32,
-                    h: prop.get_u32(3)? as i32,
-                })
-            }
-            PropertyType::Str => {
-                if prop.subtype != PropertySubType::PyExpr {
-                    return Err(Error::PropertyWrongSubType)
-                }
 
+        let mut rect = [0; 4];
+        for i in 0..4 {
+            if prop.is_expr(i)? {
                 let (screen_width, screen_height) = window::screen_size();
 
-                let locals = Python::with_gil(|py| {
-                    let locals = PyDict::new_bound(py);
-                    locals.set_item("sw", screen_width).expect("set item sw");
-                    locals.set_item("sh", screen_height).expect("set item sh");
-                    locals.unbind()
-                });
-
-                let mut rect = [0; 4];
-                for i in 0..4 {
-                    let code = prop.get_str(i)?;
-                    match eval_py_str(&code, locals.clone()) {
-                        Ok(v) => rect[i] = v as i32,
-                        Err(err) => {
-                            error!("layer '{}': python running `{}` encountered err: {}", layer.name, code, err);
-                            return Err(Error::PyEvalErr)
-                        }
-                    }
-                }
+                let expr = prop.get_expr(i).unwrap();
 
-                Ok(Rectangle::from_array(rect))
-            }
-            _ => {
-                Err(Error::PropertyWrongType)
+                let machine = SExprMachine {
+                    globals: vec![
+                        ("sw".to_string(), SExprVal::Float32(screen_width)),
+                        ("sh".to_string(), SExprVal::Float32(screen_height)),
+                    ],
+                    stmts: &expr
+                };
+
+                rect[i] = machine.call()?.as_u32()? as i32;
+            } else {
+                rect[i] = prop.get_u32(i)? as i32;
             }
         }
+        Ok(Rectangle::from_array(rect))
     }
 
-    fn render_layer(&mut self, layer_id: SceneNodeId,
-                    // parent rect
-                    ) -> Result<()> {
+    fn render_layer(
+        &mut self,
+        layer_id: SceneNodeId,
+        // parent rect
+    ) -> Result<()> {
         let layer = self.scene_graph.get_node(layer_id).unwrap();
 
         if !layer.get_property_bool("is_visible")? {
@@ -734,8 +723,8 @@ impl<'a> RenderContext<'a> {
                     if let Err(err) = self.render_mesh(child.id, &rect) {
                         error!("error rendering mesh '{}': {}", child.name, err);
                     }
-                },
-                _ => panic!("render_layer(): unknown type")
+                }
+                _ => panic!("render_layer(): unknown type"),
             }
         }
 
@@ -749,48 +738,26 @@ impl<'a> RenderContext<'a> {
         if prop.array_len != 4 {
             return Err(Error::PropertyWrongLen)
         }
-        match prop.typ {
-            PropertyType::Float32 => {
-                if prop.subtype != PropertySubType::Null {
-                    return Err(Error::PropertyWrongSubType)
-                }
-                Ok(Rectangle {
-                    x: prop.get_f32(0)?,
-                    y: prop.get_f32(1)?,
-                    w: prop.get_f32(2)?,
-                    h: prop.get_f32(3)?,
-                })
-            }
-            PropertyType::Str => {
-                if prop.subtype != PropertySubType::PyExpr {
-                    return Err(Error::PropertyWrongSubType)
-                }
 
-                let locals = Python::with_gil(|py| {
-                    let locals = PyDict::new_bound(py);
-                    locals.set_item("lw", layer_rect.w as f32).expect("set item lw");
-                    locals.set_item("lh", layer_rect.h as f32).expect("set item lh");
-                    locals.unbind()
-                });
-
-                let mut rect = [0.; 4];
-                for i in 0..4 {
-                    let code = prop.get_str(i)?;
-                    match eval_py_str(&code, locals.clone()) {
-                        Ok(v) => rect[i] = v,
-                        Err(err) => {
-                            error!("mesh '{}': python running `{}` encountered err: {}", mesh.name, code, err);
-                            return Err(Error::PyEvalErr)
-                        }
-                    }
-                }
+        let mut rect = [0.; 4];
+        for i in 0..4 {
+            if prop.is_expr(i)? {
+                let expr = prop.get_expr(i).unwrap();
+
+                let machine = SExprMachine {
+                    globals: vec![
+                        ("lw".to_string(), SExprVal::Uint32(layer_rect.w as u32)),
+                        ("lh".to_string(), SExprVal::Uint32(layer_rect.h as u32)),
+                    ],
+                    stmts: &expr
+                };
 
-                Ok(Rectangle::from_array(rect))
-            }
-            _ => {
-                Err(Error::PropertyWrongType)
+                rect[i] = machine.call()?.coerce_f32()?;
+            } else {
+                rect[i] = prop.get_f32(i)?;
             }
         }
+        Ok(Rectangle::from_array(rect))
     }
 
     fn render_mesh(&mut self, mesh_id: SceneNodeId, layer_rect: &Rectangle<i32>) -> Result<()> {
@@ -814,20 +781,14 @@ impl<'a> RenderContext<'a> {
             )
         };
 
-        let index_buffer = self.ctx.new_buffer(
-            BufferType::IndexBuffer,
-            BufferUsage::Immutable,
-            bufsrc,
-        );
+        let index_buffer =
+            self.ctx.new_buffer(BufferType::IndexBuffer, BufferUsage::Immutable, bufsrc);
 
         // temp
         let texture = self.textures.get(Stage::WHITE_TEXTURE_ID).unwrap();
 
-        let bindings = Bindings {
-            vertex_buffers: vec![vertex_buffer],
-            index_buffer,
-            images: vec![*texture],
-        };
+        let bindings =
+            Bindings { vertex_buffers: vec![vertex_buffer], index_buffer, images: vec![*texture] };
 
         self.ctx.apply_bindings(&bindings);
 
@@ -871,10 +832,14 @@ fn eval_py_str<'py>(code: &str, locals: Py<PyDict>) -> PyResult<f32> {
         // Can also use restrictedpython lib to eval the code.
         // Also PyPy sandboxing
         // and starlark / starlark-rust
-        py_run!(py, null, r#"
+        py_run!(
+            py,
+            null,
+            r#"
 __builtins__.__dict__['__import__'] = None
 __builtins__.__dict__['open'] = None
-        "#);
+        "#
+        );
 
         let locals = locals.bind(py);
         let result: f32 = py.eval_bound(code, None, Some(locals))?.extract()?;

+ 2 - 0
bin/darkwallet/src/main.rs

@@ -7,6 +7,8 @@ use std::{
 
 mod error;
 
+mod expr;
+
 mod gfx;
 use gfx::init_gui;
 

+ 18 - 4
bin/darkwallet/src/net.rs

@@ -7,6 +7,7 @@ use std::{
 
 use crate::{
     error::{Error, Result},
+    expr::SExprCode,
     prop::{Property, PropertySubType, PropertyType, PropertyValue},
     scene::{SceneGraphPtr, SceneNodeId, SceneNodeType, Slot, SlotId},
 };
@@ -174,6 +175,7 @@ impl ZeroMQAdapter {
                     prop.ui_name.encode(&mut reply).unwrap();
                     prop.desc.encode(&mut reply).unwrap();
                     prop.is_null_allowed.encode(&mut reply).unwrap();
+                    prop.is_expr_allowed.encode(&mut reply).unwrap();
                     (prop.array_len as u32).encode(&mut reply).unwrap();
                     prop.min_val.encode(&mut reply).unwrap();
                     prop.max_val.encode(&mut reply).unwrap();
@@ -276,6 +278,7 @@ impl ZeroMQAdapter {
                 let prop_ui_name = String::decode(&mut cur).unwrap();
                 let prop_desc = String::decode(&mut cur).unwrap();
                 let prop_is_null_allowed = bool::decode(&mut cur).unwrap();
+                let prop_is_expr_allowed = bool::decode(&mut cur).unwrap();
 
                 match prop_type {
                     PropertyType::Uint32 => {
@@ -323,7 +326,7 @@ impl ZeroMQAdapter {
                         if max_is_some {
                             return Err(Error::PropertyWrongType)
                         }
-                    },
+                    }
                 }
 
                 let prop_enum_items = Vec::<String>::decode(&mut cur).unwrap();
@@ -332,6 +335,7 @@ impl ZeroMQAdapter {
 
                 prop.set_ui_text(prop_ui_name, prop_desc);
                 prop.is_null_allowed = prop_is_null_allowed;
+                prop.is_expr_allowed = prop_is_expr_allowed;
                 if !prop_enum_items.is_empty() {
                     prop.set_enum_items(prop_enum_items)?;
                 }
@@ -353,12 +357,13 @@ impl ZeroMQAdapter {
                 let node_id = SceneNodeId::decode(&mut cur).unwrap();
                 let prop_name = String::decode(&mut cur).unwrap();
                 let prop_i = u32::decode(&mut cur).unwrap() as usize;
-                debug!(target: "req", "{:?}({}, {})", cmd, node_id, prop_name);
+                let prop_type = PropertyType::decode(&mut cur).unwrap();
+                debug!(target: "req", "{:?}({}, {}, {}, {:?})", cmd, node_id, prop_name, prop_i, prop_type);
 
                 let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?;
                 let prop = node.get_property(&prop_name).ok_or(Error::PropertyNotFound)?;
 
-                match prop.typ {
+                match prop_type {
                     PropertyType::Null => {}
                     PropertyType::Bool => {
                         let val = bool::decode(&mut cur).unwrap();
@@ -372,10 +377,14 @@ impl ZeroMQAdapter {
                         let val = f32::decode(&mut cur).unwrap();
                         prop.set_f32(prop_i, val)?;
                     }
-                    PropertyType::Str | PropertyType::Enum => {
+                    PropertyType::Str => {
                         let val = String::decode(&mut cur).unwrap();
                         prop.set_str(prop_i, val)?;
                     }
+                    PropertyType::Enum => {
+                        let val = String::decode(&mut cur).unwrap();
+                        prop.set_enum(prop_i, val)?;
+                    }
                     PropertyType::Buffer => {
                         let val = Vec::<u8>::decode(&mut cur).unwrap();
                         prop.set_buf(prop_i, val)?;
@@ -384,6 +393,11 @@ impl ZeroMQAdapter {
                         let val = SceneNodeId::decode(&mut cur).unwrap();
                         prop.set_node_id(prop_i, val)?;
                     }
+                    PropertyType::SExpr => {
+                        let val = SExprCode::decode(&mut cur).unwrap();
+                        debug!(target: "req", "  received code {:?}", val);
+                        prop.set_expr(prop_i, val)?;
+                    }
                 }
             }
             Command::GetSignals => {

+ 51 - 3
bin/darkwallet/src/prop.rs

@@ -11,7 +11,7 @@ use std::{
     },
 };
 
-use crate::scene::SceneNodeId;
+use crate::{scene::SceneNodeId, expr::SExprCode};
 
 type Buffer = Arc<Vec<u8>>;
 
@@ -26,6 +26,7 @@ pub enum PropertyType {
     Enum = 5,
     Buffer = 6,
     SceneNodeId = 7,
+    SExpr = 8,
 }
 
 impl PropertyType {
@@ -39,6 +40,7 @@ impl PropertyType {
             Self::Enum => PropertyValue::Enum(String::new()),
             Self::Buffer => PropertyValue::Buffer(Arc::new(vec![])),
             Self::SceneNodeId => PropertyValue::SceneNodeId(0),
+            Self::SExpr => PropertyValue::SExpr(Arc::new(vec![])),
         }
     }
 }
@@ -51,7 +53,6 @@ pub enum PropertySubType {
     // Size of something in pixels
     Pixel = 2,
     ResourceId = 3,
-    PyExpr = 4,
 }
 
 #[derive(Debug, Clone)]
@@ -65,6 +66,7 @@ pub enum PropertyValue {
     Enum(String),
     Buffer(Arc<Vec<u8>>),
     SceneNodeId(SceneNodeId),
+    SExpr(Arc<SExprCode>),
 }
 
 impl PropertyValue {
@@ -79,6 +81,7 @@ impl PropertyValue {
             Self::Enum(_) => PropertyType::Enum,
             Self::Buffer(_) => PropertyType::Buffer,
             Self::SceneNodeId(_) => PropertyType::SceneNodeId,
+            Self::SExpr(_) => PropertyType::SExpr,
         }
     }
 
@@ -96,6 +99,13 @@ impl PropertyValue {
         }
     }
 
+    pub fn is_expr(&self) -> bool {
+        match self {
+            Self::SExpr(_) => true,
+            _ => false,
+        }
+    }
+
     fn as_bool(&self) -> Result<bool> {
         match self {
             Self::Bool(v) => Ok(*v),
@@ -138,10 +148,16 @@ impl PropertyValue {
             _ => Err(Error::PropertyWrongType),
         }
     }
+    fn as_sexpr(&self) -> Result<Arc<SExprCode>> {
+        match self {
+            Self::SExpr(v) => Ok(v.clone()),
+            _ => Err(Error::PropertyWrongType),
+        }
+    }
 }
 
 impl Encodable for PropertyValue {
-    fn encode<S: Write>(&self, mut s: S) -> std::result::Result<usize, std::io::Error> {
+    fn encode<S: Write>(&self, s: &mut S) -> std::result::Result<usize, std::io::Error> {
         match self {
             Self::Unset | Self::Null | Self::Buffer(_) => {
                 // do nothing
@@ -153,6 +169,7 @@ impl Encodable for PropertyValue {
             Self::Str(v) => v.encode(s),
             Self::Enum(v) => v.encode(s),
             Self::SceneNodeId(v) => v.encode(s),
+            Self::SExpr(v) => v.encode(s),
         }
     }
 }
@@ -168,6 +185,8 @@ pub struct Property {
     pub desc: String,
 
     pub is_null_allowed: bool,
+    pub is_expr_allowed: bool,
+
     // Use 0 for unbounded length
     pub array_len: usize,
     pub min_val: Option<PropertyValue>,
@@ -191,6 +210,8 @@ impl Property {
             desc: String::new(),
 
             is_null_allowed: false,
+            is_expr_allowed: false,
+
             array_len: 1,
             min_val: None,
             max_val: None,
@@ -233,6 +254,10 @@ impl Property {
         self.is_null_allowed = true;
     }
 
+    pub fn allow_exprs(&mut self) {
+        self.is_expr_allowed = true;
+    }
+
     fn check_defaults_len(&self, defaults_len: usize) -> Result<()> {
         if !self.is_bounded() || defaults_len != self.array_len {
             return Err(Error::PropertyWrongLen)
@@ -350,6 +375,17 @@ impl Property {
     pub fn set_node_id(&self, i: usize, val: SceneNodeId) -> Result<()> {
         self.set_raw_value(i, PropertyValue::SceneNodeId(val))
     }
+    pub fn set_expr(&self, i: usize, val: SExprCode) -> Result<()> {
+        if !self.is_expr_allowed {
+            return Err(Error::PropertySExprNotAllowed)
+        }
+        let vals = &mut self.vals.lock().unwrap();
+        if i >= vals.len() {
+            return Err(Error::PropertyWrongIndex)
+        }
+        vals[i] = PropertyValue::SExpr(Arc::new(val));
+        Ok(())
+    }
 
     // Push
 
@@ -419,6 +455,14 @@ impl Property {
         Ok(val.is_unset())
     }
 
+    pub fn is_expr(&self, i: usize) -> Result<bool> {
+        if !self.is_expr_allowed {
+            return Ok(false)
+        }
+        let val = self.get_raw_value(i)?;
+        Ok(val.is_expr())
+    }
+
     pub fn get_raw_value(&self, i: usize) -> Result<PropertyValue> {
         let vals = &self.vals.lock().unwrap();
         if self.is_bounded() {
@@ -508,6 +552,10 @@ impl Property {
         }
         Ok(Some(val.as_node_id()?))
     }
+
+    pub fn get_expr(&self, i: usize) -> Result<Arc<SExprCode>> {
+        self.get_value(i)?.as_sexpr()
+    }
 }
 
 #[cfg(test)]

+ 1 - 4
bin/darkwallet/src/scene.rs

@@ -390,10 +390,7 @@ impl SceneNode {
         self.children.swap_remove(child_idx);
     }
 
-    pub fn get_children(
-        &self,
-        allowed_types: &[SceneNodeType],
-    ) -> Vec<SceneNodeInfo> {
+    pub fn get_children(&self, allowed_types: &[SceneNodeType]) -> Vec<SceneNodeInfo> {
         self.children
             .iter()
             .cloned()