A C# and Java interpreter for the Lox programming language by Robert Nystrom. Easily use it as an embedded scripting language for your programs.
Special thanks to Mr. Nystrom, the website for the text this project was based on can be found here: Crafting Interpreters
This implementation of Lox doesn't support many extended features. The language is simple in nature. This implementation supports the following unique implementation differences.
- Support for 'null' along side 'nil'.
- Command line/programmatic support for processing a 'globals' file that contains Lox source code that drives entries into the global environment.
- Command line/programmatic support for processing a 'globals' script string that contains Lox source code that drives entries into the global environment.
- Programmatic support for injecting a list of key, value pairs into the global environment.
- Support for Array, List, and Dict data structures with basic get/set/add/length functionality depending on the data structure.
- Support for multi-line comments with nested multi-line comments.
- Support for a generic, easily extensible function called "sys" that's first argument is a string indicating what code to run. Used as a quick an easy way to integrate the Lox interpreter into an application environment.
- Arbitrary number of function arguments via special handling of an arity value of -1.
- **Support for retrieving the script to execute from a URL.
- **Support for retrieving a globals script to run from a URL.
- **Support for returning a global variable after script execution to a URL.
** With this support Lox can act as a data driven execution subsystem in part of a larger application where the language has been integrated to some extent using globals, "sys", or other customizations.
Victor G. Brusca
Carlo Bruscani
An example of the CLI arguments are as follows:
Usage: java -jar JavaLox.jar or CsLox.exe ([-f script file] | [-s script string] | [-u script url] | [-p REPL]) & [-gf script file] [-gs script string] [-gu script url] [-ru url] [-gv global variable name to respond with]
Where:
-f = Run a Lox script.
-s = Run a Lox program in a string.
-p = Runs a Lox REPL.
-u = Runs a script from a URL, expect a script JSON = { "script":"some Lox code here" }.
-gf = A Lox file script to process, loading globals, before running the intended script.
-gs = A Lox string script to process, loading globals, before running the intended script.
-gu = A Lox URL script to process, loading globals, before running the intended script, expect a script JSON = { "script":"some Lox code here" }.
-ru = A URL to send an execution response message to. Sends form key value pairs as strings: "answer" = "variable value as string" and "variableName" = "name of the global variable used".
-gv = The global variable to return after script execution, objects are converted to a JSON string.
app.MapPost("/getGlobal", () =>
{
return "{ \"script\":\"var urlGlobal = true;\"}";
});
app.MapPost("/getScript", () =>
{
return "{ \"script\":\"urlGlobal = false;\nprint urlGlobal;\"}";
});
app.MapGet("/getGlobal", () =>
{
return "{ \"script\":\"var urlGlobal = true;\"}";
});
app.MapGet("/getScript", () =>
{
return "{ \"script\":\"urlGlobal = false;\nprint urlGlobal;\"}";
});
app.MapGet("/setAnswer", (HttpRequest request) =>
{
return "{ \"msg\":\"Found answer: '" + request.Query["answer"] + ", " + request.Query["variableName"] + "'\" }";
});
app.MapPost("/setAnswer", (HttpRequest request) =>
{
return "{ \"msg\":\"Found answer: '" + request.Form["answer"] + ", " + request.Form["variableName"] + "'\" }";
});
The actual test server code in visual studio.

