Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / props.nas
1 ##
2 # Node class definition.  The class methods simply wrap the
3 # low level extension functions which work on a "ghost" handle to a
4 # SGPropertyNode object stored in the _g field.
5 #
6 # Not all of the features of SGPropertyNode are supported.  There is
7 # no support for ties, obviously, as that wouldn't make much sense
8 # from a Nasal context.  The various get/set methods work only on the
9 # local node, there is no equivalent of the "relative path" variants
10 # available in C++; just use node.getNode(path).whatever() instead.
11 #
12 var Node = {
13     getNode          : func wrap(_getNode(me._g, arg)),
14     getParent        : func wrap(_getParent(me._g, arg)),
15     getChild         : func wrap(_getChild(me._g, arg)),
16     getChildren      : func wrap(_getChildren(me._g, arg)),
17     addChild         : func wrap(_addChild(me._g, arg)),
18     addChildren      : func wrap(_addChildren(me._g, arg)),
19     removeChild      : func wrap(_removeChild(me._g, arg)),
20     removeChildren   : func wrap(_removeChildren(me._g, arg)),
21     removeAllChildren: func wrap(_removeAllChildren(me._g, arg)),
22     getAliasTarget   : func wrap(_getAliasTarget(me._g, arg)),
23
24     getName        : func _getName(me._g, arg),
25     getIndex       : func _getIndex(me._g, arg),
26     getType        : func _getType(me._g, arg),
27     getAttribute   : func _getAttribute(me._g, arg),
28     setAttribute   : func _setAttribute(me._g, arg),
29     getValue       : func _getValue(me._g, arg),
30     setValue       : func _setValue(me._g, arg),
31     setIntValue    : func _setIntValue(me._g, arg),
32     setBoolValue   : func _setBoolValue(me._g, arg),
33     setDoubleValue : func _setDoubleValue(me._g, arg),
34     unalias        : func _unalias(me._g, arg),
35     alias          : func(n) _alias(me._g, [isa(n, Node) ? n._g : n]),
36     equals         : func(n) _equals(me._g, [isa(n, Node) ? n._g : n]),
37     clearValue     : func _alias(me._g, [_globals()]) and me.unalias(),
38
39     getPath : func {
40         var (name, index, parent) = (me.getName(), me.getIndex(), me.getParent());
41         if(index != 0)    { name ~= "[" ~ index ~ "]"; }
42         if(parent != nil) { name = parent.getPath() ~ "/" ~ name; }
43         return name;
44     },
45
46     getBoolValue : func {
47         var val = me.getValue();
48         var mytype = me.getType();
49         if((mytype == "STRING" or mytype == "UNSPECIFIED") and val == "false") return 0;
50         return !!val;
51     },
52
53     remove : func {
54         if((var p = me.getParent()) == nil) return nil;
55         p.removeChild(me.getName(), me.getIndex());
56     },
57 };
58
59 ##
60 # Static constructor for a Node object.  Accepts a Nasal hash
61 # expression to initialize the object a-la setValues().
62 #
63 Node.new = func(values = nil) {
64     var result = wrapNode(_new());
65     if(typeof(values) == "hash")
66         result.setValues(values);
67     return result;
68 }
69
70 ##
71 # Useful utility.  Sets a whole property tree from a Nasal hash
72 # object, such that scalars become leafs in the property tree, hashes
73 # become named subnodes, and vectors become indexed subnodes.  This
74 # works recursively, so you can define whole property trees with
75 # syntax like:
76 #
77 # dialog = {
78 #   name : "exit", width : 180, height : 100, modal : 0,
79 #   text : { x : 10, y : 70, label : "Hello World!" } };
80 #
81 Node.setValues = func(val) {
82     foreach(var k; keys(val)) { me._setChildren(k, val[k]); }
83 }
84
85 ##
86 # Private function to do the work of setValues().
87 # The first argument is a child name, the second a nasal scalar,
88 # vector, or hash.
89 #
90 Node._setChildren = func(name, val) {
91     var subnode = me.getNode(name, 1);
92     if(typeof(val) == "scalar") { subnode.setValue(val); }
93     elsif(typeof(val) == "hash") { subnode.setValues(val); }
94     elsif(typeof(val) == "vector") {
95         for(var i=0; i<size(val); i+=1) {
96             var iname = name ~ "[" ~ i ~ "]";
97             me._setChildren(iname, val[i]);
98         }
99     }
100 }
101
102 ##
103 # Counter piece of setValues(). Returns a hash with all values
104 # in the subtree. Nodes with same name are returned as vector,
105 # where the original node indices are lost. The function should
106 # only be used if all or almost all values are needed, and never
107 # in performance-critical code paths. If it's called on a node
108 # without children, then the result is equivalent to getValue().
109 #
110 Node.getValues = func {
111     var children = me.getChildren();
112     if(!size(children)) return me.getValue();
113     var val = {};
114     var numchld = {};
115     foreach(var c; children) {
116         var name = c.getName();
117         if(contains(numchld, name)) { var nc = numchld[name]; }
118         else {
119             var nc = size(me.getChildren(name));
120             numchld[name] = nc;
121             if(nc > 1 and !contains(val, name)) val[name] = [];
122         }
123         if(nc > 1) append(val[name], c.getValues());
124         else val[name] = c.getValues();
125     }
126     return val;
127 }
128
129 ##
130 # Initializes property if it's still undefined.  First argument
131 # is a property name/path. It can also be nil or an empty string,
132 # in which case the node itself gets initialized, rather than one
133 # of its children.  Second argument is the default value. The third,
134 # optional argument is a property type (one of "STRING", "DOUBLE",
135 # "INT", or "BOOL").  If it is omitted, then "DOUBLE" is used for
136 # numbers, and STRING for everything else.  Returns the property
137 # as props.Node.  The fourth optional argument enforces a type if
138 # non-zero.
139 #
140 Node.initNode = func(path = nil, value = 0, type = nil, force = 0) {
141     var prop = me.getNode(path or "", 1);
142     if(prop.getType() != "NONE") value = prop.getValue();
143     if(force) prop.clearValue();
144     if(type == nil) prop.setValue(value);
145     elsif(type == "DOUBLE") prop.setDoubleValue(value);
146     elsif(type == "INT") prop.setIntValue(value);
147     elsif(type == "BOOL") prop.setBoolValue(value);
148     elsif(type == "STRING") prop.setValue("" ~ value);
149     else die("initNode(): unsupported type '" ~ type ~ "'");
150     return prop;
151 }
152
153 ##
154 # Useful debugging utility.  Recursively dumps the full state of a
155 # Node object to the console.  Try binding "props.dump(props.globals)"
156 # to a key for a fun hack.
157 #
158 var dump = func {
159     if(size(arg) == 1) { prefix = "";     node = arg[0]; }
160     else               { prefix = arg[0]; node = arg[1]; }
161
162     index = node.getIndex();
163     type = node.getType();
164     name = node.getName();
165     val = node.getValue();
166
167     if(val == nil) { val = "nil"; }
168     name = prefix ~ name;
169     if(index > 0) { name = name ~ "[" ~ index ~ "]"; }
170     print(name, " {", type, "} = ", val);
171
172     # Don't recurse into aliases, lest we get stuck in a loop
173     if(type != "ALIAS") {
174         children = node.getChildren();
175         foreach(c; children) { dump(name ~ "/", c); }
176     }
177 }
178
179 ##
180 # Recursively copy property branch from source Node to
181 # destination Node. Doesn't copy aliases. Copies attributes
182 # if optional third argument is set and non-zero.
183 #
184 var copy = func(src, dest, attr = 0) {
185     foreach(var c; src.getChildren()) {
186         var name = c.getName() ~ "[" ~ c.getIndex() ~ "]";
187         copy(src.getNode(name), dest.getNode(name, 1), attr);
188     }
189     var type = src.getType();
190     var val = src.getValue();
191     if(type == "ALIAS" or type == "NONE") return;
192     elsif(type == "BOOL") dest.setBoolValue(val);
193     elsif(type == "INT" or type == "LONG") dest.setIntValue(val);
194     elsif(type == "FLOAT" or type == "DOUBLE") dest.setDoubleValue(val);
195     else dest.setValue(val);
196     if(attr) dest.setAttribute(src.getAttribute());
197 }
198
199 ##
200 # Utility.  Turns any ghosts it finds (either solo, or in an
201 # array) into Node objects.
202 #
203 var wrap = func(node) {
204     var argtype = typeof(node);
205     if(argtype == "ghost") {
206         return wrapNode(node);
207     } elsif(argtype == "vector") {
208         var v = node;
209         var n = size(v);
210         for(var i=0; i<n; i+=1) { v[i] = wrapNode(v[i]); }
211         return v;
212     }
213     return node;
214 }
215
216 ##
217 # Utility.  Returns a new object with its superclass/parent set to the
218 # Node object and its _g (ghost) field set to the specified object.
219 # Nasal's literal syntax can be pleasingly terse. I like that. :)
220 #
221 var wrapNode = func(node) { { parents : [Node], _g : node } }
222
223 ##
224 # Global property tree.  Set once at initialization.  Is that OK?
225 # Does anything ever call globals.set_props() from C++?  May need to
226 # turn this into a function if so.
227 #
228 var globals = wrapNode(_globals());
229
230 ##
231 # Shortcut for props.globals.getNode().
232 #
233 var getNode = func return call(props.globals.getNode, arg, props.globals);
234
235 ##
236 # Sets all indexed property children to a single value.  arg[0]
237 # specifies a property name (e.g. /controls/engines/engine), arg[1] a
238 # path under each node of that name to set (e.g. "throttle"), arg[2]
239 # is the value.
240 #
241 var setAll = func(base, child, value) {
242     var node = props.globals.getNode(base);
243     if(node == nil) return;
244     var name = node.getName();
245     node = node.getParent();
246     if(node == nil) return;
247     var children = node.getChildren();
248     foreach(var c; children)
249         if(c.getName() == name)
250             c.getNode(child, 1).setValue(value);
251 }
252
253 ##
254 # Turns about anything into a list of props.Nodes, including ghosts,
255 # path strings, vectors or hashes containing, as well as functions
256 # returning any of the former and in arbitrary nesting. This is meant
257 # to be used in functions whose main purpose is to handle collections
258 # of properties.
259 #
260 var nodeList = func {
261     var list = [];
262     foreach(var a; arg) {
263         var t = typeof(a);
264         if(isa(a, Node))
265             append(list, a);
266         elsif(t == "scalar")
267             append(list, props.globals.getNode(a, 1));
268         elsif(t == "vector")
269             foreach(var i; a)
270                 list ~= nodeList(i);
271         elsif(t == "hash")
272             foreach(var i; keys(a))
273                 list ~= nodeList(a[i]);
274         elsif(t == "func")
275             list ~= nodeList(a());
276         elsif(t == "ghost" and ghosttype(a) == "prop")
277             append(list, wrapNode(a));
278         else
279             die("nodeList: invalid nil property");
280     }
281     return list;
282 }
283
284 ##
285 # Compiles a <condition> property branch according to the rules
286 # set out in $FG_ROOT/Docs/README.conditions into a Condition object.
287 # The 'test' method of the returend object can be used to evaluate
288 # the condition.
289 # The function returns nil on error.
290 #
291 var compileCondition = func(p) {
292     if(p == nil) return nil;
293     if(!isa(p, Node)) p = props.globals.getNode(p);
294     return _createCondition(p._g);
295 }
296
297 ##
298 # Evaluates a <condition> property branch according to the rules
299 # set out in $FG_ROOT/Docs/README.conditions. Undefined conditions
300 # and a nil argument are "true". The function dumps the condition
301 # branch and returns nil on error.
302 #
303 var condition = func(p) {
304     if(p == nil) return 1;
305     if(!isa(p, Node)) p = props.globals.getNode(p);
306     return _cond_and(p)
307 }
308
309 var _cond_and = func(p) {
310     foreach(var c; p.getChildren())
311         if(!_cond(c)) return 0;
312     return 1;
313 }
314
315 var _cond_or = func(p) {
316     foreach(var c; p.getChildren())
317         if(_cond(c)) return 1;
318     return 0;
319 }
320
321 var _cond = func(p) {
322     var n = p.getName();
323     if(n == "or") return _cond_or(p);
324     if(n == "and") return _cond_and(p);
325     if(n == "not") return !_cond_and(p);
326     if(n == "equals") return _cond_cmp(p, 0);
327     if(n == "not-equals") return !_cond_cmp(p, 0);
328     if(n == "less-than") return _cond_cmp(p, -1);
329     if(n == "greater-than") return _cond_cmp(p, 1);
330     if(n == "less-than-equals") return !_cond_cmp(p, 1);
331     if(n == "greater-than-equals") return !_cond_cmp(p, -1);
332     if(n == "property") return !!getprop(p.getValue());
333     printlog("alert", "condition: invalid operator ", n);
334     dump(p);
335     return nil;
336 }
337
338 var _cond_cmp = func(p, op) {
339     var left = p.getChild("property", 0, 0);
340     if(left != nil) { left = getprop(left.getValue()); }
341     else {
342         printlog("alert", "condition: no left value");
343         dump(p);
344         return nil;
345     }
346     var right = p.getChild("property", 1, 0);
347     if(right != nil) { right = getprop(right.getValue()); }
348     else {
349         right = p.getChild("value", 0, 0);
350         if(right != nil) { right = right.getValue(); }
351         else {
352             printlog("alert", "condition: no right value");
353             dump(p);
354             return nil;
355         }
356     }
357     if(left == nil or right == nil) {
358         printlog("alert", "condition: comparing with nil");
359         dump(p);
360         return nil;
361     }
362     if(op < 0) return left < right;
363     if(op > 0) return left > right;
364     return left == right;
365 }
366
367 ##
368 # Runs <binding> as described in $FG_ROOT/Docs/README.commands using
369 # a given module by default, and returns 1 if fgcommand() succeeded,
370 # or 0 otherwise. The module name won't override a <module> defined
371 # in the binding.
372 #
373 var runBinding = func(node, module = nil) {
374     if(module != nil and node.getNode("module") == nil)
375         node.getNode("module", 1).setValue(module);
376     var cmd = node.getNode("command", 1).getValue() or "null";
377     condition(node.getNode("condition")) ? fgcommand(cmd, node) : 0;
378 }
379