Appendix: Joe Grammar

This appendix shows the grammar of the Joe language, in the syntax used in Crafting Interpreters. Below that is the full JLox grammar.

Grammar

Here is the grammar; see Semantic Constraints for some conditions enforced by the compiler.

Statements

program         → declaration* EOF ;

declaration     → classDecl
                | funDecl
                | varDecl
                | statement ;

classDecl       | "class" IDENTIFIER ( "extends" IDENTIFIER )?
                  "{" classItem* "}" ;
classItem       → "method" function
                | "static" "method" function
                | "static" block ; 
                
funDecl         → "function" function ;
function        → IDENTIFIER "(" parameters? ")" block ;
parameters      → IDENTIFIER ( "," IDENTIFIER )* ;

recordDecl      → "record" IDENTIFIER "(" parameters ")" 
                  "{" recordItem* "}" ;
recordItem      → "method" function
                | "static" "method" function
                | "static" block ; 
                  
varDecl         → "var" IDENTIFIER ( "=" expression )? ";" 
                | "var" pattern "=" expression ";" ;
               
statement       → exprStmt
                | breakStmt
                | continueStmt
                | forStmt
                | foreachStmt
                | ifStmt
                | matchStmt
                | printStmt
                | returnStmt
                | switchStmt
                | throwStmt
                | whileStmt 
                | block ;

exprStmt        → expression ";" ;
breakStmt       → "break" ";" ;
continueStmt    → "continue" ";" ;
forStmt         → "for" "(" ( varDecl | exprStmt | ";" )
                  expression? ";"
                  expression? ")" statement ;
forEachStmt     → "foreach" "(" pattern ":" expression ")" statement;
ifStmt          → "if" "(" expression ")" statement 
                  ( "else" statement )? ;
matchStmt       → "match" "(" expression ")" "{" 
                  ( "case" pattern ( "if" expression )? "->" statement )+ 
                  ( "default" "->" statement )? 
                  "}" ;                  
printStmt       → "print" expression ";" ;
returnStmt      → "return" expression? ";" ;
switchStmt      → "switch" "(" expression ")" "{"
                  ( "case" expression ( "," expression )* "->" statement )+
                  ( "default" "->" statement )?
                  "}" ;
throwStmt       → "throw" expression ";" ;
whileStmt       → "while" "(" expression ")" statement ;
block           → "{" declaration* "}" ;

Expressions

expression      → assignment ;
assignment      → ( ( call "." )? IDENTIFIER 
                    | call "[" primary "]"
                  )
                  ( "=" | "+=" | "-=" | "*=" | "/=" ) 
                  assignment 
                | ternary ;
ternary         → logic_or "?" logic_or ":" logic_or ;
logic_or        → logic_and ( "||" logic_and )* ;
logic_and       → equality ( "&&" equality )* ;
equality        → comparison ( ( "!=" | "==" ) comparison )* ;
comparison      → term ( ( ">" | ">=" | "<" | "<=" | "~" | "in" | "ni" ) term )* ;
term            → factor ( ( "-" | "+" ) factor )* ;
factor          → unary ( ( "/" | "*" ) unary )* ;
unary           → ( "++" | "==" | "-" | "!" ) unary 
                | postfix ;
postfix         → call ( "++" | "--" )? ;
call            → primary ( "(" arguments? ")" 
                          | "." IDENTIFIER     
                          | "[" primary "]"
                  )*  ;
arguments       → expression ( "," expression )* ;
primary         → "true" | "false" | "nil"
                | NUMBER | STRING | KEYWORD
                | "this"
                | "@" IDENTIFIER
                | "ruleset" "{" ruleset "}" ;
                | IDENTIFIER 
                | lambda 
                | grouping
                | list 
                | "super" "." IDENTIFIER ;
grouping        → "(" expression ")"
lambda          → "\" parameters? "->" ( expression | block ) ; 
list            → "[" (expression ( "," expression )* ","? )? "]" ;
map             → "{" (map_entry ( "," map_entry )* ","? )? "]" ;
map_entry       → expression ":" expression ;

Patterns

A pattern is a destructuring pattern that can be used with the ~ operator, and with a number of Joe statements.

pattern         → patternBinding
                | constantPattern
                | wildcardPattern
                | valueBinding
                | listPattern
                | mapPattern 
                | namedFieldPattern 
                | orderedFieldPattern ;
                

A patternBinding is a pattern that binds a subpattern's matched value to a binding variable. patternBindings are disallows as the top pattern in some contexts.

patternBinding   | IDENTIFIER "@" pattern ;

A constantPattern matches a literal or computed constant. Variables referenced in a constantPattern must be defined in the enclosing scope and not shadowed by one of the pattern's binding variables.

constantPattern → "true" | "false" | "null" | STRING | NUMBER | KEYWORD
                | "$" IDENTIFIER
                | "$" "(" expression ")" ;

A valueBinding binds the matched value, whatever it is, to a variable.

valueBinding    → IDENTIFIER ;

A listPattern is a list of subpatterns to be matched against the items of a list, with an optional binding variable for the tail of the list.

