foo(x,y)(i.e. unqualified function-like invocations) are evaluated by looking along the searchpath for a function named
foo.
If none is found, we then look for a utility method named foo.
A utility method is one that is available from the evaluator for
doing tasks that are commonly performed, but have no natural
context/class in which they might reside. For example,
the method exec in
exec(String)
is available.
These are naturally stand-alone functions but for efficiency reasons
(and simple practical ones at this early stage in the development)
it is convenient to put them in a class and have them compiled.
Rather than confusing the role of the
evaluator
by adding these methods to it, we support an additional source of such
functions: the function table collection.
Each evaluator allows one to add (and remove) an arbitrary object or class to a list whose elements are search sequentially to resolve these function-like calls. The same method dispatching is done using each element of the list as a potential `this' or source of a static method in the case where the element is a class. If no method is found in this list, then we look in the evaluator.
This mechanism allows one to add and remove functions dynamically without instantiating a new evaluator. It allows quasi-functions to be reordered to allow dynamic resolution of "symbols". While static methods from class objects are most natural, using instances of classes also allow one to preserve state across calls but in a non-global manner, i.e. local to the evaluator and specifically the instance of the class.
Effectively, one can think of the elements as being miniature modules or libraries that one can dynamically attach and detach.
Many of the utilities built into the different evaluators can and should now be moved into their own classes.
A simple example of how this works is shown below.
We have three classes - Foo, Bar and one
which contains only static methods
FooBar
//This shows how this defines a secondary search path which is interrogated sequentially to look for methods. It can be used to simplify access to certain static methods. For example, we can gain access to the mathematical functions by attaching theadd an instance of Foo as first element of list[] this.addFunctionTable(new Foo()) 1 //add an instance of Bar as second element of list[] this.addFunctionTable(new Bar()) 2 //call foo1, found in instance of Foo[] foo1(1) [1] //a vector containing the argument squared.//call foo2(), found in instance of Foo also.[] foo2() A simple string from org.omegahat.Environment.lib.Language.Foo //call method in instance of Bar.[] bar() -1 //remove the first element of the list, // making instance of Bar the default source[] removeFunctionTable(functionTableList()[[0]]) true //check what the list now contains[] functionTableList() [org.omegahat.Environment.lib.Language.Bar@7d31aa] //call foo2, now resolved in instance of Bar[] foo2() Overridden method foo2 org.omegahat.Environment.lib.Language.Bar //add a class as element of the list, making its static methods // available[] this.addFunctionTable(StaticFoo) 2 //call foobar, found as static method in StaticFoo[] foobar() Static class method class org.omegahat.Environment.lib.Language.StaticFoo []
Math class.
[6] addFunctionTable(Math) [7] PI 3.141592653589793 [8] log(PI) 1.1447298858494002We can alias one class name with one of our choosing.
[6] addFunctionTable(Math,"A") [7] A.PI 3.141592653589793One has to be careful that there is no class named
A
in the classpath.
Because of the non-strict matching used to identify methods which allows matching of scalars to arrays, different types of numbers (such as double/Double to int, etc.), it is possible that a less approriate method will be matched earlier in the search path. For example, if in two classes A and B, we have methods
foo(int[] x) {
..
}
and
foo(int x){
..
}
respectively.
Then, if the list contains an instance of A and then B in that order,
a call
foo(1)
will incorrectly match the first one, from A.
Of course, it is possible to fix this and it can be done as the objects are added to the list rather than each time. An implementation similar to the omegahat Method dispatching would work conveniently which would involve merging the methods as they are added by compiling a new discardable class.
Alternatively, we can identify the elements in the function table by name to provide more resolution in identifying them and their methods. When adding an Each entry to the list of function tables, one can optionally specify a name by which it can be referred to at a future time. If this is omitted, the value is taken to be the class name, with the qualifying package name removed. For example,
addFunctionTable(Math)yields an entry with the name
Math
Adding an instance of a class will, by default, use its class name
also.
So in this case, we would be able to invoke
the method B.foo
via the command
B.foo(1)Or more generally,
[1] addFunctionTable(new A(), "a") 1 [2] addFunctionTable(new B(), "b") 2 [3] b.foo(1) In B.foo(int) null [4] a.foo(1) In A.foo(int[]) null
time()
Quote
can now be handled in this manner also.
A simple change to
MethodCall
allows us to search for a method in the
internal function tables by the given name
with a signature
foo(org.omegahat.Environment.Parser.Parse.List,
org.omegahat.Environment.Interpreter.Evaluator)
in a class that implements
the interface
LazyFunctionTable
It is the responsibility of the method to evaluate the arguments
appropriately. There are methods in the different
expression objects to assist this.