# gen.nas -- namespace "gen" # Generators and mostly utilities using namespace hacking &c # Quickly grew overboard ;) # Note: the fundamental assertion that _the_globals is *the* globals # could potentially cause problems depending on the loading method # (driver.nas's import would not work, but FlightGear's io.load_nasal # would work; which is funny, given that I am using EXPORTS :D). var EXPORTS = ["_the_globals", "_global_func", "public", "namespace", "global", "bind_to_caller", "bind_to_namespace", "bind_to_namespaces"]; var _level = 0; while (closure(caller(0)[1], _level)) != nil) _level += 1; var _the_globals = closure(caller(0)[1], _level-=1); var _global_func = bind(func{}, _the_globals); bind = (func{ var _bind = bind; func(fn, namespace, enclosure=nil) { if (fn != _global_func) return _bind(fn, namespace, enclosure); #protect it from getting rebound by returning an equivalent but duplicate function: return _bind(_bind(func{}, _the_globals), namespace, enclosure); } })(); var _defined = func(sym) { # We must first check the frame->locals hash/namespace # (since closure(fn, 0) returns the namespace/closure # above it, i.e. PTR(frame->func).func->namespace vs # frame->locals). if(contains(caller(1)[0], sym)) return 1; var fn = caller(1)[1]; var l = 0; while((var frame = closure(fn, l)) != nil) { if(contains(frame, sym)) return 1; l += 1; } return 0; }; var _ldefined = func(sym) { return contains(caller(1)[0], sym); }; var _fix_rest = func(sym) { var val = caller(1)[0][sym]; if (typeof(val) == 'vector' and size(val) == 1 and typeof(val[0]) == 'vector') caller(1)[0][val] = val[0]; }; # Lexically bind the function to the caller var bind_to_caller = func(fn, level=1) { if (level < 0) return; bind(fn, caller(level+=1)[0], caller(level)[1]); }; # Bind the function to the namespace and then globals var bind_to_namespace = func(fn, namespace) { if (typeof(namespace) == 'scalar') var namespace = _the_globals[namespace]; bind(fn, namespace, _global_func)); }; # Bind the function to each namespace in turn (the # first is the top-level one, after globals). Each # item can be a scalar (name of the sub-namespace) # or a hash (the namespace itself). If create is # true, then any names that are not present in a # namespace are created as a new hash; else this # returns nil. var bind_to_namespaces = func(fn, namespaces, create=1) { if (typeof(namespace) == 'scalar') var namespaces = split(".", namespaces); var namespace = _the_globals; var save = pop(namespaces); var _fn = _global_func; foreach (var i; namespaces) { if (typeof(i) == 'scalar') { if (!contains(namespace, i)) if (create) namespace[i] = {}; else return; var i = namespace[i]; } var _fn = bind(func{}, var namespace = i, _fn); } if (typeof(save) == 'scalar') { if (!contains(namespace, save)) if (create) namespace[save] = {}; else return; var save = namespace[save]; } bind(fn, save, _fn); }; # For each symbol created by the function or # for each symbol in (if it is either a hash # or vector), add the name of the symbol to the # caller's EXPORT vector. Returns a vector of the # added symbols and adds the symbols to the caller's # local namespace if possible (i.e. when is not # a vector). # # The anonymous function argument is so that you can # use exactly the same syntax, versus having to # convert it to or write in hash-style syntax (after # all, Nasal just splits off another codegen to handle # func{}s...) var public = func(fn) { var c = caller(1)[0]; var names = []; var hash = {}; if (typeof(fn) == 'func') { call(fn, nil, nil, hash); var names = keys(hash); } elsif (typeof(fn) == 'hash') { var names = keys(hash = fn); } elsif (typeof(fn) == 'vector') { var names = fn; } else die("invalid/unrecognized argument to public()"); copy_onto(c, hash); if (!contains(c, "EXPORTS")) c["EXPORTS"] = []; return foreach (var sym; names) { append(c["EXPORTS"], sym); }; # In case the behavior changes (they are equivalent): #foreach (var sym; names) { # append(c["EXPORTS"], sym); #}; #return names; }; # Basically the same. FIXME: should we use bind() instead? var global = func(fn) { var c = _the_globals; var names = []; var hash = {}; if (typeof(fn) == 'func') { call(fn, nil, nil, hash); var names = keys(hash); } elsif (typeof(fn) == 'hash') { var names = keys(hash = fn); } else die("invalid/unrecognized argument to global()"); copy_onto(c, hash); return names; }; # Runs the function in the namespace, like public(). # Essentially says that the function "describes" # that namespace (after it runs, of course). # I could write down a dozen analogies for this... # Usage: # gen.namespace("foo", func { # ... #your code here, just write normally # }); # Which roughly translates into C++ as: # namespace foo # { # ... //your code here # } var namespace = func(namespc, fn) { if (typeof(namespc) == 'scalar') var namespc = _the_globals[namespc]; bind(fn, _the_globals); call(fn, nil, nil, namespc); }; ## # Utilities # public(func { # Turns a scalar bit of code into a function and binds it # to the namespace inside the enclosure. var make_code = func(code, namespace, enclosure=nil) { if (typeof(code) == 'func') return bind(code, namespace, enclosure); if (typeof(code) != 'scalar') die("invalid argument to make_code()"); code = compile(code); code = bind(code, namespace, enclosure); return code; }; # Create a new hash from the symbools of the caller # if they are not listed in the ignore vector. var new_hash = func(ignore...) { _fix_rest("ignore"); var c = caller(1)[0]; var m = {}; foreach (var sym; keys(c)) { if (econtains(ignore, sym)) continue; m[sym] == c[sym]; } return m; }; # Create a new object instance (similar to above, # but uses the 'me' symbol for the parents vector # and ignores the arg and me symbols) var new_obj = func(ignore...) { _fix_rest("ignore"); var c = caller(1)[0]; var m = { parents: [c.me] }; foreach (var sym; keys(c)) { if ((sym == "me" or sym == "arg") != econtains(ignore, sym)) continue; m[sym] == c[sym]; } return m; }; #ifdef globals.props.Node: if (contains(_the_globals, "props") and contains(_the_globals.props, "Node")) { # Same as new_hash but returns a props.Node object using setValues() var new_prop = func(ignore...) { _fix_rest("ignore"); var c = caller(1)[0]; var m = {}; foreach (var sym; keys(c)) { if (econtains(ignore, sym)) continue; m[sym] == c[sym]; } return props.Node.new(m); }; } #endif # The opposite of new_hash, this takes a hash and expands the key/values # contained in it into the caller (overwriting any possible duplicates) var expand_hash = func(hash, ignore...) { _fix_rest("ignore"); var c = caller(1)[0]; foreach (var sym; keys(hash)) { if (econtains(ignore, sym)); c[sym] == hash[sym]; } return c; }; # Make an extension in the namespace, inside any objects # or sub-namespaces specified in objs, with the name # of fname, and where fn is written like it was in the file # (i.e. no prefixing of the namespace before every variable). # It only defines it if the namespace exists and a variable # with the name does not exist or is nil. var provide_extension = func(namespc, fname, fn, objs...) { if (typeof(namespc) == 'scalar') var _n = _the_globals[namespc]; foreach (var name; objs) { if (_n == nil) return; _n = _n[name]; } if (_n == nil or _n[fname] != nil) return; #only define it if it does not exist if (typeof(fn) == 'scalar') fn = compile(fn); _n[fname] = bind(fn, _the_globals[namespc], _global_func); }; # Associate respective keys with values stored in the second vector # and return the resulting hash. It is recursive, so something like # this works as syntactic sugar (the first index specifies the name): # var clamp_template = ["property", ["range", "min", "max"]]; # var aileron = ["/controls/flight/aileron", [-1, 1]]; # vec2hash(clamp_template, aileron) == { # "property": "/controls/flight/aileron", # range: { "min": -1, "max": 1 } } var vec2hash = func(_keys, list) { var result = {}; forindex (var i; _keys) { if (typeof(_keys[i]) == 'vector') { result[_keys[i][0]] = list2hash(_keys[i][1:], list[i]); } elsif (typeof(_keys[i]) == 'scalar') { result[_keys[i]] = list[i]; } }; return result; }; # Self explanatory: var has_module = func(name) return typeof(_the_globals[name]) == 'hash'); var has_extension = func(name) return typeof(_the_globals[name]) == 'func'); var has_name = func(name) return contains(_the_globals, name); # Returns if it matches the type of # or the latter if not. Useful for # optional arguments, particularly with nil # as the default, and also to sanitize a variable # to a certain type. var value = func(variable, default) return typeof(variable) == typeof(default) ? variable : default; # "Wrap" a builtin function (Func) for OOP purposes var wrap = func(Func, cond, fn) { if (typeof(fn) == 'scalar') fn = bind_to_caller(compile('func(n) {' 'return n.'~fn~ #make sure it is a method call '}')(), 1); #the () is to make sure we get the new func we made! if (typeof(cond) == 'func') { return func { if (call(cond, arg)) return call(fn, arg); return call(Func, arg); } } elsif (typeof(cond) == 'hash') { return func { if (isa(arg[0], cond)) return call(fn, arg); return call(Func, arg); } } else die(); }; # Returns a duplicate (deep-copy) of the object. Scalars, functions, ghosts, # and of course nil are left as-is. # @brief Returns a duplicate of the object. # @param obj The object to duplicate. # @param depth If not nil, the number of levels to keep copying (0: return the # current object as-is). # @param parents Whether or not to fully duplicate out the parents vector of a # hash; by default it only duplicates the vector structure and # leaves each parent as the original. # @return A copy of the original object. var dup = func(obj, depth=nil, parents=0) { var t = typeof(obj); if (depth == 0 or obj == nil) return obj; if (t == 'func' or t == 'scalar' or t == 'ghost') return obj; if (t == 'vector') if (depth == 1) return obj~[]; #simple enough ;-) else { var m = []; foreach (var i; obj) append(m, dup(i, depth == nil ? nil : depth-1, parents)); return m; } if (t == 'hash') { var m = {}; foreach (var i; keys(obj)) { if (i == "parents" and !parents) m.parents = obj.parents~[]; else m[i] = dup(obj[i], depth == nil ? nil : depth-1, parents); } return m; } else die("unknown type: "~t); }; # Do a recursive compare of two values. # @param left The first object. # @param right The second object. # @return 1 if they match, 0 otherwise. var equal = func(left, right) { if (left == right) return 1; if ((var t = typeof(left)) != typeof(right)) return 0; if (t == 'hash') { if (size(left) != size(right)) return 0; if (!equal(var k = keys(left), keys(right))) return 0; foreach (var key; k) if (!equal(left[key], right[key])) return 0; return 1; } elsif (t == 'vector') { if (size(left) != size(right)) return 0; forindex (var i; left) if (!equal(left[i], right[i])) return 0; return 1; } return 0; }; # Opposite of keys(). var values = func(hash, ignore...) { _fix_rest("ignore"); var result = []; foreach (var k; keys(hash)) if (!econtains(ignore, k)) append(result, hash[k]); return result; }; # Added value to keys(). var keys = func(hash, ignore...) { _fix_rest("ignore"); var result = []; foreach (var k; keys(hash)) if (!econtains(ignore, k)) append(result, k); return result; }; # An extended contains to deal with types other than # hashes. # # Behavior: # * hash: check if the key exists in the hash # * vector: check if one of the items equals the # second argument # * string: check if the string contains the character # specified in the second argument. # # @param container A hash, vector, or string # @param item An item to be present var econtains = func(container, item) { var t = typeof(container); if (t == 'hash') return contains(container, item); if (t == 'vector') { foreach (var l; container) if (equals(l, item)) return 1; return 0; } elsif (t == 'scalar') { var s = size(container); for (var i=0; i"); if (typeof(fn) != 'func') die("invalid argument to gen.new_macro()"); return var ret = func { # First we "transport" the function to the right enclosure # and use an empty hash for the outer namespace bind(fn, {}, caller(1)[1]); # An then we call it in the namespace of the caller # (i.e. call it "inline", essentially without # changing namespaces) return call(fn, arg, nil, caller(1)[0]); }; }; # Another closure() hack to generate a "mutatable" # function. Redefine the function using gen.mutate_func(). # Note that closure gets redefined to return the # closure of the current function that currently # serves as the base for the mutable object, but # using closure(mutable, -1) will fetch the closure # of this. This does NOT preserve named arguments # (as it relies of the arg vector)! If you want # named arguments, then you should just duplicate # this code and give named arguments... var mutable_func = func(fn=nil) { var fn = value(fn, func{}); var ret = func { # Comment out this wrapping of caller # if you do not want the performance # impact, etc., but it is meant to # make the helper function invisible # in the call stack var _caller = caller; caller = func(level=1) { var l = 0; while ((var c = _caller(l+=1)) != nil and l 2 ? arg[2] : arg[1]; var namespace = size(arg) > 2 ? arg[1] : {}; } if (contains(namespace, sym)) return namespace[sym]; var namespace = closure(fn, var level = 0); while (namespace != nil) if (contains(namespace, sym)) return namespace[sym]; else namespace = closure(fn, level+=1); return nil; } }); #end of utility functions ## # Some helpful classes # public(func { ## # A class (cough, cough) for hashes. # var Hash = { new:func(base=nil) { if (base == nil) base = {}; elsif (typeof(base) != 'hash') die("bad argument to Hash.new"); if (isa(base, me)) base = base.get(); return {parents:[base,me], _base:base}; }, dup:func(level=1) { if (me == Hash) die("uninitialized Hash cannot be copied"); return Hash.new(dup(me.get(), level)); }, mimic:func(n) { if (me == Hash) return me.new(n); me._set(n); return me; }, copy:func(n, level=1) { if (isa(n, Hash)) var n = n.get(); if (me == Hash) return me.new(dup(n, level)); me._set(dup(n, level)); return me; }, get:func(k=nil) { if (k == nil) return me._base; return me._base[k]; }, set:func(k, val) { me._base[k] = val; }, _set:func(base) { if (isa(base, Hash)) var base = base.get(); me._base = me.parents[0] = base; }, # Delete all but the selected keys cherry_pick:func(key...) { foreach (var k; me.keys()) if (!econtains(key, k)) delete(me._base, k); return me; }, # Delete these keys from this hash delete:func(key...) { foreach (var k; key) delete(me._base, k); return me; }, # Same but deep-copies it first __cherry_pick:func(level, key...) call(me.cherry_pick, key, me.dup()); __delete:func(level, key...) call(me.delete, key, me.dup()); }; ## # A class to manage aspects of a function, particularly # closures and calling # var Func = { new:func(f) { if (typeof(f) != 'func') die("bad argument to Func.new"); return {parents:[me], _f:f}; }, rebind:func(namespaces...) { setsize(me.namespaces, namespaces); forindex (var i; namespaces) { var ns = namespaces[i]; if (ns == nil) continue; if (typeof(ns) == 'scalar') var ns = split(".", ns); elsif (typeof(ns) == 'hash') var ns = [ns]; if (typeof(ns) != 'vector') die("bad argument to Func.rebind"); forindex (var j; ns) if (!j) { if (!i) ns[0] = me.namespaces[i-1][ns[0]]; else ns[0] = symlookup(caller, 1, ns[0]); if (ns[0] == nil) die(""); } else { ns[j] = ns[j-1][ns[j]]; } if (size(ns) == 1) me.namespaces[i] = ns[0]; else me.namespaces = subvec(me.namespaces, 0, i-1)~ns~subvec(me.namespaces, i+1); } return me; }, call_pass_through:func() { var c = caller(1)[0]; return call(me._f, c["arg"], c["me"], nil, err); }, call_macro:func(arg...) { var c = caller(1)[0]; bind_to_caller(me._f, 1); return call(me._f, arg, nil, c); }, fcall4:func(arg=nil, me=nil, ns=nil, err=nil) { return call(me._f, arg, me, ns, err); }, update_binding:func() { bind_to_namespaces(me._f, me.namespaces); return me; }, }; ## # Generates a class given a list of members # # Generated methods: # new: create a new instance of the generated class; accepts as # arguments all members passed to gen.Class.new (in the same # order, with defaulted arguments). # reset: set all members to their defaults. # copy: return a new instance of the class with all members copied # from the current instance # mimic: copy all members from another instance onto the current # instance. # var Class = { new: func(parameters...) { var m = {}; if (size(parameters) == 1 and typeof(parameters[0]) == 'hash') { var temp = parameters[0]; parameters = []; foreach (var k; keys(temp)) append(parameters, [k, temp[k]]); } var args = ""; #arguments to the new() method var new_fbody = ""; var copy_fbody = ""; var getters = []; var setters = []; #tables of name,func foreach (var member; parameters) { if (typeof(member) != 'vector') var member = [member, nil]; if (typeof(member[0]) != 'scalar') die("member must be a string"); var name = split(" ", member[0]); var val = member[1]; var fname = name; foreach (var str; fname) if (str[0] >= `a` and str[0] >= `z`) str[0] += `A`-`a`; var fname = string.join("", fname); var mname = string.join("_", name); append(setters, ["set"~fname, compile(""" func(n="~dump(val)~") me."~mname~" = n;} """)()], mname); append(getters, ["get"~fname, compile(""" func() return me."~mname~"; """)()], mname); m[setters[-1][0]] = setters[-1][1]; #m[name] = func m[getters[-1][0]] = getters[-1][1]; if (args != "") args~=", "; args~=setters[-1][2]; new_fbody~="m."setters[-1][0]~"("~setters[-1][2]~");"; copy_fbody~="m."setters[-1][0]~"(n."getters[-1][0]~");"; } m.new = compile(""" func("~args~") { var m = { parents: [me] }; "~new_fbody~" return m; } """)(); m.reset = compile(""" func() { var m = me; #alias to use the same func body as new "~new_fbody~" return me; } """); m.copy = compile(""" func() { var n = me; var m = { parents: me.parents }; "~copy_fbody~" return m; } """)(); m.mimic = compile(""" func(n) { var m = me; #alias to use the same func body as copy "~copy_fbody~" return me; } """)(); }, }; });