remove README.Protocol and add a README that refers to the "real"
[fg:toms-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 # There is no support for the "listener" interface yet.  The aliasing
12 # feature isn't exposed, except that you can get an "ALIAS" return
13 # from getType to detect them (to avoid cycles while walking the
14 # tree).
15 #
16 Node = {
17     getType        : func { wrap(_getType(me._g, arg)) },
18     getAttribute   : func { wrap(_getAttribute(me._g, arg)) },
19     setAttribute   : func { wrap(_setAttribute(me._g, arg)) },
20     getName        : func { wrap(_getName(me._g, arg)) },
21     getIndex       : func { wrap(_getIndex(me._g, arg)) },
22     getValue       : func { wrap(_getValue(me._g, arg)) },
23     setValue       : func { wrap(_setValue(me._g, arg)) },
24     setIntValue    : func { wrap(_setIntValue(me._g, arg)) },
25     setBoolValue   : func { wrap(_setBoolValue(me._g, arg)) },
26     setDoubleValue : func { wrap(_setDoubleValue(me._g, arg)) },
27     getParent      : func { wrap(_getParent(me._g, arg)) },
28     getChild       : func { wrap(_getChild(me._g, arg)) },
29     getChildren    : func { wrap(_getChildren(me._g, arg)) },
30     removeChild    : func { wrap(_removeChild(me._g, arg)) },
31     removeChildren : func { wrap(_removeChildren(me._g, arg)) },
32     getNode        : func { wrap(_getNode(me._g, arg)) },
33
34     getPath : func {
35         name = me.getName();
36         if(me.getIndex() != 0)    { name = name ~ "[" ~ me.getIndex() ~ "]"; }
37         if(me.getParent() != nil) { name = me.getParent().getPath() ~ "/" ~ name; }
38         return name;
39     },
40
41     getBoolValue : func {
42         val = me.getValue();
43         if(me.getType() == "STRING" and val == "false") { 0 }
44         elsif (val == nil) { 0 }
45         else { val != 0 }
46     },
47 };
48
49 ##
50 # Static constructor for a Node object.  Accepts a Nasal hash
51 # expression to initialize the object a-la setValues().
52 #
53 Node.new = func {
54     result = wrapNode(_new());
55     if(size(arg) > 0 and typeof(arg[0]) == "hash") {
56         result.setValues(arg[0]);
57     }
58     return result;
59 }
60
61 ##
62 # Useful utility.  Sets a whole property tree from a Nasal hash
63 # object, such that scalars become leafs in the property tree, hashes
64 # become named subnodes, and vectors become indexed subnodes.  This
65 # works recursively, so you can define whole property trees with
66 # syntax like:
67 #
68 # dialog = {
69 #   name : "exit", width : 180, height : 100, modal : 0,
70 #   text : { x : 10, y : 70, label : "Hello World!" } };
71 #
72 Node.setValues = func(val) {
73     foreach(var k; keys(val)) { me._setChildren(k, val[k]); }
74 }
75
76 ##
77 # Private function to do the work of setValues().
78 # The first argument is a child name, the second a nasal scalar,
79 # vector, or hash.
80 #
81 Node._setChildren = func(name, val) {
82     var subnode = me.getNode(name, 1);
83     if(typeof(val) == "scalar") { subnode.setValue(val); }
84     elsif(typeof(val) == "hash") { subnode.setValues(val); }
85     elsif(typeof(val) == "vector") {
86         for(var i=0; i<size(val); i+=1) {
87             var iname = name ~ "[" ~ i ~ "]";
88             me._setChildren(iname, val[i]);
89         }
90     }
91 }
92
93 ##
94 # Counter piece of setValues(). Returns a hash with all values
95 # in the subtree. Nodes with same name are returned as vector,
96 # where the original node indices are lost. The function should
97 # only be used if all or almost all values are needed, and never
98 # in performance-critical code paths. If it's called on a node
99 # without children, then the result is equivalent to getValue().
100 #
101 Node.getValues = func {
102     var children = me.getChildren();
103     if(!size(children)) return me.getValue();
104     var val = {};
105     var numchld = {};
106     foreach(var c; children) {
107         var name = c.getName();
108         if(contains(numchld, name)) { var nc = numchld[name]; }
109         else {
110             var nc = size(me.getChildren(name));
111             numchld[name] = nc;
112             if (nc > 1 and !contains(val, name)) val[name] = [];
113         }
114         if(nc > 1) append(val[name], c.getValues());
115         else val[name] = c.getValues();
116     }
117     return val;
118 }
119
120 ##
121 # Useful debugging utility.  Recursively dumps the full state of a
122 # Node object to the console.  Try binding "props.dump(props.globals)"
123 # to a key for a fun hack.
124 #
125 var dump = func {
126     if(size(arg) == 1) { prefix = "";     node = arg[0]; }
127     else               { prefix = arg[0]; node = arg[1]; }
128
129     index = node.getIndex();
130     type = node.getType();
131     name = node.getName();
132     val = node.getValue();
133
134     if(val == nil) { val = "nil"; }
135     name = prefix ~ name;
136     if(index > 0) { name = name ~ "[" ~ index ~ "]"; }
137     print(name, " {", type, "} = ", val);
138
139     # Don't recurse into aliases, lest we get stuck in a loop
140     if(type != "ALIAS") {
141         children = node.getChildren();
142         foreach(c; children) { dump(name ~ "/", c); }
143     }
144 }
145
146 ##
147 # Recursively copy property branch from source Node to
148 # destination Node. Doesn't copy aliases. Copies attributes
149 # if optional third argument is set and non-zero.
150 #
151 var copy = func(src, dest, attr = 0) {
152     foreach(var c; src.getChildren()) {
153         var name = c.getName() ~ "[" ~ c.getIndex() ~ "]";
154         copy(src.getNode(name), dest.getNode(name, 1), attr);
155     }
156     var type = src.getType();
157     var val = src.getValue();
158     if(type == "ALIAS" or type == "NONE") { return; }
159     elsif(type == "BOOL") { dest.setBoolValue(val); }
160     elsif(type == "INT") { dest.setIntValue(val); }
161     elsif(type == "DOUBLE") { dest.setDoubleValue(val); }
162     else { dest.setValue(val); }
163     if(attr) dest.setAttribute(src.getAttribute());
164 }
165
166 ##
167 # Utility.  Turns any ghosts it finds (either solo, or in an
168 # array) into Node objects.
169 #
170 var wrap = func {
171     argtype = typeof(arg[0]);
172     if(argtype == "ghost") {
173         return wrapNode(arg[0]);
174     } elsif(argtype == "vector") {
175         v = arg[0];
176         n = size(v);
177         for(i=0; i<n; i+=1) { v[i] = wrapNode(v[i]); }
178         return v;
179     }
180     return arg[0];
181 }
182
183 ##
184 # Utility.  Returns a new object with its superclass/parent set to the
185 # Node object and its _g (ghost) field set to the specified object.
186 # Nasal's literal syntax can be pleasingly terse. I like that. :)
187 #
188 var wrapNode = func { { parents : [Node], _g : arg[0] } }
189
190 ##
191 # Global property tree.  Set once at initialization.  Is that OK?
192 # Does anything ever call globals.set_props() from C++?  May need to
193 # turn this into a function if so.
194 #
195 var globals = wrapNode(_globals());
196
197 ##
198 # Sets all indexed property children to a single value.  arg[0]
199 # specifies a property name (e.g. /controls/engines/engine), arg[1] a
200 # path under each node of that name to set (e.g. "throttle"), arg[2]
201 # is the value.
202 #
203 var setAll = func {
204     node = props.globals.getNode(arg[0]);
205     if(node == nil) { return; }
206     name = node.getName();
207     node = node.getParent();
208     if(node == nil) { return; }
209     children = node.getChildren();
210     foreach(c; children) {
211         if(c.getName() == name) {
212             c.getNode(arg[1], 1).setValue(arg[2]); }}
213 }
214
215 ##
216 # Evaluates a <condition> property branch according to the rules
217 # set out in $FG_ROOT/Docs/README.conditions. Undefined conditions
218 # and a nil argument are "true". The function dumps the condition
219 # branch and returns nil on error.
220 #
221 var condition = func(p) {
222     if(p == nil) { return 1; }
223     if(!isa(p, props.Node)) { p = props.globals.getNode(p); }
224     return _cond_and(p)
225 }
226
227 var _cond_and = func(p) {
228     foreach(var c; p.getChildren()) {
229         if(!_cond(c)) { return 0; }
230     }
231     return 1;
232 }
233
234 var _cond_or = func(p) {
235     foreach(var c; p.getChildren()) {
236         if(_cond(c)) { return 1; }
237     }
238     return 0;
239 }
240
241 var _cond = func(p) {
242     var n = p.getName();
243     if(n == "or") { return _cond_or(p); }
244     if(n == "and") { return _cond_and(p); }
245     if(n == "not") { return !_cond_and(p); }
246     if(n == "equals") { return _cond_cmp(p, 0); }
247     if(n == "not-equals") { return !_cond_cmp(p, 0); }
248     if(n == "less-than") { return _cond_cmp(p, -1); }
249     if(n == "greater-than") { return _cond_cmp(p, 1); }
250     if(n == "less-than-equals") { return !_cond_cmp(p, 1); }
251     if(n == "greater-than-equals") { return !_cond_cmp(p, -1); }
252     if(n == "property") { return !!getprop(p.getValue()); }
253     printlog("alert", "condition: invalid operator ", n);
254     dump(p);
255     return nil;
256 }
257
258 var _cond_cmp = func(p, op) {
259     var left = p.getChild("property", 0, 0);
260     if(left != nil) { left = getprop(left.getValue()); }
261     else {
262         printlog("alert", "condition: no left value");
263         dump(p);
264         return nil;
265     }
266     var right = p.getChild("property", 1, 0);
267     if(right != nil) { right = getprop(right.getValue()); }
268     else {
269         right = p.getChild("value", 0, 0);
270         if(right != nil) { right = right.getValue(); }
271         else {
272             printlog("alert", "condition: no right value");
273             dump(p);
274             return nil;
275         }
276     }
277     if(left == nil or right == nil) {
278         printlog("alert", "condition: comparing with nil");
279         dump(p);
280         return nil;
281     }
282     if(op < 0) { return left < right; }
283     if(op > 0) { return left > right; }
284     return left == right;
285 }
286
287