listPattern     → "[" 
                      (subpattern ( "," subpattern )* ","? )?
                      ( ":" IDENTIFIER )?
                  "]";

A mapPattern is a map of key patterns and value patterns to be matched against the entries of a map value. All keys in the pattern must appear in the map, but the pattern need not exhaust the map. The key patterns must be constantPatterns; Joe does not attempt to do full pattern matching on the map keys.

mapPattern      → "{"
                      ( entryPattern ( "," entryPattern )* ","? )?
                  "}" ;
entryPattern    → constantPattern ":" subpattern ;

A namedFieldPattern matches the type and fields of a Joe value, matching fields by name rather than by position. The first IDENTIFIER must match the value's type name.

namedFieldPattern → IDENTIFIER "("
                      ( fieldPattern ( "," fieldPattern )* ","? )?
                  ")" ;
fieldPattern    → IDENTIFIER ":" subpattern ;

An orderedFieldPattern matches the type and fields of a Joe object that has ordered fields (e.g., Joe records). The fields are matched by position rather than by name in a list-pattern-like fashion.

orderedFieldPattern  → IDENTIFIER "(" 
                           ( pattern ( "," pattern )* ","? )? 
                       ")" ;

Nero

This is the grammar for Nero, Joe's dialect of Datalog. Nero rule sets can be parsed standalone, or as part of a Joe script via the ruleset expression.

Differences from classic Datalog with negation:

  • Comments begin with // rather than %
  • Horn clauses end with ; rather than '.'.
  • Relations usually have initial caps, to match Monica type names.
  • Constant terms can be Monica scalar values, or, if read from a scripted input fact, any Monica value.
  • Variables are normal identifiers, usually lowercase.
  • Wildcards are identifiers with a leading _.
  • Constraints follow a where token.
  • Body atoms can reference fact fields either by name or by position.
nero        → clause* EOF ;
ruleset     → ( clause | export )* EOF ;
export      → "export" IDENTIFIER ( "as" expression )? ";" ;
clause      → axiom
            | rule ;
axiom       → head ";"
rule        → head ":-" body ( "where" constraints )? ";"
head        → indexedAtom ;
body        → "not"? bodyAtom ( "," "not"? bodyAtom )* ;
bodyAtom    → indexedAtom | namedAtom ;
indexedAtom → IDENTIFIER "(" term ( "," term )* ")" ;
orderedAtom → IDENTIFIER "(" namedTerm ( "," namedTerm )* ")" ;
namedTerm   → IDENTIFIER ":" term ;
constrants  → constraint ( "," constraint )* ;
constraint  → variable ( "==" | "!=" | ">" | ">=" | "<" | "<=" ) term ;
term        → constant | variable | wildcard ;
constant    → KEYWORD | STRING | NUMBER | TRUE | FALSE | NULL ;
variable    → IDENTIFIER ;     // No leading "_"
wildcard    → IDENTIFIER ;     // With leading "_"

Semantic restrictions:

  • Normal body atoms can contain free variables to bind against facts. All variables in a rule's head, negated body atoms, or constraints must be bound in normal body atoms.
  • Only body atoms (negated or otherwise) may contain wildcard terms.

JLox Grammar

Here is Nystrom's JLox grammar, for comparison.

// Statements
program         → declaration* EOF ;

declaration     → classDecl
                | funDecl
                | varDecl
                | statement ;

classDecl       | "class" IDENTIFIER ( "extends" IDENTIFIER )?
                  "{" methodDecl* "}" ;
methodDecl      | "method" function ;
funDecl         | "function" function ;
function        → IDENTIFIER "(" parameters? ")" block ;
parameters      → IDENTITIFIER ( "," IDENTIFIER )* ;
varDecl         → "var" IDENTIFIER ( "=" expression )? ";" ;
               
statement       → exprStmt
                | forStmt
                | ifStmt
                | printStmt
                | returnStmt
                | whileStmt 
                | block ;

exprStmt        → expression ";" ;
forStmt         → "for" "(" ( varDecl | exprStmt | ";" )
                  expression? ";"
                  expression? ")" statement ;
ifStmt          → "if" "(" expression ")" statement 
                  ( "else" statement )? ;
printStmt       → "print" expression ";" ;
returnStmt      → "return" expression? ";" ;
whileStmt       → "while" "(" expression ")" statement ;
block           → "{" declaration* "}" ;

// Expression
expression      → assignment ;
assignment      → ( call "." )? IDENTIFIER "=" assignment
                | logic_or ;
logic_or        → logic_and ( "||" logic_and )* ;
logic_or        → equality ( "&&" equality )* ;
equality        → comparison ( ( "!=" | "==" ) comparison )* ;
comparison      → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
term            → factor ( ( "-" | "+" ) factor )* ;
factor          → unary ( ( "/" | "*" ) unary )* ;
unary           → ( "-" | "!" ) unary 
                | call ;
call            → primary ( "(" arguments? ")" | "." IDENTIFIER )* ;
arguments       → expression ( "," expression )* ;
primary         → "true" | "false" | "nil"
                | NUMBER | STRING 
                | "this"
                | IDENTIFIER 
                | "(" expression ")" 
                | "super" "." IDENTIFIER ;