Free Languages & compilers lessons
Lexing, parsing, grammars, and syntax trees
Lexing commonly groups characters into tokens; parsing checks those tokens against a grammar and constructs a syntax tree. Precedence and associativity decide whether a+b*c means a+(b*c). The tree discards punctuation that no longer matters while preserving the nesting required for later meaning.
A grammar that admits two parse trees for one token sequence leaves meaning underdetermined unless another rule resolves it. Languages define precedence, associativity, or explicit delimiters; parser generators may report conflicts. Silently accepting whichever parse happens first is not a sound semantics.
Static constraints and runtime meaning
Static checking rejects some programs before execution; dynamic checking attaches decisions to runtime values. Rich types can encode variants, ownership, nullability, effects, or units. No practical type system proves every desirable property, and unsafe escape hatches or external data reintroduce obligations.
Instead of a nullable result with side-channel error codes, define Success(value) or Failure(reason). Pattern matching then requires each variant to be handled, and the payload appropriate to one variant is unavailable in the other. The representation moves a runtime convention into a checkable structure.
Compilers, interpreters, VMs, and JITs
A native compiler can optimize ahead of time into target instructions. An interpreter can execute a syntax tree or bytecode step by step. A virtual machine defines a portable instruction set. A just-in-time compiler observes running code and compiles hot paths using runtime facts, with warm-up and deoptimization costs.
Constant folding can replace 3×7 with 21; dead-code elimination can remove work whose result is unobservable; inlining exposes cross-function opportunities. But exceptions, I/O, timing contracts, concurrency, and language rules constrain what counts as unobservable. Faster wrong code is not an optimization.
Lifetime, allocation, garbage collection, and boundaries
Manual management gives explicit control but risks leaks, use-after-free, and double release. Tracing garbage collection discovers objects reachable from roots and reclaims the rest, trading control for runtime work and pauses. Ownership systems encode lifetime constraints statically. Reference counting is prompt but struggles with cycles unless supplemented.
Managed code calling native code must agree on layout, ownership, error signalling, threading, and how objects remain alive while native pointers exist. A type-safe caller can still be corrupted by a callee that writes beyond a buffer. Safe wrappers minimize and validate the unsafe surface.