Executing JavaScript Code
Relevant source files
The following files were used as context for generating this wiki page:Jurassic is a .NET library that compiles and executes JavaScript code. It is not an interpreter; it compiles JavaScript into .NET bytecode (CIL) for high performance. The primary entry point for all script execution is the Jurassic.ScriptEngine class, which provides the environment and methods necessary to run scripts.
Execution can be performed in two main ways: direct evaluation of source code for one-off tasks, or pre-compilation into a CompiledScript object for scripts that need to be executed multiple times. The engine also provides a rich set of interoperability features, allowing .NET code to set and get global JavaScript variables, and call or expose functions.
Sources: README.md, Jurassic/Core/ScriptEngine.cs
The ScriptEngine Class
The ScriptEngine class represents a complete JavaScript runtime environment. Instantiating it sets up the entire execution context, including all built-in objects and prototypes required by the ECMAScript standard.
Sources: Jurassic/Core/ScriptEngine.cs
Initialization
When a ScriptEngine is created, it performs a series of initialization steps to build the global environment. This includes creating fundamental objects like Object, Function, Array, String, and error types, as well as their respective prototypes.
The diagram below illustrates the high-level initialization process of the ScriptEngine.
Sources: Jurassic/Core/ScriptEngine.cs:81-220
Configuration
The ScriptEngine offers several properties to control its behavior:
| Property | Type | Description |
|---|---|---|
ForceStrictMode | bool | Forces all executed code to run in ECMAScript 5 strict mode, even without a "use strict" directive. |
CompatibilityMode | CompatibilityMode | Sets the engine to ECMAScript3 or ECMAScript5 compatibility mode. This affects behaviors like the writability of Infinity, NaN, and undefined. |
EnableILAnalysis | bool | If true, the engine disassembles the generated IL and stores it in the DisassembledIL property of CompiledScript or UserDefinedFunction. |
RecursionDepthLimit | int | Sets a limit on the recursion depth for user-defined functions to prevent stack overflow exceptions. A value of 0 (default) means no limit. |
Sources: Jurassic/Core/ScriptEngine.cs:231-270
The Execution Pipeline
Jurassic is a compiler, not an interpreter. When code is executed, it passes through a multi-stage pipeline that transforms the JavaScript source into executable .NET Intermediate Language (IL).
This diagram shows the journey of JavaScript code from a source string to execution.
- Parsing: The
Parserclass consumes tokens from theLexerand builds an Abstract Syntax Tree (AST) representing the code's structure. - Optimization: The AST is traversed and optimized.
- Code Generation: The
GlobalOrEvalMethodGeneratorwalks the AST and emits .NET IL bytecode. This is the core compilation step. - Execution: The generated IL is packaged into a
GeneratedMethoddelegate, which is then invoked to run the code.
Sources: Jurassic/Core/ScriptEngine.cs:850-867, Jurassic/Compiler/Parser/Parser.cs, Jurassic/Compiler/MethodGenerator/MethodGenerator.cs:113-143, README.md
Core Execution Methods
The ScriptEngine provides several methods for direct execution of JavaScript.
Evaluate()
The Evaluate() method executes a script and returns the value of the last evaluated expression. It has overloads that accept a string or a ScriptSource object. A generic version, Evaluate<T>(), is also available to automatically convert the result to a specified .NET type.
// From Unit Tests/Core/DocumentationSamples.cs
var engine = new ScriptEngine();
Assert.AreEqual(52, engine.Evaluate("5 * 10 + 2"));
Assert.AreEqual(3, engine.Evaluate<int>("1.5 + 2.4"));
Sources: Jurassic/Core/ScriptEngine.cs:835-885, Unit Tests/Core/DocumentationSamples.cs:15-25
Execute() and ExecuteFile()
The Execute() method runs a script but does not return a value. It is suitable for scripts that perform actions but do not produce a final result, such as defining functions or modifying state.
The ExecuteFile(string path) method is a convenient wrapper for executing a script directly from a file.
// From Unit Tests/Real-world/RealWorldTests.cs
var engine = new ScriptEngine();
engine.ExecuteFile(@"../../../Real-world/Files/showdown.js");
engine.Execute("var converter = new Showdown.converter()");
Sources: Jurassic/Core/ScriptEngine.cs:912-966, Unit Tests/Real-world/RealWorldTests.cs:15-18
Pre-compilation with CompiledScript
For scripts that are executed multiple times, pre-compiling them can provide a significant performance benefit by avoiding the repeated overhead of parsing and IL generation. The CompiledScript class encapsulates a script that has already been through the compilation pipeline.
Compilation and Execution Flow
The following diagram contrasts direct evaluation with the pre-compilation approach.
Sources: Jurassic/Core/CompiledScript.cs
Usage
To use pre-compilation, first call the static CompiledScript.Compile() method. This returns a CompiledScript instance. You can then call the Execute() method on this instance as many times as needed, passing in the ScriptEngine to run it on.
// Compiles the script.
var compiledScript = CompiledScript.Compile(new StringScriptSource("x * 2"));
// Executes the compiled script.
var result = engine.Evaluate(compiledScript);
Sources: Jurassic/Core/CompiledScript.cs:25-42, Jurassic/Core/CompiledScript.cs:64-79
Interacting with the Global Scope
The ScriptEngine provides a powerful bridge between the .NET environment and the JavaScript global scope.
Setting and Getting Global Variables
You can manipulate global JavaScript variables from .NET using SetGlobalValue() and GetGlobalValue(). Jurassic handles the conversion of common .NET types (like int, double, bool, string) to their JavaScript equivalents.
// From Unit Tests/Core/DocumentationSamples.cs
var engine = new ScriptEngine();
engine.SetGlobalValue("interop", 15);
engine.Execute("interop += 5");
Assert.AreEqual(20, engine.GetGlobalValue<int>("interop"));
Sources: Jurassic/Core/ScriptEngine.cs:1028-1111, Unit Tests/Core/DocumentationSamples.cs:31-35
Exposing and Calling Functions
.NET methods can be exposed to the JavaScript environment using SetGlobalFunction(), which accepts a .NET Delegate. Conversely, a JavaScript function can be invoked from .NET using CallGlobalFunction().
// Exposing a .NET function to JavaScript
var engine = new ScriptEngine();
engine.SetGlobalFunction("add", new Func<int, int, int>((a, b) => a + b));
Assert.AreEqual(11, engine.Evaluate<int>("add(5, 6)"));
// Calling a JavaScript function from .NET
engine.Evaluate("function subtract(a, b) { return a - b }");
Assert.AreEqual(4, engine.CallGlobalFunction<int>("subtract", 10, 6));
Sources: Jurassic/Core/ScriptEngine.cs:1113-1150, Unit Tests/Core/DocumentationSamples.cs:43-55
The eval() Function
Jurassic supports the standard JavaScript eval() function for dynamic code execution. When eval() is called within a script, it triggers an internal execution pipeline.
- The call is intercepted by
GlobalObject.Eval(). - This delegates to a private
ScriptEngine.Eval(string code, RuntimeScope scope, object thisObject, bool strictMode)method. - This internal method creates a
GlobalOrEvalMethodGeneratorwith theEvalcontext. - The code string is then parsed, compiled, and executed within the provided
scopeandthisObjectcontext.
This ensures that eval executes with the correct lexical scope and context as defined by the ECMAScript specification.
Sources: Jurassic/Library/GlobalObject.cs:181-200, Jurassic/Core/ScriptEngine.cs:1288-1320
Conclusion
The Jurassic engine provides a robust and high-performance mechanism for executing JavaScript code within a .NET application. Its core ScriptEngine class serves as the central hub for managing the runtime environment, offering flexible execution strategies through direct evaluation or pre-compilation via CompiledScript. By compiling JavaScript to .NET IL, Jurassic achieves performance superior to that of traditional interpreters. Furthermore, its comprehensive API for .NET interoperability allows for seamless integration and data exchange between the two environments.