MinJ

MinJ Language

MinJ, short for minimalistic Java, is a lightweight interpreter designed to bring a concise, statically typed scripting language to the JVM. Built from the ground up in Java with ANTLRv4 for lexical and syntactic analysis, MinJ provides an accessible platform for experimenting with language design and interpreter implementation. Its clean grammar and modular visitor‑based execution model ensure fast parsing and straightforward extensibility.

MinJ currently supports variable declarations (var and val), arithmetic and comparison operations, conditional statements (if/elseif/else), loops (while, for, foreach), list manipulation, and printing literals or variables, enabling you to write programs like FizzBuzz, iterate over collections, and perform calculations with ease.

MinJ is built using Gradle with the ANTLR plugin and ShadowJar for packaging a standalone fat‑JAR. To use the language yourself: simply download the release and execute your scripts via java -jar minjc-<VERSION>.jar <yourfile>.mj. A comprehensive CI/CD pipeline on GitHub Actions ensures that every change is validated, tested, and packaged automatically for reliable releases.

Key Features

MinJ ships as a standalone “fat‑JAR” built with Gradle and the ANTLR plugin. Every commit runs through a CI/CD pipeline (GitHub Actions) that regenerates the parser, runs the full test suite (unit tests, self‑test scripts), and publishes versioned releases.

Quickstart

  1. Download the latest release: https://github.com/Conava/MinJ/releases
  2. Unzip the archive.
  3. Run the interpreter:

     java -jar minjc-<VERSION>.jar <yourfile>.mj
    

    Replace <VERSION> with the actual version number and <yourfile> with your MinJ script.

  4. Explore the examples in the examples directory to see MinJ in action.

  5. Write your own MinJ scripts using the provided grammar as a reference.

📦 Development Requirements

🖥️ IDE Support

For grammar development and live parse tree visualization, it’s recommended to use IntelliJ IDEA Ultimate with the ANTLR v4 plugin. This setup provides:

⚙️ Build & Run

  1. Build fat-JAR (includes ANTLR runtime):

    ./gradlew clean shadowJar  
    
  2. Run interpreter:

    java -jar build/libs/minjc-<VERSION>.jar <PROGRAM_NAME>.mj  
    

📝 Grammar Overview

Below is an in-depth look at the MinJ grammar defined in src/main/antlr/MinJ.g4, annotated with extensive explanations. Wherever a code‑block would normally begin or end with triple backticks (), you'll see the marker **** instead.


1. Top‑Level Structure

Grammar snippet

program  
: (topLevelDecl? NEWLINE)*  
topLevelDecl?  
EOF  
;  

2. Declarations & Statements

2.1 Top‑Level Declarations

Grammar snippet

topLevelDecl  
: classDecl  
| methodDecl  
| statement  
;  

2.2 Variable Declarations

Grammar snippet

varDecl  
: (type | VAR | VAL) idList (ASSIGN expr)?  
;  
idList  
: ID (COMMA ID)*  
;  
type  
: INT_TYPE | FLOAT_TYPE | DOUBLE_TYPE | BOOLEAN_TYPE | CHAR_TYPE | STRING_TYPE  
;  

2.3 Assignment & Print

Grammar snippet

assign  
: idList ASSIGN expr  
;

printStmt
: PRINT expr  
;  

3. Control‑Flow Constructs

3.1 Conditional

Grammar snippet

ifStmt  
: IF expr THEN COLON block  
(ELSEIF expr THEN COLON block)*  
(ELSE COLON block)?  
END  
;  
block  
: (statement? NEWLINE)*  
;  

3.2 Loops

While

whileStmt  
: WHILE expr DO COLON block END  
;  

For

forStmt  
: FOR (varDecl | assign) TO expr (STEP assign)? DO COLON block END  
;  

Foreach

foreachStmt  
: FOREACH ID IN expr DO COLON block END  
;  

4. Expressions

Operator precedence (highest → lowest):

  1. Unary: ! (NOT), - (negation)
  2. Multiplicative: * / %
  3. Additive: + -
  4. Relational: < > <= >= == !=
  5. Logical: &&/and, ||/or, ^/xor
  6. Primary values

Grammar snippet

expr  
: NOT expr                      // unary NOT  
| SUB expr                      // unary minus  
| expr op=(MUL|DIV|MOD) expr    // *,/, %  
| expr op=(ADD|SUB) expr        // +, -  
| expr op=(LT|GT|LE|GE|EQ|NE) expr  // comparisons  
| expr op=(AND|OR|XOR) expr     // boolean ops  
| primary                       // literals, names, calls  
;  

5. Primary & Literals

Grammar snippet

primary  
: NEW ID LPAREN RPAREN             # NewExpr  
| ID LPAREN argList? RPAREN        # CallExprPrimary  
| primary DOT ID LPAREN argList? RPAREN  # DotCallExpr  
| INT                              # IntLiteral  
| FLOAT_LIT                        # FloatLiteral  
| DOUBLE_LIT                       # DoubleLiteral  
| STRING                           # StringLiteral  
| CHAR                             # CharLiteral  
| BOOL_LIT                         # BoolLiteral  
| ID                               # VarReference  
| LPAREN expr RPAREN               # ParenExpr  
| listLiteral                      # ListExpr  
;

listLiteral
: '[' (expr (COMMA expr)*)? ']'  
;  
argList
: expr (COMMA expr)*  
;  

6. Lexer Rules Highlights

Grammar snippet

NEWLINE      : '\r'? '\n' ;  
WS           : [ \t]+ -> skip ;  
LINE_COMMENT : '//' ~[\r\n]* -> skip ;  
BLOCK_COMMENT: '/*' .*? '*/' -> skip ;

