(c) 2001-2023 Guglielmo Nigri
(guglielmonigri at yahoo.it
, googlielmo at gmail.com
)
Gleam comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions; see LICENSE.TXT.
Gleam is a Scheme language interpreter written in Java.
Project goals: to support the discovery of Scheme for beginners, simplicity, R5RS compliance, full integration with the Java platform (call Java from Scheme and vice versa).
- How to build and run Gleam
- Project history
- Programming in Gleam Scheme
- Using Java from Scheme
- Using Scheme from Java
- Changes
- Contributing
- Code of Conduct
- GNU General Public License Version 2
- R5RS implementation status (may be out of date)
- Backlog / TODO list (may be out of date)
See BUILD.md.
I started this project in 2001 while looking for a Lisp interpreter to add scripting capabilities to Java programs.
I learned Lisp (and liked it a lot) during my university days in Pisa, Italy, and I thought I could augment Java with Lisp. It was around that time that I discovered Scheme, and I was instantly fascinated by the simplicity and elegance of the language. I found out that there were already some good Java implementations of Scheme out there, but, you know, I have to try my hand at something to grok it! :-)
So I started my simple, slow implementation of an interpreter, always looking at the R5RS document for reference. One of the first things that I was curious to implement was first-class continuations. I wondered if there was a simple way to do those in Java. In fact, this being an interpreter, it was relatively simple to implement them. For a compiler, it would be trickier. We'll see in version 2 ;-)
First, a word of caveat! Please note that Gleam is still being developed and does not yet fully support the Scheme language as defined in R5RS. This means that some key features, such as support for strings and arrays, are not yet fully implemented, and hygienic macros are still in their early stages of development. However, you can invoke Java from Scheme to work around some of these limitations (see below). Although it is possible to write non-trivial programs with Gleam today, it can be a significant undertaking.
If you're interested in Gleam and have the time to invest, you can also contribute to its development! Check out CONTRIBUTING.md and the next sections to learn how.
Gleam provides a convenient way to create new Java objects and call methods on
existing ones from within Scheme. To create a new Java object, use the new
function with a fully-qualified class name as the first argument, followed by
the constructor arguments (if any).
For example, to create a new java.util.Date
object, you can use the following
code:
(define now (new 'java.util.Date))
To call a method on an existing Java object, use the call
function with the
method name and the object instance as arguments, followed by the method
arguments (if any).
Here's an example that demonstrates how to call the toString
method on
the now
object created earlier:
(call 'toString now) ;; "Sun Feb 12 20:26:44 CET 2023"
These features can help you tap into a wide range of libraries and tools available on the JVM. Here are some more examples:
; Create a new object of the java.awt.Point class
(define p (new 'java.awt.Point 1 2))
; Call the setLocation method on the object, passing in two arguments
(call 'translate p 10 -5)
; Call the getLocation method on the object to retrieve its coordinates
(call 'getLocation p) ;; java.awt.Point[x=11,y=-3]
By using these functions, you can seamlessly integrate Java and Scheme code, allowing you to take advantage of both languages' strengths.
Interacting with Gleam from Java is simple and can be done in two ways. You can
use an Interpreter
instance directly or leverage the Java Scripting API
defined by JSR 223. In the next sections, we'll take a quick look at both of
these options.
To execute Scheme code, you need an Interpreter instance. You can create one with:
Interpreter intp = Interpreter.newInterpreter();
You can create as many interpreters as needed, each with a separate internal status.
If desired, you can also set up a global environment that will be shared among multiple interpreter instances. Check out the chapter below, "Using the Java Scripting API," for further details.
Several flavors of eval()
let you evaluate Scheme expressions.
intp.eval("(define x 1.2345)");
Entity res = intp.eval("x");
boolean eq = 1.2345 == ((Number) res).doubleValue(); // true
All Scheme objects implement the Entity
interface. Additionally, numerical
data extends the standard Java Number
class.
Calling toString()
on an Entity
returns a string in a format identical to
what you'd expect when using the Scheme display
procedure:
String s = intp.eval("(cdr (list 1 2 3 4))").toString(); // (2 3 4)
To get a string in the same format as used by write
, use the toWriteFormat()
method:
String d = intp.eval("#\\space").toString(); // (one space)
String w = intp.eval("#\\space").toWriteFormat(); // #\space
By default, interactive code, or code submitted to eval
, is executed in the
session environment, which includes an execution context that holds the default
I/O ports (i.e., in, out, and error character streams) along with some
special properties. One such property is the noisy
flag, which, when set to
true, causes the result of each top-level expression to be displayed after
evaluation.
Creating an interactive REPL is simple and requires just a few lines of code to
evaluate expressions from a java.io.Reader
:
ExecutionContext context = intp.getSessionEnv().getExecutionContext();
context.setNoisy(true);
context.getOut().printf("Welcome to Gleam Scheme!\nSend EOF (^D) to quit\n");
Reader reader = context.getIn().getReader();
while (true) {
try {
intp.eval(reader);
break;
}
catch (GleamException e) {
context.getOut().printf("Error: %s\n", e.getMessage());
}
}
For your convenience, you can import the static methods defined in
the Entities
class, such as cons
, nil
, and list
, to create and
manipulate Scheme values more easily. As an example, let's look at a couple of
different ways for constructing a list with three elements: a multiplication
symbol, a real number, and an integer:
// with cons() and nil()
List list1 = cons(symbol("*"),
cons(real(1.234),
cons(integer(2), nil()))); // (* 1.234 2)
// with list()
List list2 = list(symbol("*"), real(1.234), integer(2)); // (* 1.234 2)
The resulting lists are identical, and when evaluated with eval()
, they would
yield a value of 2.468
.
To use the JSR 223 Java Scripting API, ensure that you have gleam-$VERSION.jar
in your classpath, then include the following code in your program:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("gleam");
This creates a ScriptEngine
that allows you to execute Gleam Scheme code.
You can evaluate a Scheme snippet from a string with eval
, like this:
Object value = engine.eval("(+ 2 40)");
double asDouble = ((Number) value).doubleValue(); // 42.0
Entities returned from Scheme are converted, if necessary, to plain Java objects
such as String
, Integer
, or Double
instances. Pair
and EmptyList
values are converted to the List
interface. Void or undefined values are
returned as null
.
The scopes that bind string names to Java objects are represented by
the Bindings
interface, which is a special kind of Map<String, Object>.
Each engine executes code within a specific ScriptContext
, which includes the
engine Bindings and the default I/O character streams.
Engines can access both a local engine scope (local to each engine) and a global one (shared by engines created by the same engine manager).
You can directly manipulate engine scope attributes (i.e., variables)
with put()
and get()
methods like this:
engine.put("attr", 40);
Object value = engine.eval("(+ 2 attr)"); // 42
You can also get or set attributes via the engine context, which is useful when you want to manipulate the global scope, for example:
engine.getContext().setAttribute("global", 30, GLOBAL_SCOPE);
engine.getContext().setAttribute("local", 12, ENGINE_SCOPE);
Object value = engine.eval("(+ global local)"); // 42
Implementing a minimal REPL using the Java Scripting API requires only a few lines of code:
ScriptContext context = engine.getContext();
context.setAttribute(CONTEXT_ATTR_NOISY, true, ENGINE_SCOPE);
PrintWriter writer = new PrintWriter(context.getWriter(), true);
writer.printf("Welcome to Gleam Scheme!\nSend EOF (^D) to quit\n");
Reader reader = context.getReader();
while (true) {
try {
engine.eval(reader);
break;
}
catch (ScriptException e) {
writer.printf(String.format("Error: %s\n", e.getMessage()));
}
}
You can set the noisy
and traceEnabled
properties in the
underlying ExecutionContext
with the special attributes CONTEXT_ATTR_NOISY
and CONTEXT_ATTR_TRACE_ENABLED
.