A Java interpreter for the Intermediate Student Language (with lambda abstraction).
This is our final project for the course Praktische Informatik 2 (OOP) at the University of Tübingen.
There are four execution modes:
- Run a source file (default)
- Inspect the lexer output of a given source file
- Inspect the parser output of a given source file
- Run an interactive REPL
If you got a JAR executable you can use the following commands:
java -jar jisl.jar FILENAME # to run
java -jar jisl.jar inspect-lexing FILENAME
java -jar jisl.jar inspect-parsing FILENAME
java -jar jisl.jar repl
This project is built with Maven.
There is a POSIX wrapper script to run the program directly.
Running the project with IntelliJ is also easily possible,
either by using the Maven functionality or by manually running de.rbuurman.jisl.Main
.
./jisl-run ARGS # to run the project
mvn test # to run the test suite
mvn clean package # to generate a JAR executable
- Lexer
- Parser
- Basic Evaluation
- REPL
- Simple library support (+ nested)
- Local definitions
- Source position of ProgramElements
- Lists
- Symbols/Atoms
- Structures
- Write a comments & tests (always room for improvement)
- Important builtins + stdlib
- Samples
- Correct error messages
- Better REPL (using JLine)
-
let
,let*
andletrec
- images (never)
- signatures (maybe)
- quoted/quasiquoted (maybe)
- complex/inexact numbers (probably never)
Our (as the standard) interpreter has three basic equality comparators:
eq?
, equal?
and =
.
eq?
matches the exact same object.
equal?
matches structurally same objects.
=
matches equal numbers.
This example illustrates the subtle difference between eq?
and equal?
:
(define A (list 1 2))
(eq? A A) ; evaluates to #true
(eq? (list 1 2) (list 1 2)) ; evaluates to #false
(equal? (list 1 2) (list 1 2)) ; evaluates to #true
Two lambdas are equal if they have the same definition. So they must share the same argument names and also the same expression in the exact same form to be equal.
We handle functions a little different than the default interpreter. A function is just a name for a lambda.
Therefore, equality between functions may act a little weird.
(define (foo x y) (+ x y))
(define (bar x y) (+ x y))
(define (baz a b) (+ a b))
(equal? foo bar) ; evaluates to #true
(equal? foo baz) ; evaluates to #false
There are builtin "lazy procedures" like and and
, or
and if
.
For example, this won't fail although (modulo 0 0)
on its own would:
(and #false (modulo 0 0))
Interestingly, normal lambdas do not evaluate lazily, this will in fact fail (like with the standard intepreter):
(define myAnd (lambda (x y) (and x y)))
(myAnd #false (modulo 0 0))
In our implementation a lambda/function definition is not semantically checked, at all.
(define (foo x y) (* BAR BAZ)) ; this does not throw an error
The standard implementation would throw an error because neither BAR
nor BAZ
are defined.
At the time, we compare structs based on their names. With local definitions (that allow shadowing) this leads to problems.
(define-struct foo (a b))
(foo? (local [(define-struct foo (c))] (make-foo 1)))
; evaluates to #true but arguably should be #false
There is syntax that we handle as normal builtin applicables/procedures where the standard implementation does not.
(procedure? if) ; we return #true, standard interpreter #false