# 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 <fn> or
# for each symbol in <fn> (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 <fn> 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 <variable> if it matches the type of
# <default> 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<s; i+=1)
if (container[i] == item) return 1;
return 0;
}
};
# Copy one hash onto another.
# @param container The hash to copy the values onto.
# @param data The hash with the key:value pairs.
# @param replace If false, check if the key doesn't exist
# in the container before copying.
# @param ignore... Keys to avoid copying
var copy_onto = func(container, data, replace=1, ignore...) {
_fix_rest("ignore");
foreach (var k; keys(data))
if ((replace or !contains(container, k)) and !econtains(ignore, k))
container[k] = data[k];
return container;
};
# Accumulate all closure of the caller of this function
# into the local namespace of that caller.
# @return The namespace of the caller.
var accumulate = func(ignore...) {
_fix_rest("ignore");
var c = caller(0);
for (var level=0; closure(c[1], level+1) != nil; level+=1) {
copy_onto(c[0], closure(c[1], level+1), 0, ignore);
}
return c[0];
};
# Overload a function. Takes a list of entries with two
# fields per item:
# * list of argument types in order (vector)
# * function to call (func)
var overload = func(list...) {
return func {
foreach (DESC; var desc; list) {
if (size(desc[0]) != size(arg)) continue;
forindex (TYPE; var i; desc[0]) {
if (typeof(desc[0][i]) == 'scalar'
? typeof(arg[i]) != desc[0][i]
: !isa(arg[i], desc[0][i]))
continue DESC;
}
return call(desc[1], arg);
}
};
};
# A small wrapper to create 'macros', functions that are run in the caller
# an thus have direct access to any local variables (they cannot use
# break/continue/return though!)
var new_macro = func(fn) {
if (typeof(fn) == 'scalar')
fn = compile(fn, "<macro:"~caller(1)[2]~"@line"~caller(1)[3]~">");
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 <ret> 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<level+1)
# If we find our function on the stack, then we return
# caller(level+1) (as viewed from our caller's vantage
# point), or caller(level+2) (as viewed from here).
if (c[1] == ret) return _caller(level+2);
return _caller(level+1); #< this is probably unreachable in most
# cases, since unless the data function
# triggers a call of another function
# from C code (like a listener), the
# "data" function will be on the call
# stack and we will return the next caller
}
call(fn, arg);
caller = _caller; #undo our changes
};
return ret;
};
# Redefine the function we saved
var mutate_func = func(mutable, new_fn=nil) {
var new_fn = value(new_fn, func{});
closure(mutable).fn = new_fn;
};
var mutable_str = func(base) {
var sz = size(base);
var ret = bits.buf(sz);
for (var i=0; i<sz; i+=1)
ret[i] = base[i];
return ret;
};
var symlookup = func {
if (size(arg) == 3 and arg[0] == caller) {
var fn = caller(arg[1]+1)[1];
var namespace = caller(arg[1]+1)[0];
var sym = arg[2];
} else {
var fn = arg[0];
var sym = size(arg) > 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;
}
""")();
},
};
});