Language Expansion: Variables, Statements, Functions

Lesson, slides, and applied problem sets.

View Slides

Lesson

Language Expansion: variables, statements, functions, and execution model

This is the first “real language” module: still dynamically typed, but now it has mutable state, branches, calls, and block control.


Why expansion happens in steps

A language that starts with expressions is easy to parse but hard to program. This expansion stage adds:

  • declarations,
  • assignment,
  • control flow,
  • functions as top-level declarations,
  • runtime truth model.

Each addition is introduced with minimal cross-cutting impact.


Runtime truthiness contract

In this pack:

  • false and nil are falsey
  • everything else is truthy

This rule must be stable across parser and VM for consistent branch semantics.


Variable declarations and mutation

Keywords:

  • let, var (mutable)
  • const (immutable)

Semantics contract:

  • declarations introduce names in current scope
  • assignments update existing bindings
  • declarations are parsed in statement context

Although const exists here, its enforcement is handled by semantic checks/VM behavior depending on implementation details.


Statements and control

Supported statements:

  • varDecl, assignStmt, exprStmt, if/else, while, return, block

This turns the parser into a statement dispatcher and introduces structured nesting:

  • block pushes a statement list boundary
  • nested blocks can change variable resolution (later phases can enforce shadowing rules)

Functions in this stage

Function declarations are top-level shape:

  • fn name(params) { body }
  • parameters are local names
  • return is explicit via return

At this stage calls and closures are intentionally limited: functions are declarations with callable bodies and explicit returns.


Design pressure that justifies this stage

Why not jump directly to modules/typing?

  • Without if/while, you cannot test side-effect sequencing.
  • Without return, there is no observable function contract.
  • Without vars, you cannot meaningfully validate name resolution.

Small-stage evolution keeps each hidden failure surface visible.


Deep practice checkpoints (almost solved)

1) Keyword semantics in declarations

Input: let x = 1; var y = 2; const c = 3;

Expected:

  • three declarations, each valid in dynamic semantics phase.

2) Mutability intent

Input: const x = 1; x = 2;

Expected:

  • front-end may accept parse; semantic phase should enforce immutability contract.

3) Block semantics

Input: { let x = 1; if (x) { let y = 2; } }

Expected:

  • block-scoped shape is produced by parser.

4) If/else branching

Input: if (x) { y = 1; } else { y = 2; }

Expected:

  • parser builds branch structure with two block bodies.

5) While semantics shape

Input: while (x) { break; }

Expected:

  • while statement with condition and non-empty body.

6) Return placement

Input: return; and return x + 1;

Expected:

  • both parse as return forms.

7) Function declaration layout

Input: fn add(a, b) { return a + b; }

Expected:

  • parsed as top-level function declaration with param list and body block.

8) Assignment classification

Input: x = x + 1;

Expected:

  • assignment statement, not expression statement.

9) Expression statements

Input: add(1, 2);

Expected:

  • expression statement containing call expression.

10) Truthiness edge

Input: if (nil) { x = 1; }

Expected:

  • branch should be interpreted as falsey.

Module Items

Join Discord