Skip to content

Fenia Grammar

Kit Oliynyk edited this page Nov 2, 2026 · 4 revisions

TL;DR: The formal grammar of Fenia as the parser accepts it today, with tokens and operator precedence. It is regenerated from the yacc source, not written by hand. Use when: you need to know exactly what parses, for example when writing a linter, an editor mode or a code generator. Source of truth: dreamland_code/src/fenia/parse.y++ (rules and precedence), scan.l++ (tokens) Verified: 2026-11-02 against dreamland_code 88da8302

For the meaning of each construct, types and runtime rules see Fenia-Language. This page only says what is accepted.

Origin

The language started as a mob-program language for another MUD in 2004. The parser headers carry that year and two authors. The history is on History.

Tokens

Token Matches
IDENT [A-Za-z_] or a Cyrillic letter, then letters, digits, _
NUMBER [0-9]+ (read with atoi), and the words true (1) and false (0)
STRING '...' or "...", escapes \n \r \t, any other \x gives x, may span lines
keywords null if in else for break continue return function var try catch throw and this
punctuation < > - + = * / ( ) ! ~ % | ^ , . { } [ ] ; and &
two-character == != ->
comments // ... to end of line, /* ... */ nested

There is no >= or <= token. The grammar builds them from > followed by =. && and || are likewise two & or two | tokens in a row.

Precedence

Declared in parse.y++, lowest first. right means right-associative.

Level Operators
1 -> (right)
2 = (right)
3 logical && || (left, rule level)
4 |
5 ^
6 &
7 == !=
8 < > (<=, >= take the same level)
9 + -
10 * / %
11 unary ! ~ - (right)
12 . ( [ (left)

The logical rules carry a lower precedence than the lookahead tokens | and &. When the parser sees a && b || c it prefers to shift, so the chain groups to the right: a && (b || c). This was derived by replaying this table in an LALR parser generator, not by running the engine (unverified on the engine). Parenthesize mixed && and ||.

Grammar

Generated from the rules in parse.y++ with the semantic actions removed. (empty) is the empty production. Single quotes mark literal tokens.

input
    : exp

stmts
    : (empty)
    | stmts stmt
    | stmts error

stmt
    : stmt_aux

stmt_aux
    : ';'
    | '{' stmts '}'
    | 'if' '(' exp ')' stmt
    | 'if' '(' exp ')' stmt 'else' stmt
    | 'for' '(' explist ';' exp ';' explist ')' stmt
    | 'for' '(' explist ';' ';' explist ')' stmt
    | 'for' '(' id 'in' exp ')' stmt
    | 'break' ';'
    | 'continue' ';'
    | exp ';'
    | 'return' ';'
    | 'return' exp ';'
    | 'throw' exp ';'
    | 'var' arguments ';'
    | 'try' stmt 'catch' '(' id ')' stmt

nonnull_explist
    : exp
    | nonnull_explist ',' exp

explist
    : (empty)
    | nonnull_explist

exp
    : binary_exp
    | exp '&' '&' exp
    | exp '|' '|' exp
    | reference '=' exp
    | lambda

lambda_rh
    : '->' exp
    | '->' '{' stmts '}'

lambda
    : id lambda_rh
    | '(' ')' lambda_rh
    | '(' nonnull_arguments ',' id ')' lambda_rh

binary_exp
    : unary_exp
    | exp '+' exp
    | exp '*' exp
    | exp '-' exp
    | exp '/' exp
    | exp '&' exp
    | exp '|' exp
    | exp '%' exp
    | exp '^' exp
    | exp '==' exp
    | exp '!=' exp
    | exp '>' exp
    | exp '>' '=' exp
    | exp '<' exp
    | exp '<' '=' exp

unary_exp
    : primary
    | '!' unary_exp
    | '~' unary_exp
    | '-' unary_exp

id
    : IDENT

reference
    : id
    | '.' id
    | primary '.' id
    | primary '[' exp ']'

primary
    : reference
    | reference '(' explist ')'
    | '[' nonnull_explist ']'
    | '(' exp ')'
    | constant
    | 'function' '(' arguments ')' '{' stmts '}'
    | 'function' NUMBER '(' arguments ')' '{' stmts '}'
    | 'function' NUMBER

constant
    : 'null'
    | NUMBER
    | string

arguments
    : (empty)
    | nonnull_arguments

nonnull_arguments
    : id
    | nonnull_arguments ',' id

string
    : STRING
    | string STRING

Reading the grammar

  • One expression per file. input is a single exp. A statement list only appears inside function ( ) { ... } and -> { ... }.
  • var takes names only. 'var' arguments ';' is a list of identifiers. There is no initializer.
  • A call needs a reference on the left. The rule is reference '(' explist ')'. A call on a call result or on a parenthesized function does not parse.
  • Assignment targets are a reference: a name, .name, x.name or x[expr]. Assignment is an expression, so it is allowed inside if (...) conditions.
  • [a, b, c] is not a list literal. The rule '[' nonnull_explist ']' evaluates every element and yields the last one. It cannot be empty.
  • for has two C-style forms (with and without a condition) and the for (id in exp) form. var is not allowed in the init part.
  • stmts error is the recovery rule. A bad statement inside a body is reported as statement expected and parsing continues, but the file is still rejected.
  • Numbered functions (function 12 (a) { } and function 12) are how the engine writes stored closures back out. Do not write them by hand.
  • Lambdas need a single bare name, (), or two or more names in parentheses. (a) -> a with one parenthesized name does not match any lambda rule.
  • Missing on purpose: while, do, switch, ?:, ++, --, compound assignment, new, object and array literals, else if as a keyword.

See also

Clone this wiki locally