## # Node class definition. The class methods simply wrap the # low level exention functions which work on a "ghost" handle to a # SGPropertyNode object stored in the _g field. # # Not all of the features of SGPropertyNode are supported. There is # no support for ties, obviously, as that wouldn't make much sense # from a Nasal context. The various get/set methods work only on the # local node, there is no equivalent of the "relative path" variants # available in C++; just use node.getNode(path).whatever() instead. # There is no support for the "listener" interface yet. The aliasing # feature isn't exposed, except that you can get an "ALIAS" return # from getType to detect them (to avoid cycles while walking the # tree). # Node = { getType : func { wrap(_getType(me._g, arg)) }, getAttribute : func { wrap(_getAttribute(me._g, arg)) }, setAttribute : func { wrap(_setAttribute(me._g, arg)) }, getName : func { wrap(_getName(me._g, arg)) }, getIndex : func { wrap(_getIndex(me._g, arg)) }, getValue : func { wrap(_getValue(me._g, arg)) }, setValue : func { wrap(_setValue(me._g, arg)) }, setIntValue : func { wrap(_setIntValue(me._g, arg)) }, setBoolValue : func { wrap(_setBoolValue(me._g, arg)) }, setDoubleValue : func { wrap(_setDoubleValue(me._g, arg)) }, getParent : func { wrap(_getParent(me._g, arg)) }, getChild : func { wrap(_getChild(me._g, arg)) }, getChildren : func { wrap(_getChildren(me._g, arg)) }, removeChild : func { wrap(_removeChild(me._g, arg)) }, removeChildren : func { wrap(_removeChildren(me._g, arg)) }, getNode : func { wrap(_getNode(me._g, arg)) }, getPath : func { name = me.getName(); if(me.getIndex() != 0) { name = name ~ "[" ~ me.getIndex() ~ "]"; } if(me.getParent() != nil) { name = me.getParent().getPath() ~ "/" ~ name; } return name; }, getBoolValue : func { val = me.getValue(); if(me.getType() == "STRING" and val == "false") { 0 } elsif (val == nil) { 0 } else { val != 0 } }, }; ## # Static constructor for a Node object. Accepts a Nasal hash # expression to initialize the object a-la setValues(). # Node.new = func { result = wrapNode(_new()); if(size(arg) > 0 and typeof(arg[0]) == "hash") { result.setValues(arg[0]); } return result; } ## # Useful utility. Sets a whole property tree from a Nasal hash # object, such that scalars become leafs in the property tree, hashes # become named subnodes, and vectors become indexed subnodes. This # works recursively, so you can define whole property trees with # syntax like: # # dialog = { # name : "exit", width : 180, height : 100, modal : 0, # text : { x : 10, y : 70, label : "Hello World!" } }; # Node.setValues = func { foreach(k; keys(arg[0])) { me._setChildren(k, arg[0][k]); } } ## # Private function to do the work of setValues(). # The first argument is a child name, the second a nasal scalar, # vector, or hash. # Node._setChildren = func { name = arg[0]; val = arg[1]; subnode = me.getNode(name, 1); if(typeof(val) == "scalar") { subnode.setValue(val); } elsif(typeof(val) == "hash") { subnode.setValues(val); } elsif(typeof(val) == "vector") { for(i=0; i<size(val); i+=1) { iname = name ~ "[" ~ i ~ "]"; me._setChildren(iname, val[i]); } } } ## # Useful debugging utility. Recursively dumps the full state of a # Node object to the console. Try binding "props.dump(props.globals)" # to a key for a fun hack. # var dump = func { if(size(arg) == 1) { prefix = ""; node = arg[0]; } else { prefix = arg[0]; node = arg[1]; } index = node.getIndex(); type = node.getType(); name = node.getName(); val = node.getValue(); if(val == nil) { val = "nil"; } name = prefix ~ name; if(index > 0) { name = name ~ "[" ~ index ~ "]"; } print(name, " {", type, "} = ", val); # Don't recurse into aliases, lest we get stuck in a loop if(type != "ALIAS") { children = node.getChildren(); foreach(c; children) { dump(name ~ "/", c); } } } ## # Recursively copy property branch from source Node to # destination Node. Doesn't copy aliases. Copies attributes # if optional third argument is set and non-zero. # var copy = func(src, dest, attr = 0) { foreach(c; src.getChildren()) { name = c.getName() ~ "[" ~ c.getIndex() ~ "]"; copy(src.getNode(name), dest.getNode(name, 1), attr); } var type = src.getType(); var val = src.getValue(); if(type == "ALIAS" or type == "NONE") { return; } elsif(type == "BOOL") { dest.setBoolValue(val); } elsif(type == "INT") { dest.setIntValue(val); } elsif(type == "DOUBLE") { dest.setDoubleValue(val); } else { dest.setValue(val); } if(attr) dest.setAttribute(src.getAttribute()); } ## # Utility. Turns any ghosts it finds (either solo, or in an # array) into Node objects. # var wrap = func { argtype = typeof(arg[0]); if(argtype == "ghost") { return wrapNode(arg[0]); } elsif(argtype == "vector") { v = arg[0]; n = size(v); for(i=0; i<n; i+=1) { v[i] = wrapNode(v[i]); } return v; } return arg[0]; } ## # Utility. Returns a new object with its superclass/parent set to the # Node object and its _g (ghost) field set to the specified object. # Nasal's literal syntax can be pleasingly terse. I like that. :) # var wrapNode = func { { parents : [Node], _g : arg[0] } } ## # Global property tree. Set once at initialization. Is that OK? # Does anything ever call globals.set_props() from C++? May need to # turn this into a function if so. # var globals = wrapNode(_globals()); ## # Sets all indexed property children to a single value. arg[0] # specifies a property name (e.g. /controls/engines/engine), arg[1] a # path under each node of that name to set (e.g. "throttle"), arg[2] # is the value. # var setAll = func { node = props.globals.getNode(arg[0]); if(node == nil) { return; } name = node.getName(); node = node.getParent(); if(node == nil) { return; } children = node.getChildren(); foreach(c; children) { if(c.getName() == name) { c.getNode(arg[1], 1).setValue(arg[2]); }} } ## # Evaluates a <condition> property branch according to the rules # set out in $FG_ROOT/Docs/README.conditions. Undefined conditions # and a nil argument are "true". The function dumps the condition # branch and returns nil on error. # var condition = func(p) { if(p == nil) { return 1; } if(!isa(p, props.Node)) { p = props.globals.getNode(p); } return _cond_and(p) } var _cond_and = func(p) { foreach(var c; p.getChildren()) { if(!_cond(c)) { return 0; } } return 1; } var _cond_or = func(p) { foreach(var c; p.getChildren()) { if(_cond(c)) { return 1; } } return 0; } var _cond = func(p) { var n = p.getName(); if(n == "or") { return _cond_or(p); } if(n == "and") { return _cond_and(p); } if(n == "not") { return !_cond_and(p); } if(n == "equals") { return _cond_cmp(p, 0); } if(n == "not-equals") { return !_cond_cmp(p, 0); } if(n == "less-than") { return _cond_cmp(p, -1); } if(n == "greater-than") { return _cond_cmp(p, 1); } if(n == "less-than-equals") { return !_cond_cmp(p, 1); } if(n == "greater-than-equals") { return !_cond_cmp(p, -1); } if(n == "property") { return !!getprop(p.getValue()); } printlog("alert", "condition: invalid operator ", n); dump(p); return nil; } var _cond_cmp = func(p, op) { var left = p.getChild("property", 0, 0); if(left != nil) { left = getprop(left.getValue()); } else { printlog("alert", "condition: no left value"); dump(p); return nil; } var right = p.getChild("property", 1, 0); if(right != nil) { right = getprop(right.getValue()); } else { right = p.getChild("value", 0, 0); if(right != nil) { right = right.getValue(); } else { printlog("alert", "condition: no right value"); dump(p); return nil; } } if(left == nil or right == nil) { printlog("alert", "condition: comparing with nil"); dump(p); return nil; } if(op < 0) { return left < right; } if(op > 0) { return left > right; } return left == right; }