-
Notifications
You must be signed in to change notification settings - Fork 0
Script Loading
Script loading happens in 3 major steps:
- Parsing
- Composition
- Execution
When a script is loaded, the source has to be converted ('parsed') into a form which is structurally understandable by the composer, loader and compiler.
This process starts with "lexing" (lexical analysis), which converts the string input into an ordered stream of tokens. If I, for example, provide the lexer with the following source string:
// this will be excluded by the lexer
5 * ("hello" - abc)
That will be lexed into a stream of tokens like the following:
literalNumber(5) operator(mul) leftParen literalString(hello) operator(sub) identifier(abc) rightParen
Since this has eleminated whitespace and simplified the string into only the important parts with the important data, it is now much easier to parse into an abstract syntax tree.
An expression consists of a structure of nodes:
5 * ("hello" - abc)
Would result in:
*
/ \
5 -
/ \
"" abc
As you can see, this has determined the structure of the expression, with correct operator
priority.
This can be evaluated as-is for simple expressions (which is used for OneRuntime#evalSimple),
but that is not sufficient for full scripts due to the more complex declarations
and definitions permitted.
Composition is the process of classifying and parsing the parsed script source into a series of definitions and blocks. It will extract class definitions, methods, fields and code blocks.
For example:
/* Will be classified as a script meta statements
Script meta statements are stored in the loaded script. */
package a.b.c;
/* Will also be classified as script meta statements */
import "MyScript.os"; /* Importing scripts imports the script class directly. */
/* Basically the same as:
let script = loadAndRunScript("MyScript.os");
importClass(script.getScriptClass());
*/
import one.runtime.OneRuntime;
/* Classified as root-level code
These will be put in the static initializer of the JVM Script$ class created. */
let: string myVar = "World!";
println("Hello, " + myVar);
/* Classified as a root-level function
These symbols will be put in the JVM Script$ class created as static methods. */
func hello(name as string) string {
return "Hello, " + name + "!";
}
/* Classified as a root-level field
These fields will be put in the JVM Script$ class created as static fields. */
final val: string myRootField = "Test";
/* Classified as a package class declaration
This class will be located at the scripts specified package (a.b.c)
Everything inside is classified as part of the class. */
pub class MyClass {
private static final val: this INSTANCE;
/* this can be used as a type parameter */
pub static func get() this {
return INSTANCE;
}
private myField as string = "Hey Guys!";
pub this(myField as string) {
this.myField = myField;
}
}A pseudo-code Java definition of this script loaded would be:
/* Structure */
a.b.c
- Script$MyScript
- MyClass
// Script$MyScript //
public static void script$run(OneRuntime runtime) {
String myVar = "World!";
one.lang.OneSystem.println("Hello, " + myVar);
}
public static final String myRootField = "Test";
public static String hello(String name) {
return "Hello, " + name + "!";
}
// MyClass //
class MyClass {
public static final MyClass INSTANCE;
public static MyClass get() {
return INSTANCE;
}
public String myField = "Hey Guys!";
public MyClass(String myField) {
this.myField = myField;
}
}Code in OneScript is not interpreted, instead, the code is compiled into JVM bytecode and is run at roughly the same speed as a normal Java program. When a script is loaded, it creates a descriptor of all contents of the script from the composition, but doesn't load any classes. That is an important detail of the OneScript runtime: Classes are loaded on-demand.
The only class it will create and load at the start of a script is the script's main class. This class contains all script-namespace
functions, fields and code. The class is always located at oneclasses.<package>.Script$<scriptName>.
All root level code is put in the script main class' public static void script$run(OneRuntime) method.
All root level symbols like fields and methods are defined as public static in the script main class.
Classes are (by default, you can make them load immediately with @LoadImmediate) loaded and initialized on demand in OneScript, though for the purpose of compilation and lookup all classes are declared immediately. Classes can either be defined in script, in which case we call them "script" or "internal" classes or defined in Java, in which case we call them "native" or "Java" classes. Script classes' JVM counterparts are always under the oneclasses. package. This way the special class loader responsible for loading classes can distinguish between script and native classes.
Pseudo-code of the class loading process:
loadClass:
if jvm class is under oneclasses.:
get class type by jvm name
if class is already compiled and loaded:
return get compiled and loaded class
else:
compile all fields and methods
call the static initializer
return get compiled and loaded class
else:
make parent loader load native class
return native class