||Compiled Lazy and
Variable Number of Arguments Functions
||Constructors
||Scoping
||Type Checking
||Lazy Evaluation
||Overloading Functions
||Dynamically Compiled Classes
||User Level Classes
||Casting
||Dynamic Class Location
||Loading Classes
||Array Initialization
||Subsettting
||Utilities
||Dynamic CORBA
||Compilation
||Signal Handlers
||Prompt Evaluation
||Multiple Evaluator Classes, Instances and Threads
||Others
||Lexers and Parsers
||Substitute
||Comments
||Language Changes
||Input Continuation Separator
||Internal Utility Functions
||
Where fields or methods are not public but necessary, we have provided
proxy methods in the evaluator which act as Internals.
There is now support in the method calling mechanism
to invoke public methods in protected classes.
This allows us to access objects such
as UNIXProcess which are not directly accessible
in the earlier reflectance setup.
This is the preferred way to provide access to these methods
unless additionally functionality is to be provided.
=) invokes the assign() method of the current Evaluator and this communicates with its SearchPath to determine the appropriate
database to which the object is assigned. At that point the
characteristics of the selected Database apply. The other database
operators in the evaluator include objects(),
remove() (no rm) exists(). One
can manipulate objects directly in a database rather than via the
default assignment behavior of the evaluator using the same methods
but on the target database. It does not have to be in the search path.
The searchPath is an ordered
list/table of database objects maintained by an evaluator. Each can be
shared across multiple evaluator
instances. Similarly, elements of one search path can be elements of
another search path.
There are several different types of databases, all of which can be used in the basic evaluation interchangeably. What differentiates them is the level of support they provide for 1) persistence, 2) program support.
Persistence is provided essentially by the Java serialization with a
little bit of bookkeeping. There are two models for persistence: 1)
the S-style of individual objects stored in separate files, and 2)
collections of objects serialized as a single unit (more similar to
the R style). These different models are implemented by related but
different classes - PersistentObjectDatabase and ObjectDatabase.
Assignments in the first model are immediately committed to disk.
When the databases is first attached, its contents are filtered
and "matching" filenames are taken as objects and the
database (partially) populated.
In the second model, you must explicitly serialize the database. (This can be done on session completion.) The
Evaluator's
serialize()
methods can be used for this.
To serialize an object, one can use the family of serialize()
methods in the evaluator. Basically, specify the object and the
destination (i.e. java.io.File or String filename)
So, the following works
? serialize(1::10, "/tmp/foo")
? in = new ObjectInputStream(new FileInputStream("/tmp/foo"))
? in.readObject()
So for the default database, you can do
? serialize(defaultDatabase(), "/tmp/foo")To retrieve it, there is a special convenience method
? attach(new File("/tmp/foo"))
or
? attach(new File("/tmp/foo"), 0)
to specify the position of the attachment.
The biggest difference between the two models is what is
stored. In the first scenario, if the object being serialized contains
references to other objects, their contents will be serialized in the
serialized version. When it is restored, these associated objects will
not be restored into the database. This means that dynamic links will
not be conserved and more disk space will be used.
The second form of persistence does preserve links
and takes minimal copies. This is provided for free by Java.
For example,
? x = y = 1::10
? x[0] = 100
? y[0]
100
? serialize(defaultDatabase(), "temp.ser")
? exit()
% omega
? attach(new File("temp.ser"))
? x[0]
100
? x[0] = 20
? y[0]
20 // still reference to the same object as x.
This example also illustrates the convenience methods
for serializing the database and attaching it.
Either persistence model can use compression. The GZIP output
streams can be applied at an object level. A simple flag controls
this in the PersistentObjectDatabase.
For serializing the entire object, simply construct the ObjectOutputStream with a GZIPOutputStream and the input stream in a
corresponding manner.
Programming support is provided by several different database classes.
All databases can be attached as read-write or read-only. This status
applies to the collection indicating whether new elements can be added
or existing values removed from the collection. The class
ReadWriteDatabase allows individual
elements to be specified as read-write or read-only. This allows one
to allow new elements be written to a database while ensuring that
certain others are not removed or overwritten. (This is how
final variables are implemented.)
The default class of database can be specified in the OmegaOptions
file as defaultDatabaseClass
Databases are used extensively in the evaluation of statement blocks.
The LazyFunctionDatabase is for use in evaluating functions with lazy arguments (as the name
suggests) and hides the details of delayed evaluation of specific
arguments. These evaluation frames are temporarily attached to the
Evaluator's searchPath.
The different database classes implement the Database interface. One can substitute
entirely different implementations of this interface into the
evaluator's search path.
Databases also support listeners. Other objects can register to be notified of assignment and removal events. Additionally, they can receive notification of attachments and detachments.
a.b style syntax for
referencing the variable b in a Database a.
This allows access to variables
in nested Databases to be accessed easily
For example,
[13] a = newDatabase("Foo")
{}
[14] a.b = 1
1
[15] a
{b=1}
[16] a.c = 2
2
[17] a.d = newDatabase("bar")
{}
[18] a.d.a = 1
1
[19] a
{c=2, b=1, d={a=1}}
[20] a.d.a
1
The primary motivation for this nesting is used in conjunction with
CORBA NamingContexts
to allow CORBA objects appear as local Omega variables.
Specifically, the Omega
NamingServiceDatabase class
is a proxy for a NamingContext
and can be attached to the SearchPath
as a regular Database. Objects within a sub-NamingContext
can then be accessed using the . (or field-access) notation
This is explained more thoroughly in the
CORBA documentation.
This is an example of the more general Omega
DynamicFieldAccessInt interface which allows an
object to reflect fields dynamically rather than supporting real Java
Fields.
q() method checks the value of
a property (OmegaFinalScript) for a file of Omega commands
to run when the application is terminated.
x
somewhere in the search path and yet want to use it locally
e.g within the body of loop.
While we can declare it by providing a type such
as
for(i=0;i <k;i++) {
Date x = 1
..
}
this assigns a particular type to the variable
or is irrelevant.
In order to handle this,
we can use the "reserved word" "local"
to identify that a variable should be created locally.
This is much like the my in Perl.
The above statement would look like
for(i=0;i<k;i++) {
local x = 1;
..
}
but does not assign any type to the variable
x.
The choice of the word local is not
fixed as one can change the value of the field
LocalDeclarator in the class
Omega.Parser.Parse.LocalVariable
x = new StringBuffer()
y = x.getClass() z = new y()In other words, we can use an explicit class name or we can use a
Class object stored in an Omega variable.
The evaluation model may make this understandable.
We treat both as Name expressions.
Evaluating a Name
resolves an omega variable or a class name.
The order of the search may lead to surprising
results. Suppose, we have
a variable named String.
Then, new String()
may fail because it will use the value of the variable
String rather than treating
it as a name of a class.
We may change this in the future to iron out this precedence.
In the example above, to obtain an object of class String
one can use
x = new lang.String()
searchPath() method displays the
current list; find() allows one to locate an object, etc.
x = new File("foo");"x" in the appropriate database
or overwrite the existing value of a previously created
object named x.
See also Scoping above.
The exception to the optional type declaration arises in cases such as
static Object x = 2;The grammar currently requires a type (default should be
Object) if a modifier (e.g static,
synchronized, static, public,
etc.) is specified. Otherwise, a syntax error is reported. This needs to be changed.
In the near future, I will add the abilitity to optionally and dynamically require declaration of variables. We can do this without type checking using the local keyword introduced in omega. The dynamic aspect allows the feature to be applied to certain blocks, or expression trees, when debugging errors that may be caused by misspelling variable names, etc.
setq* of Lisp. Since this is
a common form of lazy evaluation specification, this might suffice.)
When defining a function, a parameter name can be
qualified not only by a type
but also by the keyword
lazy.
This ensures that the value of this argument provided in a function
call will not be evaluated until it is needed.
For example,
function foo(x, y= x.size()) {
..
show(y);
}
will defer the evaluation of y until
the last statement is evaluated.
In some cases, (e.g. the switch function again),
it is more convenient to declare all the arguments
of a function as lazy, rather than having to
qualify each one individually with
lazy.
This can be done by qualifying the function
definition with the keyword
lazy
as in
lazy function foo(x, y=x.size()) {
...
}
The lazy
keyword must preceed the optional
return type.
Given the ability to specify the ``mode'' of an individual argument
and a default mode as lazy,
we have also introduced the keyword
eager
which is used in exactly the same places
as lazy
but has the opposite effect - namely, the argument is evaluated
at the time of the function call.
For example,
in the following, all the arguments will be evaluated
only when they are accessed except the first one.
lazy function Switch(eager condition, ...) {
..
}
We can also introduce a deferred
which might indicate to evaluate at the time of the function call but
not at the time of the function definition. The Quote() method can be used for this.
For an alternative to interpreted functions, see Utility Functions
Classses that are to be used as entries in the function table list, either directly or indirectly via instances, can implement either or both of the interfaces LazyFunctionTable and VarArgsFunctionTable The first of these indicates that the arguments should not be evaluated, but passed directly to Java method as a list. Such methods must be declared
public return type foo(org.omegahat.Environment.Parser.Parse.List args,
org.omegahat.Environment.Interpreter.Evaluator evaluator)
All the arguments in the interpreted call are passed unevaluated. The
method itself is responsible for evaluating the arguments as
necessary. The second argument provides the evaluator in which the
evaluation should be performed. Methods that assist in the evaluation
of these arguments (e.g. getting the frame correct, handling named
argument assignments, matching names, etc.) can be provided in a base
class that a class implementing either of these interfaces can extend.
Many of the methods are already available in different forms
from the expression classes in the
Environment.Parser.Parse
sub-package.
The second interface, VarArgsFunctionTable implies that the arguments should be evaluated (unless the object or class implements the LazyFunctionTable interface as well as this one). However, they are passed as a single List
For some examples of classes, see
time()
and Quote()
functions.
Switch()
function.
This setup changes the search order for symbols. Now we search the function tables before the regular databases for functions.
We do not necessarily (or currently) return the reflect.Method associated with a method in a lazy
or variable argument object.
function
with method.
For example, the two commands
> function foo(x,y) {...}
> method foo(Vector x, Vector y) {}
has the effect of merging the two function definitions
into a collection of methods referred to by the "function"
name foo.
A call such as
foo(a,b)will search the collection of methods for the "most appropriate" match and then invoke that matching function.
Handling ... and
lazy evaluation is not very well supported at present
to say the least!
There are now two implementations of these classes. See Interpreted Classes. The newest mechanism generates compiled Java classes to represent the user-level classes and implements the declared methods by calling the associated method. This neccesitated facilities for identifying unqualified method calls by looking in the instance itself rather than in the global list and
The entirely interpreted version does support multiple inheritance. Additionally, linkage between the parent classes and fields is supported so that changes to base classes are propagated to the children, by default, but with events which allow the right of veto.
Classes are represented by templates or prototypes which absorb the
fields and methods from the parent classes. Instances transfers
copies of these fields on instantiation (allowing changes to the
prototypes between instantiation) for regular fields and references
for the static fields shared across instances of the class.
Similarly, method tables (named collections of Method objects) are shared across instances.
A class can be defined by directly instantiating `free' AbstractUserClass or UserClass objects and adding fields to it. In this
case, they are not visible to the evaluator and cannot be used for
method and field dispatching. To introduce the class to the system,
one can use the evaluator methods defineClass(AbstractUserClass) and defineClass(String name, Vector superclasses) and
add fields with the return value. This has the effect of putting the
AbstractUserClass into a table which manages
packages of classes in a multi-way hierarchy. These tables are
evaluator-specific but inherited from the parent evaluator when
available.
As with most of the larger features added to the interpreter/evaluation model, support for User Level classes is optional and does not impact the performance of the basic evaluator which does not support it. This is done basically by providing classes that extend field and method access and instantiating those in an extended parser that overrides the factory methods for creating such objects.
invoke() method
of the reflect.Method
class insists on invoking the method dynamically located within the
object, not the class of the method object.
Hence, we cannot invoke methods in the base class overridden
by a derived class in an instance of the derived class.
In Java 1.1, we can get around this limitation of the JVM
and implement casting of types for arbitrary method invocation.
Consider the class
Similarly, if we have a class
The change to the semantics of the op code
Casting also works in the very special case
of a method call invoking
Note also that a call such as
Now, system level classes (i.e. those used by the classes in the Omega
distribution) must be in the classpath but classes referenced only by
the user are loaded by a separate class loader.
This is done by adding new directories or jar/zip files
to the object returned by the method
URLs in this context are slightly different from regular files in that
they are expected to be Zip or Jar files, rather than directories.
When the URL is such a container, the ClassList mechanism
reads the list of contained classes.
The classes are now identified and can be referenced using
partial qualification.
There are three different URL-based class loaders which
resolve the class references.
Dynamic classes are created and loaded
by writing byte-code (via the Jas packages)
and a special class loader. This allows the classes to be
written to disk for cached use in future sessions
or to be written into a buffer directly loaded.
The latter means that the file does not have to be written into
one of the elements in the classpath (local or system).
The following illustrates how to create a new class,
named
The different class loaders and locators are fields in the basic
evaluator. These are shared across parent and children when new
evaluators are created from the parent evaluator.
They can of course be immediately overwritten or
different constructors can be invoked to alter this behavior.
The mechanism works for some of the core Java classes (as special
cases). These are currently
The subsetting mechanism is implemented in the methods
Examples of classes that implement these two subsetting interfaces
are OrderedTable
and
DataList
This mechanism illustrates the more general notion
of operator overloading. This will be added
for the different operators
If the particular instance of the
This can be easily used to provide easy access to assignments to
specific databases. When a database is attached, it can be assigned to
an object in the default database. It currently provides its own name
by default and can be provided one. Thus, having attached a database
named
Rather than polluting the name space of the default database,
newly attached database do not have to be assigned to an Omega
variable. Instead, the search mechanism for the
The current implementation allows arbitrary nesting of access to
database elements. For example,
A
public class A {
public String foo() {
System.err.println("In A");
return "A";
}
public int val() {
return(1);
}
public boolean bar() {
return(true);
}
}
and the derived class B
public class B extends A {
public String foo() {
System.err.println("In B");
return "B";
}
public int val() {
return(-1);
}
public boolean bar() {
return(false);
}
}
Then, creating an instance of the class B,
we can invoke each of its methods.
[] b = new B()
[] b.foo()
In B
B
[] ((A)b).foo()
In A
A
[] b.val()
-1
[] ((A)b).val()
1
C that extends
B and doesn't override all of the methods
will behave as expected.
invokespecial
does not allow us to do this in Java2 (at the moment!!!).
Casting Arguments
Consider the first case.
Suppose we have an overloaded method A in a class called
Cast with two definitions as follows.
public void A(Hashtable tb) {
System.err.println("In hashtable");
}
public void A(Omega.Utils.OrderedTable tb) {
System.err.println("In ordered table");
}
Then, the following illustrates the nature of casting the argument
types to modify the method selection for a call.
> t = new OrderedTable() // extends Hashtable
{}
> x = new Cast()
Cast@1dce4700
> x.A(t)
In ordered table
null
> x.A((Hashtable)t)
In hashtable
null
>
When a cast expression is evaluated, the result is
checked against the class to which it is being typecast.
This is done with the isAssignableFrom
in the Class core Java class.
Casting for Element Access
Field Access
Here consider the example that
CastA extends Cast
and that both have a public field
named foo of type String
and assigned values Cast and CastA respectively.
x = CastA();
x.foo // CastA
CastA
((Cast)x).foo //
Cast
This handles different types for shadowed variables.
For example, if we introduce a class CastC
that has a field
int foo = 13;
all behaves "as expected".
getClass()
and the qualifier being a CastExpression.
In this case, the return value is the class to which
the object has been cast.
> x = new CastC();
> ((Cast)x).getClass()
class Cast
> x.getClass().getField("foo")
public int CastC.foo
> ((Cast)x).getClass().getField("foo")
public java.lang.String Cast.foo
Methods
This currently does not work and is unlikely to without
a major change to the JVM.
For methods, dynamic method lookup is done within
the invoke()
method of the Method
class. Thus, the wrong method is actually invoked
and the result is the same as ignoring the cast.
For example, suppose
CastA extends
Cast
and each has its own method named A that prints out the class
in which it is defined (statically, not based on this.getClass())
x = new CastA();
((Cast)x).A()
CastA
We would like this to be Cast.
x.super.foo()((SuperClass)x).foo()x.
Of course, it is important that one does know this,
but it is still inconvenient to type.
Accordingly, one can now use the idiom
((super)x).f
So super is now recognized in a cast expression
as a special word and implies the superclass of the object to which
the expression being cast evaluates.
Class Name Completion
When one refers to a class, we look
through the classpath elements and attempt to find a match. For
example, new Vector() will create an instance of the
class java.lang.Vector (assuming there is no other
match). This essentially replaces the need for import statements with
a more general mechanism. One difficulty is that it is harder to
temporarily specify a different order for searching. This can be done
by permuting the elements in the evaluator's classpath.
Dynamic Class Loading
The class loading in Omega has recently been extended
to support
In the first version of Omega, all classes that were to be used
either directly by the Omega system classes,
or by the user via the interpreter were loaded from the regular
System classloader. This necessitated that the appropriate
directories & jar files be in the classpath
before the Omega process was started.
localClasses()
as follows:
localClasses().add(new File("/home/duncan/Java/extensionDirectory"));
localClasses().add(new File("http://www.omegahat.org/extensions.jar"));
localClasses().add("/home/duncan/Java/extensionDirectory");
localClasses().add(new URL("http://www.omegahat.org/extensions.jar"));
u = new URL("http://tigger/Java-SSH.zip");
localClasses().add(new LocalURLClassList(u,new DeferredEagerURLClassLoader(u)))
l = localClasses().add("http://www.omegahat.org/Java-SSH.zip")
From this point, the usual partially qualified class name
will function by looking in 1) the system
classes, 2) the user classes
and 3) the dynamic classes.
Exactly the same mechanism is used, except that the
classes loaded implicitly by the user
rather than the system are maintained by one or more different
class loaders. This makes them invisible to the Omega
classes!
URLClassLoader) is entirely lazy and
loads the bytes for a class when it is referenced. This incurs the
cost of reestablishing the URL connection for each class and
traversing the list of ZipEntrys until the appropriate
one is found.
EagerURLClassLoader)
allows a single connection to be established and all the
classes contained in the URL to be loaded immediately.
This trades the speed of establishing the connection with the
possibility that most of the classes will never be used and
would not have been loaded.
DeferredEagerURLClassLoader)
changes the tradeoff by loading the bytes
for each class but not actually defining the class.
When the connection is first established to read the list of
classes, the bytes are also retrieved and cached.
When the class is referenced, the bytes are taken from this
local table and converted to a class definition.
The tradeoff here is again loading bytes that may never
be used and also storing these.
This class can be used to store all the byte arrays and
then immediately after initialization the extraneous classes
can be removed from the local byte table.
FunctionActionListener
and
> dynamicClassLoader().defineClass("java.awt.event.ActionListener", "FunctionActionListener")
class FunctionActionListener
> v = new FunctionActionListener(function(x) {x++;})
> btn.addActionListener(v);
Array Initialization
JDK1.1 allows inline initialization of
arrays such as x = new int[]{1,2,3};. The elements in
the initializer must be constants however. We relax this due to our
dynamic evaluation model. One can have arbitrary objects here. So
x = new Integer[]{1,2,x,y,z}; works.
This will be true even in the compiled code. There we can substitute a
call to get("x") for an database object named x
or a subsequent call to x[i] = x; where x is a field in a class
Subsetting
There is now support for some simple subsetting
via an extensible mechanism for newly created classes.
The parser recognizes simple array-like subscripting
with the [ operator and now the [[ operator. In general, the former
returns an object of the same class as the source
and the latter returns discards this class if a single element is
returned.
These work differently from the S/R model in that the dimensions
are in different [ rather than comma-separated.
Both operators support the
Thus to get the first element of an object contained in the second
element of an object x,
we use the expression
x[1][0]x[1][0] = 1Vector,
Hashtable Enumeration. (Assignments are not
done on Enumeration.)
The mechanism is extensible in that new
classes can be created that provide methods implement the subsetting
and the assignment to subsets as is appropriate for the class. To
offer this, a class implements one or either of the interfaces Subsettable and AssignableSubset
(These methods here are selected/invoked in preference to the built-in methods
for Hashtables, Vector, etc.)
get and assign in ArrayAccess in the
Parser.Parse package.
+, -, *, /, etc.
A new mechanism now allows the subsetting to be
performed by functions or compiled utility functions.
When we fail to find an appropriate method for the subsetting,
we search for a function or compiled method in the
internal function list of the evaluator for a method
named subset
which takes 3 arguments:
See SubsetFunctions
for an example of how this works.
true
and false respectively.
Database Element Access
An additional cube of syntactic sugar has been added to
allow simple references to elements of Database
objects. The user can now issue commands such as
db = new ObjectDatabse()
db.x = foo(z)
db.x
The last element of the name on the LHS of the assignment expression
is used as a key in the Database (as a String) and its corresponding element returned or
assigned a value. The retrieval and assignment use the appropriate
methods in the Database so type, read-only,
etc. attributes are handled correctly.
Database
has a publically accessible field of the given name, the last
component of the field access is not considered as a key. Instead, the
field in the object is used and either returned or assigned the new
value. (Of course, public access is not encouraged and should be
provided via accessor functions.) One can explicitly assign an object
to the Database using the assign() method or via the
subsetting operators, rather than this
syntactic shortcut.
"X",
the command
X.y = 1
would be shorthand for
assign("y",1,getDatabase("X"))
FieldAccess can be modified to traverse the search
path of Databases in the event that a
variable is not found.
d = new ObjectDatabase()
d.table = new ObjectDatabase()
d.table.element = 1
d.table.element
d.table
See getField()
in FieldAccess
and setField()
in AssignExpression.
newnew. So x = Vector(3) will call the
appropriate constructor for Vector. It does this if there
is no function object or method that could be called instead. This is
an implicit new.
This does not work for primitive types (int, short, etc.) This is a
feature of the parser and throws a syntax error. Similarly, when
declaring and initializing an array or declaring an array with an
unspecified dimension, one needs to use new
x = String[3]; // works
x = String[3][]; // doesn't work
x = String[]{"a","b","c"}; // doesn't
x = new String[]{"a","b","c"}; // does
The simple statement
x = Vectorassigns the class Vector to x. This avoids the more cumbersome
x = Class.forName("java.lang.Vector")
or
even
findClass("Vector");
Of course, it is ambiguous in that if there
is an object of that name on a database, it will be
returned.
casting to an arbitrary class from which an object
is derived.
this which is the
evaluator. The latter provides access to ``internal'' functions.
In the case of functions, an object of class Function is searched for and if found, it is
evaluated with the arguments in the method call. No lazy evaluation
is used.
This mechanism has been extended to handle method dispatching on Omega functions.
show(i) maps to evaluator.show(i)
i.show() is the way to invoke the method show()
on i.
Additionally, the code is now used to implement the S/R to Java interface. Calls from S/R are made Java objects and classes and arguments are "converted" to Java objects.
A feature has been added that allows promoting an object
to an array in order to match a method.
The object is contained in an array of length 1
and the arrays component type is identified by the matching method.
For example, consider the class
public class foo {
static public int doFoo(String[] x) {
System.err.println("In String[]");
return(x.length);
}
static public int doFoo(int[] x) {
System.err.println("In int[]");
return(x.length);
}
static public int doFoo(int[][] x) {
System.err.println("In doFoo int[][]");
return(x.length);
}
static public int doBar(int[][] x) {
System.err.println("In int[][] " + " " + x[0].length);
return(x.length);
}
}
and the calls
foo.doFoo("my string")
foo.doFoo(1)
foo.doBar(new int[]{1,2})
Ordinarily, none of these would match any of the methods. By
elevating the value to an array of the type specified in the method,
each call resolves to the corresponding entry in the class.
This also works in the same way for constructors. It is is especially useful in the S/R to Java interface where S and R have no actual scalars but vectors of length 1. Thus the automated conversion of such an object is ambiguous. By converting to simple primitives, the Omegahat method dispatching will perform the appropriate promotion to arrays.
Non-public (i.e. explicitly protected/private and "inner") classes provide complications for this dynamic reflected invocation. For example,
> p = Runtime.getRuntime().exec("cat some-file")
> p.getClass()
class java.lang.UNIXProcess
> p.getOutputStream()
class is not public - class java.lang.UNIXProcessEvaluation Error: inaccessible method public java.io.OutputStream java.lang.UNIXProcess.getOutputStream()java.lang.IllegalAccessException: java/lang/UNIXProcess
In Java2, there is a mechanism to temporarily make a reflected object (Field and Method) accessible, in spite of the permissions. For cases like the one above, this is not a security or programming issue. The non-public nature of the class is a feature of the Java implementation, not a design of the class.
The facility is used in the following manner.
Method m = findMethod(.....); m.setAccessible(true); m.invoke(....);
Gaining access to fields for write permission and internal methods which are intended to be called only by other methods of that class or object is a potentially a bad thing. As a result, we wait until there is an access error and only then do we test that the method is public but the class is not. This is not currently implemented for static methods as it is assumed they are either public or not by design.
Casting is now honored.
Suppose we have an instance, named x of class
C
which is derived from a class B
which in turn extends A.
Then, calls of the form
x.foo() ((B)x).foo() ((A)x).foo()will call the appropriate instance of the method
foo()
in the different super-classes.
The implementation of this requires dynamically generating and loading a new Java class. As a result, this might be an expensive operation. In the future, we might cache these classes.
${VARIABLE} with the value in a
Properties table indexed by the String
VARIABLE.
While this is used to expand environment variables in-line,
it can be used for arbitrary purposes with any table.
source() method in the
evaluator and via the class Omega.IO.ArchiveEntry.
Now consider the prompt expression string. This can
be a simple expression that access variables in the databases or in
the evaluator itself. For example, we may choose to emit the task
number as the prompt surround by [] pair.
This might be done (depending on the evaluator class and features) as
"[" + taskList().size() + "]" taskList() is provided by the
evaluator and presumably is a (derivative of) Vector
or Hashtable that
contains the previously. (Alternatively, we might cache the previous
k tasks but keep a running count of the number of total
tasks processed accessible via the method numTasks().)
Alternatively, we may just keep a count of the number of prompts emitted. An expression of the form
_lineNumber++;
"["+lineNumber+"] "_lineNumber) in an effort not to conflict with other
names used during the Omega session. A better way to do this is to
use a Closure to provide
local scope via an anonymous function:
function() { static int count = 0;
return("[" + ++count + "] ");
}
Here, count is local to this function and
we take care to turn this into an evaluable expression.
Users can provide their own signal handlers in the form of Omega expressions or functions for different signals and have these be evaluated when the signal is raised. This is done in the UserLevelSignalHanders class which is a separate Evaluator. Usually, a single instance of this class is created in the startup scripts and populated with the appropriate handlers.
In the near future, we will support specification via named properties of the form
INT: function(sig) { ......}
HUP: rereadInits()
This requires JNI support and so cannot be used
in an applet, but it doesn't make sense there anyway!
Using JNI
1::10, foo(x)::20
0::(x.length-1)
apply()
which takes an Omega function
and applies it to each of the elements in the parse tree
rooted at that expression. This is recursive and currently
returns a java.util.Vector.
In the future, we will support returning trees
with the same structure and other features.
The class of the default lexer is now configured to be read from the
OmegaOptions file. This allows different lexers to be easily
instantiated without recompiling. In fact, setting the value in the
options() object of the evaluator allows us to change the
lexer dynamically.
The primary motivation for having two lexer classes
(omegaJavaLexer and omegaNestedStringLexer)
is to provide different ways of handling escaped strings. The orginal
lexer (omegaJavaLexer) cannot handle escaped strings
without some modification, and more importantly, the user interface
for such nesting must be simple. So, the second and default lexer
omegaJavaLexer now treats the " " and
' ' pairs as mutually-nestable. We still of course
require balanced pairs (so a string cannot start with " and end with '
or vice-versa). Using this, the following works parse(" x=
'abc';") and gives an expression containing the literal string
"abc".
In order to handle this nesting, we have to sacrifice the the Java
syntax for speciying characters - e.g. 'a'. At the moment, I don't
think this is a problem. However, there is a chicken-egg problem in
that we don't use characters much in statistics because we use strings
instead because the languages encourage this.
To use this syntax, set the default lexer property
defaultLexerClass to
omegaJavaLexer.
As mentioned above, this can be done dynamically and temporarily -
for the duration of one command.
For example,
[] x = 'a'
a
[] x.getClass()
class java.lang.String
[] options().put("defaultLexerClass","omegaJavaLexer")
omegaNestedStringLexer
[] x = 'a'
a
[] x.getClass()
class java.lang.Character
[]
The current complicated framework (or maze) of calls to parse a
string in the Evaluator class is intended to
allow us to reuse previously initialized lexers and
parsers. Currently, we have to instantiate a new lexer and parser for
each line of input. If we want to handle split-lines of input and
just be generally more efficient, we have to find a way to convince
See ResettableLexer and the lexer methods
in Environment/Parser/AntlrParser.
parse() method of the evaluator. This
returns a List of expressions. This is a tree of
expressions and can be recursively processed. One such method is
substitute which behaves like its S/R counterparts. It
takes a Hashtable of values indexed by some key and
recursively traverses the children of the tree replacing objects that
"match" the key with the content. The current setup works for
Name objects. The name is converted to a string and a
corresponding entry in the Hashtable is used as a
replacement (directly) for the Name object.
For example,
[1] db = new Hashtable(); db["x"] = parse("foo(2);")[[0]]; p = parse("if(x > 1) { foo(x); }")
if(x > 1) {
foo(x);
}
[2] p.substitute(db)
if(foo(2) > 1) {
foo(foo(2));
}
[3]
This does not currently work correctly in all cases.
This is due to the the tree relationships
needing to be simplified into one model.
//, /* */
and /** */.
At present, these can be specified
only at the statement level.
For example
/** Comment */
for(i=0; i < 3; i++) {
/* another comment */
x = 1;
//
y = foo(x);
}
However,
if(x == 1 // test for something
&& y < 2)
will generate a syntax error. This will change in the future.
Note that these statement-level comments
may not be printed currently. The toString
methods of the different expressions need to
handle these. Note also that each expression class
has two linked lists of comments - pre and post.
3^2
^ symbol in Java is the binary
XOr operator. Currently, the precedence
in Omega is the same as the same symbol in Java.
//.
For example, we can define a function as follows
function foo(x) { //
y = x+1; //
x*(y+x); //
}
The parser will change relatively soon to communicate the incomplete
input to the reader.
foo(x,y)are now resolved in a different manner. Previously, the following steps were used:
foo
foo in
are now resolved in a different mannerevaluator
new foo(x,y)
See Utility Functions for a more complete description and an example session.
Method dispatching doesn't convert Integers, etc. to their
corresponding primitives!
File search mechanism via used defined search path.
FileLocator
class which takes a list of directories
(or Jar files) and searches within these
when a "file" is requested.
Evaluator class currently has
a collection of help() methods which
display the tree of HTML documents using the latest
JHelp package from Sun.
There is little content, but the structure is mapped.
We may generalize this to use XML based documentation which we
can either process into HTML or use directly with specialized
browsers. The latter allows us to offer "live" functionality
in the browser such as interactive plots, drag and drop of
objects onto plots, updating of results, etc.
The JEditorPane in the Swing/JFC package is a well designed
starting point for our customization.
See the new documentation in S as a prototype model.
The Java XML package from Sun provides the necessary tools for
parsing and processing XML documents and trees.
Alternatively, we might use the flexible Doclet approach in JDK1.2
or merge the XML and Doclet approaches.