The HTTP server and port number are dependent on how you have the included visual studio project configured.
CsLox.exe -f "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\test.lox" -gs "var GBL_BASE_NAT_LOG = 2.71828;" -gf "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox" CsLox.exe -u https://localhost:7109/getScript -gs "var GBL_BASE_NAT_LOG = 2.71828;" -gf "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox" -gu https://localhost:7109/getGlobal -ru https://localhost:7109/setAnswer -gv urlGlobal java -jar JavaLox.jar -f "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\test.lox" -gs "var GBL_BASE_NAT_LOG = 2.71828;" -gf "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox" java -jar JavaLox.jar -u https://localhost:7109/getScript -gs "var GBL_BASE_NAT_LOG = 2.71828;" -gf "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox" -gu https://localhost:7109/getGlobal -ru https://localhost:7109/setAnswer -gv urlGlobal
You can test URL functionality by using the CsLoxTestServer project, Visual Studio, and either Lox interpretter. For the Java version you'll have to add a certificate to the JRE by using a command similar to this run as administrator in the current JRE bin dir. I renamed the .pem file downloadable from the browser into a .cer and it worked fine. For the C# version you'll be prompted to add the necessary certificates if you run it in Visual Studio.
Visual Studio prompts you to setup the SSL certificate, but will go on without you and launch an http version of the test server if you take too long to answer.

Once the server is up you can quickly test it with CsLox.eve with no futher configuration.

PEM file download for use with java's keytool. Make sure you install it with the same version of java that you use to run JavaLox.jar.

Rename the certificate if need be.

keytool -import -trustcacerts -alias LOX_LOCAL -file "C:\Users\brusc\Downloads\localhost.pem | .cer" -keystore "C:\Program Files\Java\jdk-21\lib\security\cacerts" -storepass LOX_LOCAL
Running it on a Windows box in Powershell with Administrator privs looks like so. In this case the .cer is a renamed .pem from the Visual Studio CsLoxTestServer project. Make sure you install it with the same version of java that you use to run JavaLox.jar.
&"C:\Program Files\Java\jdk-26\bin\keytool.exe" -import -trustcacerts -alias LOX_LOCAL -file "C:\Users\brusc\Downloads\localhost.cer" -keystore "C:\Program Files\Java\jdk-26\lib\security\cacerts" -storepass LOX_LOCAL
Now you can run the java URL tests against the CsLoxTestServer.

