v3 Parsing: Postfix, Maps, and Arrays

Lesson, slides, and applied problem sets.

View Slides

Lesson

v3 Parsing: postfix calls, indexing, and literals

v3 upgrades expression parsing from flat primaries to compound expressions that can call and index at any point.


Problem this module solves

  • represent nested functions and higher-order expressions
  • build call chains like f()(1)
  • access indexed values a[i] and nested forms
  • parse collection literals as first-class primary expressions

Core grammar

program    -> stmt* EOF

stmt       -> varDecl
           | ifStmt
           | whileStmt
           | returnStmt
           | block
           | fnDecl
           | assignOrExpr

fnDecl     -> "fn" IDENT "(" params? ")" block
params     -> IDENT ("," IDENT)*

varDecl    -> ("let"|"var"|"const") IDENT ("=" expr)? ";"
assignOrExpr -> expr ("=" expr)? ";"
block      -> "{" stmt* "}"
expr       -> equality
postfix    -> primary ("(" args? ")" | "[" expr "]")*
args       -> expr ("," expr)*
primary    -> NUMBER | STRING | TRUE | FALSE | NIL
           | IDENT
           | "(" expr ")"
           | array
           | map

array      -> "[" (expr ("," expr)*)? "]"
map        -> "{" (pair ("," pair)*)? "}"
pair       -> expr ":" expr

Parsing flow and structure

1) parseStmt dispatch is token-driven.

  • let/var/const => declaration
  • if/while => statement forms with blocks
  • { => nested block
  • fn => function declaration
  • everything else => assignOrExpr

2) parseExpr starts at lowest-precedence (parseEquality) and recurses down.

3) parsePostfix keeps a loop and repeatedly wraps the previous expression with:

  • call node when ( is seen,
  • index node when [ is seen.

This naturally creates left-to-right chains.


Nested function declarations and scoping effects

Function declarations are ordinary statements and can nest inside blocks:

  • parse block as statement list,
  • each inner function has its own parameter and body parse flow,
  • no special “definition pass” is needed at parse stage.

L-value rule in v3

Assignment target validation remains explicit:

  • valid targets: identifier and index expressions
  • invalid target (e.g., "x" = 1, a + b = c, f() = 1) should fail with invalid assignment target

This is enforced by a dedicated check (isLValue).


Literal forms and delimiters

  • arrays: accept both empty and populated forms
  • maps: allow empty and comma-separated key/value pairs
  • map parsing requires : between key and value
  • missing }/] and : errors should be surfaced where they occur

Common pitfall:

  • if and while always parse a block after condition, never a bare expression.

Deep practice checkpoints (almost solved)

1) Postfix chaining

Input: f(1)[idx](x);

Expected:

  • parse as nested postfix chain (call -> index -> call)

2) Nested function declaration

Input: fn outer(x) { fn inner(y) { return x + y; } return inner(x); }

Expected:

  • outer body contains a function statement (inner) and a return statement

3) Map shape errors

Input: {1 2}

Expected:

  • parse error expected ':' after map key

4) Empty collection literals

  • [] and {} parse as array/map literals with empty element lists.

5) Assignment target restriction

Input: f(1) = 2;

Expected:

  • error invalid assignment target

6) Block vs literal distinction

  • {} in statement position is parsed as empty block only when reached through parseStmt.
  • {} in expression context (after operator, = RHS, etc.) is parsed through parsePrimary as empty map.

7) Precedence check

Input: 1 + 2 * 3 == 7;

Expected:

  • * grouped before +
  • root comparison is ==

8) Return shape

return 1 + 2; and return;

Expected:

  • both parse when semicolon is present; no trailing expression in return;

9) Call argument optionality

f(); and f(1, 2);

Expected:

  • both are valid call expressions inside expression statements

Module Items

  • Program Parser v3

    Parse v3 programs with maps, arrays, and nested functions.

    hard Upgrade to Pro to access hard problems
Join Discord