Language Reference

Detailed reference for Jot grammar, precedence, values, scoping, and runtime behavior.

This page describes the current Jot language as implemented in parser/src/main/antlr4/Jot.g4 and the runtime/compiler modules.

Quick facts

  • Jot programs are newline-separated statements.
  • The language is expression-oriented (if, fn, blocks, and lists are expressions).
  • while is a statement.
  • Functions are first-class values.
  • Built-ins currently available by default: print(value) and push(list, value).

Lexical rules

Keywords

if, then, else, let, while, fn, true, false

Identifiers

[a-zA-Z_][a-zA-Z0-9_]*

Number literals

  • Integer: [0-9]+
  • Decimal: [0-9]+.[0-9]+

Notes:

  • Negative literals are not a separate token; use expressions like 0 - 5.
  • String literals are not implemented.
  • Comments are not implemented.

Program shape

A program must contain at least one statement.

  • Newlines separate top-level statements.
  • Optional blank lines are allowed before and after the program.
  • No semicolon statement separator exists.
let a = 1
let b = 2
print(a + b)

Statements

Jot statements:

  • let binding
  • assignment
  • block statement
  • while statement
  • expression statement

Let binding

let name = expression

Rules:

  • Variable must not already exist in the current scope.
  • Shadowing is allowed in nested scopes.

Assignment

name = expression

Rules:

  • Variable must already exist in some visible scope.
  • Assignment updates that slot.

Block statement

{
  statement
  statement
}

As a statement, block value is discarded.

While statement

while condition statement

The body is any single statement. Use a block for multiple statements.

let i = 0
while i < 3 {
  print(i)
  i = i + 1
}

Condition must evaluate to boolean at runtime.

Expression statement

Any expression can stand as a statement:

print(1)
fn(x) x + 1

Its value is evaluated and ignored.

Expressions

Precedence and associativity

From lowest precedence to highest:

  1. || (left-associative)
  2. && (left-associative)
  3. ==, != (left-associative)
  4. <, <=, >, >= (left-associative)
  5. +, - (left-associative)
  6. *, /, % (left-associative)
  7. Function call (...) postfix (left-associative)
  8. Primary expressions

Primary expressions

  • Parenthesized expression: (expr)
  • Block expression: { ... }
  • List expression: [a, b, c]
  • If expression: if cond then expr else expr
  • Function literal: fn(p1, p2) expr
  • Boolean literal: true, false
  • Number literal
  • Identifier

Block expressions and block values

A block expression creates a new local scope.

let x = {
  let y = 2
  y + 3
}
print(x)  # 5

Rule for block value:

  • If the last statement in the block is an expression statement, that expression is the block result.
  • Otherwise the block result is null.
print({ let y = 1 y })
print({ let y = 1 })

Output:

1
null

If expressions

Syntax:

if condition then trueBranch else falseBranch
  • else branch is optional.
  • If else is omitted and condition is false, result is null.
  • Condition must be boolean at runtime.
print(if true then 1 else 2)
print(if false then 1)

Functions and calls

Function literal syntax:

fn(param1, param2) bodyExpression
  • Body is exactly one expression.
  • Parameter names must be unique.
  • Functions are values; bind with let.
let add = fn(a, b) a + b
print(add(2, 3))

Call syntax:

callee(arg1, arg2)
  • Calls are postfix, so chaining is valid: f()(1).
  • Arity is checked at runtime.
  • Calling a non-callable value raises a runtime error.

Closures

Functions capture outer variables when the function value is created.

let x = 10
let f = fn() x
x = 20
print(f())

Current behavior prints 10.

Values and runtime types

Current runtime value kinds:

  • Long
  • Double
  • JotBigInteger
  • JotBigDecimal
  • Boolean
  • JotList
  • JotFunction
  • JotNull (displayed as null)

Numeric behavior

  • Large integer literals that do not fit long become JotBigInteger.
  • Decimal literals are parsed through BigDecimal; they use double when exactly representable and finite, otherwise JotBigDecimal.
  • Numeric operators are defined for integer-family pairs and decimal-family pairs.
  • Mixed Long/Double arithmetic is currently a type error.

Example:

print(1 + 2)    # ok
print(1 + 2.0)  # runtime type error

Operators

Arithmetic

+, -, *, /, %

Runtime type checks apply. Invalid operand combinations raise runtime errors.

Comparisons

<, <=, >, >=

Defined for numeric types.

Equality

==, !=

  • Numbers and booleans compare by value.
  • null == null is true.
  • Other object types (for example lists/functions) compare by identity.
print([1, 2] == [1, 2])

Output:

false

Logical operators

&&, ||

  • Short-circuiting is implemented.
  • Both operands must be booleans.
print(true || (1 / 0 == 0))
print(false && (1 / 0 == 0))

Lists

List literal syntax:

[]
[1, 2, 3]
[1, true, fn(x) x]

Lists are heterogeneous and mutable through push.

Built-ins

print(value)

Prints the value and returns null.

push(list, value)

Appends value to list and returns the same list.

let l = [1, 2]
print(push(l, 3))
print(l)

Output:

[1, 2, 3]
[1, 2, 3]

Scope rules

  • New scopes are introduced by block expressions/statements and function bodies.
  • let checks duplicates only in the current scope.
  • Inner scopes can shadow outer names.
let x = 1
{
  let x = 2
  print(x)
}
print(x)

Output:

2
1

Current limitations

Not implemented yet:

  • Modules/import syntax
  • String literals
  • Comment syntax
  • Type annotations in source syntax
  • Top-level function declaration syntax (fn name(...) ...)

Because there is no named function declaration form, direct self-recursive function definitions are not currently available in syntax.