Skip to content

Lluc24/miniJ

Repository files navigation

miniJ

An interpreter for the array language G — a simplified dialect of J (derived from APL) — built from scratch with Python, ANTLR and NumPy.

J logo

Python ANTLR4 NumPy Grade 10/10


Overview

miniJ is a complete interpreter for G, a functional array-programming language modelled on J (the ASCII successor of APL, whose creator Kenneth Iverson won the Turing Award). Like APL/J, G operates on whole vectors at once, evaluates expressions right-to-left, and lets you build new functions by composing existing ones.

I implemented the full language pipeline end to end — lexing → parsing → AST construction → semantic analysis → evaluation — designing the grammar, the tree-walking interpreter and the execution engine myself.

parell =: 0 = ] @: 2 | ]   NB. define "is even" by composing functions
parell i. 6                NB. result: 1 0 1 0 1 0  (0..5, 1 where even)

🎓 Built for the Languages & Programming Paradigms (LP) course of the Bachelor's Degree in Informatics Engineering at UPC – Universitat Politècnica de Catalunya (2024–2025). Final grade: 10/10.

Why this project is worth a look

  • A real language front-end, not a toy calculator. Tokenizer, an LL(*) grammar, AST, symbol tables and an evaluator — the same architecture used by production interpreters.
  • Clean, layered architecture. Parsing, tree traversal and numeric execution live in three decoupled modules, so the numeric backend (NumPy) could be swapped without touching the interpreter logic.
  • Non-trivial language features. First-class user-defined functions, function composition (@:), right-to-left evaluation, scalar/vector broadcasting, and lazy evaluation of function bodies.
  • Robust error handling. Lexical, syntactic and semantic errors are caught and reported clearly instead of crashing — including length-mismatch checks implemented via Python closures.
  • Tested. Six curated, reproducible test suites with golden .out files and a one-command runner.

What the language can do

Category Operators / features
Arithmetic + - * % (divide) ^ (power) | (modulo)
Relational / boolean > < >= <= = <>
List manipulation , (concat) # (length / filter) { (index) i. (iota)
Adverbs ~ (reflex), / (fold/insert), : (variants like *: square)
Variables & functions =: assignment, user-defined functions, composition with @:
Evaluation right-to-left, scalar↔vector broadcasting, lazy function bodies
1 + 1 2 3        NB. 2 3 4      — scalar broadcasts over a vector
5 * 2 + 3        NB. 25         — right-to-left evaluation
+/ 1 2 3         NB. 6          — fold (+) over the list
1 0 1 0 # 1 2 3 4  NB. 1 3      — boolean filter
square =: *:
square 1 + i. 3  NB. 1 4 9      — compose iota, increment and squaring

Architecture

                ┌───────────────┐
   source .j ──▶│   g.py        │  entry point: lexing + parsing (ANTLR)
                │  (driver)     │  reports lexical/syntactic errors
                └──────┬────────┘
                       │ AST
                ┌──────▼────────┐
                │  visitor.py   │  AST visitor: symbol tables (vars & funcs),
                │ (interpreter) │  operand stack, lazy function evaluation
                └──────┬────────┘
                       │ operations
                ┌──────▼────────┐
                │  motor_g.py   │  execution engine: NumPy-backed vector ops,
                │  (engine)     │  encode/decode J literals, semantic checks
                └───────────────┘
File Responsibility
g.g4 ANTLR grammar for G (right-associative expressions, functions, operators as context-sensitive rules)
g.py Driver / entry point: runs lexical & syntactic analysis, builds the AST, delegates evaluation
visitor.py Tree-walking interpreter: symbol tables, an operand stack, and lazy evaluation of function bodies
motor_g.py Execution engine encapsulating all numeric logic on NumPy vectors and semantic validation

Selected design decisions

  • Decoupled engine. Interpreter logic (visitor.py) is separated from numeric operations (motor_g.py), improving maintainability and making the numeric backend replaceable.
  • Stack-based evaluation. Operands live on an explicit stack, which makes multi-valued operands and user functions uniform and easy to extend.
  • Lazy functions. Function definitions store their AST rather than a result, so the body is evaluated only when an argument arrives — naturally yielding lazy evaluation.
  • Closures for safety checks. Binary operators that require matching operand lengths return a closure that validates sizes at call time, when the operands are finally known.

Quick start

Requires Python 3.10, NumPy and ANTLR4.

# 1. set up an environment
python -m venv env && source env/bin/activate
pip install -r requirements.txt   # numpy, antlr4-tools, antlr4-python3-runtime

# 2. generate the parser from the grammar
make

# 3. run the interpreter on a G program
python g.py tests/exemples.j

Running the tests

The repository ships with six reproducible test suites (arithmetic, boolean/relational, functions & variables, the assignment examples, and lexical/syntactic & semantic errors), each paired with a golden .out file:

./run-tests.sh

The script runs every tests/*.j program and diffs the output against its expected result — no differences means everything passes.

Repository layout

g.g4            ANTLR grammar for the G language
g.py            interpreter entry point (lexing + parsing)
visitor.py      AST visitor / tree-walking interpreter
motor_g.py      NumPy-backed execution engine
Makefile        generates the ANTLR parser
run-tests.sh    runs all test suites against golden outputs
tests/          .j programs paired with expected .out files
enunciat.md     original assignment statement (Catalan)
entrega.zip     the exact archive submitted for evaluation

Credits & notes

  • Course: GEI-LP, UPC (2024–2025 Q2). Evaluated by Prof. Edelmira Pasarellagrade 10/10.
  • Original assignment: gebakx/lp-mini-j (Catalan).
  • 📄 The original project documentation, written in Catalan, is preserved in README.ca.md.