// Keywords  
IF     : 'if' ;  FOR    : 'for' ;  VAR : 'var' ;  VAL : 'val' ;  …

// Operators/punctuation  
ASSIGN : '=' ;  LT : '<' ;  EQ : '==' ;  AND : '&&' | 'and' ;  OR : '||' | 'or' ;  …

// Literals  
INT       : [0-9]+ ;  
FLOAT_LIT : [0-9]+ '.' [0-9]+ [fF] ;  
STRING    : '"' (~["\\\r\n] | '\\' .)* '"' ;  
BOOL_LIT  : 'true' | 'false' ;  
ID        : [a-zA-Z_] [a-zA-Z_0-9]* ;  

📚 Examples

Variable Mutability & Type‑Safety

var any = 10
any = "now a String!"    // OK: var is dynamic
int n = 5
// n = "oops"          // ERROR: type mismatch
val PI = 3.14
// PI = 3.0            // ERROR: reassign val

Boolean Operators

Copy
Edit
var a = true and false
var b = true xor true
var c = a or b
var d = !a && !b
print a  // false
print b  // false
print c  // false
print d  // true

Classes and Methods

class Counter:
    var count = 0
    method inc(): 
        count = count + 1 
    end
    method get(): 
        return count 
    end
end

var c = new Counter()
c.inc()
print c.get()   // 1

Recursive and Iterative Factorial

func factorial(n):
    if n <= 1 then: 
        return 1 
    end
    return n * factorial(n - 1)
end

func factLoop(n):
    var r = 1
    for i = 1 to n do: 
        r = r * i 
    end
    return r
end

print factorial(5)  // 120
print factLoop(5)   // 120

Lists & foreach

var nums = [1, 2, 3, 4, 5]
print "Numbers:"
foreach n in nums do:
    print n
end

var names = ["Alice", "Bob", "Charlie"]
print "Names:"
foreach name in names do:
    print name
end

Nested Blocks

var sum = 0
while sum < 10 do:
    if sum % 2 == 0 then:
        print sum
    end
    sum = sum + 1
end

🚀 Run Binary

The interpreter is packaged as a fat-JAR in the release section, including all dependencies. You can run it directly from the command line:

cd Downloads/MinJ-<VERSION>
java -jar minjc-<VERSION>.jar <PROGRAM_NAME>.mj

🔍 How It Works

  1. ANTLR Generation

    • Gradle’s antlr plugin reads MinJ.g4 and generates MinJLexer.java, MinJParser.java, MinJBaseVisitor.java, etc.
  2. Parsing (Main.java)

    • Reads source file via CharStreams.fromPath()
    • Feeds into MinJLexerCommonTokenStreamMinJParser.program()ParseTree
  3. Evaluation (EvalVisitor.java)

    • Extends MinJBaseVisitor<Object>
    • Overrides:

      • visitVarDecl → allocate in env (mark val immutable)
      • visitAssign → update env (error if immutable)
      • visitPrintStmt → print values, explicitly showing '\0' and "" for empty char/string
      • visitIfStmt → evaluate conditions in order, execute matching block
      • visitExpr → perform arithmetic & comparisons via ctx.op.getText()
      • visitPrimary → parse literals, look up variables, handle parentheses
    • Calling visitor.visit(tree) walks the AST and executes statements

🚀 Extending MinJ

  1. Add Grammar Rule Example: how the while loop has been added to the language:

    in MinJ.g4:

    • Add the whileStmt rule:
    whileStmt: 'while' expr 'do' ':' block ; 
    
    • expr is the condition, block is the body.

    • Add whileStmt to the statement rule:

    statement: varDecl | assign | printStmt | ifStmt | whileStmt ;
    
    • Add WHILE, DO, and END keywords to the lexer rules:
    WHILE: 'while' ;
    DO: 'do' ;
    END: 'end' ;
    
  2. Implement Visitor Logic

    • In EvalVisitor.java, implement the visitWhileStmt method:
     @Override
     public Object visitWhileStmt(MinJParser.WhileStmtContext ctx) {
          while ((Boolean) visit(ctx.expr())) {
               visitBlock(ctx.block());
          }
          return null;
     }
    
    • This method evaluates the condition and executes the block repeatedly until the condition is false.

3Rebuild

   ./gradlew clean generateGrammarSource compileJava shadowJar  

4Implement Visitor java @Override public Object visitWhileStmt(MinJParser.WhileStmtContext ctx) { while ((Boolean) visit(ctx.expr())) { visitBlock(ctx.block()); } return null; }

5Test

🏠 GitHub Pages

GitHub Pages automatically serves the project documentation from the master branch (configured in repository settings). A GitHub Actions workflow triggers on pushes to main, builds the site by copying README.md into a static site, and deploys to gh-pages. The site is available at https://Conava.github.io/MinJ.

🚀 CI/CD Pipeline

The CI/CD pipeline is defined in .github/workflows/ci.yml. On each push or pull request to release:

  1. Checkout repository
  2. Set up JDK 21 via actions/setup-java
  3. Run ./gradlew clean generateGrammerSource shadowJar to build the fat‑JAR including all dependencies
  4. Execute ./gradlew test for unit tests
  5. Archive the build/libs/minjc-*.jar artifact
  6. Package the minjc jar with the examples and this README to a zip file
  7. Upload the zip file as a release asset
  8. Finish and Tag the Release on GitHub

🛠️ Tools & Dependencies

MinJ leverages several tools to automate and streamline language development, parsing, and distribution:

ANTLR 4.13.0

Gradle & ShadowJar

Java & JDK 21

GitHub Actions

These tools together create a robust, reproducible development workflow, from grammar changes to production-ready interpreters.