Advanced: Compiled Scripts
Relevant source files
The following files were used as context for generating this wiki page:Jurassic is a JavaScript compiler, not an interpreter. It transforms JavaScript source code into .NET bytecode (CIL) for high-performance execution. The CompiledScript class is a key component in this architecture, representing the result of the compilation process. It encapsulates a compiled delegate that can be executed multiple times without the overhead of re-parsing and re-compiling the source code.
This compilation-centric approach provides significant performance benefits, especially for scripts that are executed frequently. While the ScriptEngine provides simple Evaluate() and Execute() methods for convenience, understanding and using CompiledScript directly allows for more advanced and optimized integration of JavaScript into a .NET application.
The Compilation and Execution Flow
The core process involves taking a ScriptSource, compiling it into a GeneratedMethod, and then executing it. The ScriptEngine orchestrates this process.
High-Level Process
The following diagram illustrates the journey from raw JavaScript code to an executable result within the Jurassic engine.
This diagram shows the compilation pipeline, starting from source code and ending with an executable CompiledScript.
Sources: Jurassic/Core/ScriptEngine.cs:810-835, Jurassic/Compiler/MethodGenerator/MethodGenerator.cs:122-140
Key Components
The compilation process involves several key classes that work together.
| Class | Description | Relevant File |
|---|---|---|
ScriptEngine | The main entry point for all scripting operations. It manages state and orchestrates the compilation and execution process. | Jurassic/Core/ScriptEngine.cs |
CompiledScript | Represents the result of a compilation. It holds a delegate to the generated .NET CIL, ready for execution. | Jurassic/Core/CompiledScript.cs |
MethodGenerator | An abstract base class that represents a unit of compilation. It manages parsing, optimization, and IL generation. | Jurassic/Compiler/MethodGenerator/MethodGenerator.cs |
GeneratedMethod | A container for the compiled delegate and its dependencies (like nested functions). | Jurassic/Compiler/MethodGenerator/MethodGenerator.cs:49-56 |
OptimizationInfo | A class that holds information for the code generator, including the AST, strict mode status, and optimization hints. | Jurassic/Compiler/Emit/OptimizationInfo.cs |
Sources: Jurassic/Core/ScriptEngine.cs, Jurassic/Core/CompiledScript.cs, Jurassic/Compiler/MethodGenerator/MethodGenerator.cs, Jurassic/Compiler/Emit/OptimizationInfo.cs
ScriptEngine: The Orchestrator
The ScriptEngine class is the primary interface for interacting with the Jurassic runtime. It provides methods to compile and execute scripts.
Compilation and Execution Methods
| Method | Description |
|---|---|
Evaluate(ScriptSource source) | Compiles and immediately executes the given script source, returning the result of the last executed statement. |
Execute(ScriptSource source) | Compiles and executes the script but does not return a value. |
Compile(ScriptSource source) | [Obsolete] Compiles the script and returns a CompiledScript instance for later execution. The documentation suggests this is the older way to compile. |
Sources: Jurassic/Core/ScriptEngine.cs:853-879, Jurassic/Core/ScriptEngine.cs:958-973, Jurassic/Core/ScriptEngine.cs:810-835
Compilation Sequence
When a method like Evaluate is called, the ScriptEngine initiates a sequence of events to process the code.
The following diagram shows the internal sequence for a call to ScriptEngine.Evaluate().
Compiler Options
The ScriptEngine can be configured with several options that affect compilation. These are passed to the MethodGenerator within a CompilerOptions object.
| Property | Type | Description |
|---|---|---|
ForceStrictMode | bool | Forces the script to be compiled in ECMAScript 5 strict mode. |
CompatibilityMode | CompatibilityMode | Sets the compatibility mode (e.g., ECMAScript3 or ECMAScript5). |
EnableILAnalysis | bool | If true, the generated IL is disassembled and stored for analysis. |
Sources: Jurassic/Core/ScriptEngine.cs:991-999, Jurassic/Core/ScriptEngine.cs:272-276, Jurassic/Core/ScriptEngine.cs:291-295
The CompiledScript Class
The CompiledScript class is a lightweight wrapper around the GeneratedMethod that results from compilation. Its primary purpose is to allow a pre-compiled script to be executed multiple times efficiently.
Class Structure
This diagram shows that CompiledScript contains a GeneratedMethod, which in turn holds the actual compiled delegate.
Sources: Jurassic/Core/CompiledScript.cs:17-27, Jurassic/Compiler/MethodGenerator/MethodGenerator.cs:49-56
Usage
While ScriptEngine.Compile() is obsolete, the modern approach would involve a static method on CompiledScript itself (though not explicitly shown in the provided files, this is the standard pattern to replace an obsolete engine-level method). Once a CompiledScript object is obtained, it can be run by calling its Execute method.
// This sample shows how the engine is used for one-off execution.
// A similar pattern would apply to using a pre-compiled script.
var engine = new ScriptEngine();
engine.ExecuteFile(@"../../../Core/Sample Files/execute1.js");
Sources: Unit Tests/Core/DocumentationSamples.cs:29-33
The Execute method of CompiledScript simply invokes the underlying compiled delegate.
// Jurassic/Core/CompiledScript.cs
public void Execute(ScriptEngine engine)
{
// Execute the compiled delegate.
this.compiledMethod.GeneratedDelegate.DynamicInvoke(ExecutionContext.CreateGlobal(engine));
// Execute any pending callbacks.
engine.ExecutePostExecuteSteps();
}
Sources: Jurassic/Core/CompiledScript.cs:36-47
MethodGenerator and Code Generation
MethodGenerator is the abstract heart of the compilation process. It defines the pipeline for turning source code into an executable method.
Key Responsibilities
- Parsing: The
Parse()method is responsible for tokenizing the source code and building an Abstract Syntax Tree (AST). - Optimization: The
Optimize()method traverses the AST to apply performance optimizations. - Code Generation: The
GenerateCode()method walks the optimized AST and emits .NET CIL using anILGenerator. It creates aDynamicMethodand produces aGeneratedMethodcontaining the delegate.
Sources: Jurassic/Compiler/MethodGenerator/MethodGenerator.cs:116-140
The GenerateCode method in MethodGenerator sets up an OptimizationInfo object, which carries context for the code generation process, such as strict mode status, the source file, and the function name for stack traces.
Sources: Jurassic/Compiler/MethodGenerator/MethodGenerator.cs:128-135, Jurassic/Compiler/Emit/OptimizationInfo.cs:28-44
Dynamic Execution with eval
In addition to pre-compiling scripts, Jurassic supports dynamic code execution via the eval function. This uses a similar, but distinct, compilation path.
The global eval function in Jurassic does not return a CompiledScript. Instead, it compiles and executes the code within the current scope.
// Jurassic/Library/GlobalObject.cs
[JSInternalFunction(Name = "eval", Flags = JSFunctionFlags.HasEngineParameter)]
public static object Eval(ScriptEngine engine, object code)
{
if (TypeUtilities.IsString(code) == false)
return code;
return engine.Eval(TypeConverter.ToString(code), RuntimeScope.CreateGlobalScope(engine), engine.Global, false);
}
Sources: Jurassic/Library/GlobalObject.cs:157-166
The internal engine.Eval method uses a GlobalOrEvalMethodGenerator with the context set to Eval. This ensures the code is compiled with access to the local scope, which is a key difference from scripts compiled in the global scope.
Sources: Jurassic/Core/ScriptEngine.cs:1222-1256
Summary
The Jurassic engine's core strength lies in its ability to compile JavaScript to .NET CIL, providing high performance. The CompiledScript class is the artifact of this process, enabling developers to compile a script once and run it many times, avoiding the overhead of parsing and code generation on subsequent executions. This is ideal for performance-critical applications where the same JavaScript logic is invoked repeatedly. The ScriptEngine provides a simple API to manage this compilation and execution lifecycle, from one-off Evaluate calls to more advanced pre-compilation workflows.