Compare to the HTTP function output above.
run:
[line 0] Log: Found global file to import: C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox
[line 0] Log: Found global script to import: var GBL_BASE_NAT_LOG = 2.71828;
[line 0] Log: Found global script to import: https://localhost:7109/getGlobal
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
HTTP/1.1
200
OK
[line 0] Log: Run URL Response: '{ "script":"var urlGlobal = true;"}'
HTTP/1.1
200
OK
[line 0] Log: Run URL Response: '{ "script":"urlGlobal = false;
print urlGlobal;"}'
false
HTTP/1.1
200
OK
[line 0] Log: Return URL Response: '{ "msg":"Found answer: 'false, urlGlobal'" }'
BUILD SUCCESSFUL (total time: 1 second)
- The C# version of Lox uses Visual Studio.
- The Java version uses NetBeans.
- There are some hardcoded paths in the project configuration for easy testing. You'll have to adjust these to match your environment if you expect to test the interpreter via IDE debugging.
- ** The test file, "test.lox", expects the globals from the file "globals.lox" and the CLI global "GBL_BASE_NAT_LOG" to be injected before the test script is executed. **
The following are added data structures that are simple but useful.
var array = Array(3); print array.length; // Prints out "3". array.set(1, "new"); // Sets the value at array index 1 to "new" print array.get(1); // Prints the value at index 1, "new".
var list = List();
print list.length; // Prints out "0".
list.add("new"); // Adds a new value, "new", into the list.
list.add("old"); // Adds a new value, "old", into the list.
list.set(0, "newer"); // Sets the value at position 0 to "newer"
print list.get(0); // Prints the value at index 0, "newer".
var dct = Dict();
dct.set("hello", "world"); // Sets the key "hello" equal to "world"
dct.set(2, "test"); // Sets the key 2 equal to "test"
print dct.length; // Prints out "2"
print dct.get(2); // Prints out "test"
print dct.get("hello"); // Prints out "world"
To demonstrate the extensibility we'll look at the Java version but the C# version is almost identical.
public Interpreter(HashMap<Object, HandleLoxCallables> handleCalls, HandleLoxGlobals externalGlobals) {
this.handleCalls = handleCalls;
this.externalGlobals = externalGlobals;
initialize();
}
private void initialize() {
environment = globals;
globals.define("clock", new Clock());
globals.define("Array", new Array());
globals.define("List", new LinkedList());
globals.define("Dict", new Dict());
externalFunctions = new ExternalLoxCallable(handleCalls);
globals.define("sys", externalFunctions);
globals.define("GBL_NUM_MIN", Double.MAX_VALUE * -1.0); //-1.7976931348623157E+308
globals.define("GBL_NUM_MAX", Double.MAX_VALUE); // 1.7976931348623157E+308
if (externalGlobals != null) {
externalGlobals.defineGlobals(globals);
}
}
(Java) Creating a sys function handler class to support the following line from the test.lox file.
sys("test", "fake argument");
class TestLoxCallable implements HandleLoxCallables
{
@Override
public Object call(int arity, Interpreter interpreter, List<Object> arguments) {
System.out.println("Custom function handler for sys arg0 of '" + arguments.get(0) + "' with arg1 of '" + arguments.get(1) + "'.");
return null;
}
}
public static HashMap handleCalls = new HashMap (); private static final Interpreter interpreter = new Interpreter(handleCalls);
handleCalls.put("test", new TestLoxCallable());
(C#) Creating a sys function handler class to support the following line from the test.lox file:
sys("test", "fake argument");
class TestLoxCallable : HandleLoxCallables
{
object HandleLoxCallables.call(int arity, Interpreter interpreter, List<object> arguments)
{
System.Console.Out.WriteLine("Custom function handler for sys arg0 of '" + arguments[0] + "' with arg1 of '" + arguments[1] + "'.");
return null;
}
}
private static Dictionary handleCalls = new Dictionary (); private static readonly Interpreter interpreter = new Interpreter(handleCalls);
handleCalls.Add("test", new TestLoxCallable());
The output from both of the examples above is as follows:
PS C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\JavaLox\dist> Java -jar JavaLox.jar -f "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\test.lox" -gs "var GBL_BASE_NAT_LOG = 2.71828;" -gf "C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox" [line 0] Log: Found global file to import: C:\FILES\OIT_LAPTOP_BACKUP\DOCUMENTS\GitHub\LoxLang\globals.lox [line 0] Log: Found global script to import: var GBL_BASE_NAT_LOG = 2.71828; 2.71828 Custom function handler for sys arg0 of 'test' with arg1 of 'fake argument'. "inner a" "outer b" "global c" "outer a" ...
Starting with the external globals, if defined the "defineGlobals" method is called and passed a reference to the globals environment.
public void defineGlobals(Environment loxGlobals) {
if (externalGlobals != null) {
for (String key : externalGlobals.keySet()) {
loxGlobals.define(key, externalGlobals.get(key));
}
}
}
If an instance of the "ExternalLoxGlobal" class is provided to the Interpreter class at instantiation the extra globals are injected into the global environment.
externalFunctions = new ExternalLoxCallable(handleCalls);
globals.define("sys", externalFunctions);
If a HashMap of - function name, LoxCallable pairs - are provided the Interpreter's constructor then you can call a "sys" function with any number of arguments that gets handled like so.
public Object call(Interpreter interpreter, List<Object> arguments) {
if (handleCalls != null && arguments.size() >= 1 && handleCalls.containsKey(arguments.get(0))) {
HandleLoxCallables handleCall = handleCalls.get(arguments.get(0));
return handleCall.call(arguments.size() - 1, interpreter, arguments);
}
return null;
}
The first argument is the name of the "sys" function to run. The entire set of arguments is then passed onto the "HandleLoxCallables" instance for the given string and with an arity of -1. Note the first argument defining what subfunction to run is not removed from the arguments even though the arity is the argument length minus one.
interface HandleLoxCallables {
public Object call(int arity, Interpreter interpreter, List<Object> arguments);
}
You can use this to quickly extend the "sys" function by adding new code to the class without having to define a more complex structure of classes.


