Reflect completed work: class methods, var parameters, unit
interface/implementation, full ARC (compiler + RTL), and string
concatenation. Mark try/except/finally as partial. Update immediate
next steps to virtual dispatch, real exception unwinding, and macOS target.
- blaise_arc.c: implement _StringAddRef, _StringRelease, _StringConcat
using a 12-byte header (refcnt/length/capacity) at the string pointer;
refcnt=-1 marks immortal static strings; _StringConcat returns refcnt=0
so the compiler-inserted _StringAddRef at assignment brings it to 1
- EmitDataSection: string literals now include the 12-byte immortal header
so _StringAddRef/_StringRelease are safe when called on literal values
- EmitWrite: add 12 to string pointer before calling printf so char data
is passed correctly (header is now part of every string allocation)
- Update TestWriteLn_StringLit_CallsPrintf to check for the +12 offset
instruction instead of the no-longer-direct label argument
- String value parameters now receive _StringAddRef on function entry
and _StringRelease on exit; var parameters are left untouched (caller owns)
- String + string binary expression lowers to $_StringConcat RTL call
- Semantic analyser accepts string + string, resolving result as string type
- Six new tests covering all ARC scenarios in cp.test.arc
Parser recognises 'var' prefix in parameter lists; IsVarParam flag propagates
through TMethodParam → TIdentExpr/TAssignment AST nodes via semantic analysis.
Codegen uses pointer-typed (l) signatures, passes variable addresses at call
sites, and double-dereferences reads/writes inside function bodies. Also fixes
ParseBlock to allow interleaved var and proc/func declaration sections.
TUnit AST node with IntfBlock/ImplBlock; ParseUnit and ParseForwardDecl;
AnalyseUnit with signature verification and missing-impl detection;
GenerateUnit with export prefix for interface-declared functions.
Adds tkTry/tkFinally/tkExcept/tkRaise tokens, TTryFinallyStmt,
TTryExceptStmt, and TRaiseStmt AST nodes, recursive-descent parser
for all three forms (bare raise supported), semantic validation
(raise requires a class expression), and QBE codegen.
try/finally emits both bodies sequentially (simplified; full
stack unwinding requires RTL). try/except emits the try body then
skips to an except_handler label (reachable via raise dispatch in
a future phase). raise emits call $_Raise(l <obj>).
26 new tests in cp.test.exceptions (378 total, all passing).
- tyNil pseudo-type; nil literal compatible with any class type
- Two-pass type analysis so self-referential fields resolve correctly
- Parent fields copied into child layout with consecutive offsets
- FindMethodDecl walks parent chain; OwnerTypeName ensures codegen
emits the defining class name (e.g. $TBase_SetX, not $TChild_SetX)
- ceql/cnel used for pointer comparisons instead of ceqw/cnew
- Multiple var sections now accepted in ParseBlock
- 19 new tests in cp.test.inherit (326 total, all passing)
while/do loops with Boolean condition type-checking and compound
begin/end body support. QBE emits an explicit jmp into the condition
block, jnz to branch into body or exit, and a back-edge jmp at the
end of the body block.
13 new tests; 307 total, 0 failures.
if/else statements with full comparison support (=, <>, <, >, <=, >=).
Comparisons produce Boolean; if condition is type-checked to require
Boolean. Compound begin/end statements work as then/else branches.
QBE emits signed comparison instructions (ceqw, cnew, csltw, etc.)
with jnz/jmp control flow and unique numbered labels.
40 new tests; 294 total, 0 failures.
Standalone procedure/function declarations are now supported at the
program block level (after the var section, standard Pascal order).
Includes TFuncCallExpr for function calls in expression context,
full semantic validation (arg count/types, Result variable), and
QBE IR emission without a Self parameter. Mutual calls between
standalone procs/funcs work because all signatures are registered
before any body is analysed.
28 new tests; 254 total, 0 failures.
Lexer:
- Added tkFunction keyword token.
AST:
- TMethodDecl gains ReturnTypeName (string) and ResolvedReturnType
(TTypeDesc, nil = procedure).
- TMethodCallExpr: new expression node for obj.Method(args) in rvalue
context; carries ObjectName, Name, Args, ResolvedClassType,
ResolvedMethod — set by uSemantic.
Parser:
- ParseMethodDecl(IsFunction): dispatches on tkFunction vs tkProcedure;
for functions, consumes ': ReturnType' after the parameter list.
- ParseClassDef: loops on tkProcedure or tkFunction tokens.
- ParseFactor: IDENT.IDENT followed by '(' now produces TMethodCallExpr
with an inline arg list; without '(' stays TFieldAccessExpr.
Semantic:
- AnalyseTypeDecls: resolves ReturnTypeName → ResolvedReturnType for
each function method alongside param type resolution.
- AnalyseMethodDecl: for function methods, defines 'Result' as a
skVariable of the return type in the method scope, making it
assignable inside the body.
- AnalyseMethodCallExpr: validates object type, looks up method,
verifies the method has a return type (not a procedure), checks arg
count and types; sets ResolvedClassType and ResolvedMethod.
- AnalyseExpr: handles TMethodCallExpr before TFieldAccessExpr.
Code generator:
- EmitMethodDef: emits 'function <qtype> $T_M(...)' for function
methods; allocates and zero-inits %_var_Result; after EmitBlock,
loads Result and emits 'ret <temp>'.
- EmitExpr: handles TMethodCallExpr — loads Self, builds arg list,
emits 'call $T_M(...)' into a typed temp, returns the temp.
Tests: 226 unit tests (22 new function-method tests), 0 failures.
Lexer:
- Added tkProcedure keyword token.
AST:
- TMethodParam: stores parameter name, type string, and resolved type.
- TMethodDecl: method declaration with name, param list, and inline body
(TBlock). Avoids TObject.MethodName collision by naming the field Name.
- TClassTypeDef.Methods: owned TObjectList of TMethodDecl.
- TMethodCallStmt: statement node for obj.Method(args) calls; ObjectName
and Name fields; ResolvedClassType and ResolvedMethod set by semantic.
- TBlock forward-declared before TMethodDecl to break circular dependency.
Parser:
- ParseMethodDecl: parses 'procedure Name [(ParamList)] ; Block ;' inside
a class body.
- ParseParamList: handles 'Name : Type [; Name : Type ...]' param groups.
- ParseClassDef: field parsing followed by method parsing before 'end'.
- ParseStmt: after 'IDENT.IDENT', dispatches to TFieldAssignment (':=')
or TMethodCallStmt (everything else, optionally with arg list).
- ParseMethodCallArgList: argument list for method call statements.
Semantic:
- FMethodIndex: TStringList mapping 'TypeName.Name' -> TMethodDecl for
O(log n) dispatch lookup.
- AnalyseTypeDecls: indexes methods and resolves param types after fields.
- AnalyseMethodBodies: analyses each method body after type registration.
- AnalyseMethodDecl: pushes a scope with Self (class type) and explicit
params, then calls AnalyseBlock for the method body.
- AnalyseMethodCall: resolves object type, looks up method, checks arg
count and types; sets ResolvedClassType and ResolvedMethod on the node.
Code generator:
- EmitMethodDefs: emits a QBE function for every method in every class
type before the main function.
- EmitMethodDef: emits 'function $TypeName_Name(l %_par_Self, ...) {'.
- EmitParamAllocs: allocates stack slots for Self and explicit params.
- EmitMethodCall: loads Self from the caller's var slot, then emits
'call $TypeName_Name(l selfTemp, ...)'.
Tests: 204 unit tests (24 new method tests), 0 failures.
Grammar: docs/grammar.ebnf updated with MethodDecl, ParamList, MethodCall.
Symbol table:
- Added tyClass kind and NewClassType factory; TRecordTypeDesc now
accepts an optional kind parameter so records and classes share the
same descriptor with distinct semantics.
Semantic analyser:
- AnalyseTypeDecls runs before PushScope so type symbols land in global
scope and survive PopScope.
- Handles TRecordTypeDef and TClassTypeDef; sets IsConstructorCall on
TypeName.Create expressions and IsClassAccess on class-variable field
access and assignment nodes.
Code generator (QBE IR):
- Class variables: 8-byte pointer slot, zeroed on entry.
- Constructor calls: malloc(sizeof fields), store pointer.
- Class field access/write: load pointer, add field offset, load/store.
- ARC string assignment: AddRef new value, Release old value, then store.
- Block exit: Release every string variable in scope (EmitStringCleanup).
Lexer / Parser / AST:
- Added tkClass keyword and ParseClassDef; FieldDecl parsing refactored
into ParseFieldDecl(AFields) shared by both record and class.
- AST gains TClassTypeDef, and IsConstructorCall/IsClassAccess flags on
TFieldAccessExpr and TFieldAssignment.
RTL:
- blaise_arc.c: Phase 2 no-op stubs for _StringAddRef / _StringRelease.
- rtl/Makefile: builds blaise_rtl.a; `make install` copies it next to
the compiler binary for automatic discovery by FindRTL.
- Blaise.pas driver: FindRTL checks BLAISE_RTL env var then binary dir;
links the RTL archive when present.
Tests:
- 180 unit tests (records ×23, classes ×22, ARC ×9, codegen ×9, …).
- 4 end-to-end integration tests in tests/integration/test_arc_strings.sh
covering string assignment, two-var cleanup, reassignment cycle, and
empty-program linkage.
Docs:
- design.adoc updated with Phase 2 implementation status table.
- docs/grammar.ebnf: new authoritative EBNF grammar for the Blaise
language as implemented, including ARC semantic annotations.
The semantic analyser now runs between parsing and code generation.
Type mismatches, undeclared identifiers, and duplicate declarations are
reported to stderr with source position and exit code 1 before any IR
is emitted.
TSemanticAnalyser walks the AST, resolves all identifiers via the symbol
table, annotates every expression node with ResolvedType, and type-checks
assignments and procedure calls. Raises ESemanticError with source position
on undeclared identifiers, type mismatches, duplicate declarations, and
unknown types. TProgram now owns the TSymbolTable after analysis so
ResolvedType pointers remain valid for the lifetime of the AST.
Also adds the 'div' keyword for integer division throughout the pipeline
(lexer, parser, semantic). 26 new FPCUnit tests, 126 total, all passing.
TSymbolTable manages a scope stack with case-insensitive lookup. Each
TScope holds a sorted TStringList (names → TSymbol) and chains to its
parent for identifier resolution. Built-in primitives (Integer, Int64,
UInt32, Byte, Boolean, string) and I/O procedures (Write, WriteLn) are
pre-registered in the global scope. 29 FPCUnit tests, all passing.
Fix TProgram.Uses reserved-word clash (renamed to UsedUnits), nested
brace-comment in uParser.pas, and missing uAST in Blaise.pas uses clause.
Vendor QBE 1.2 source in vendor/qbe/ (built locally with make; binary
excluded via .gitignore). End-to-end pipeline confirmed: Blaise compiles
Hello World and Calc (6*7=42) to native x86_64 Linux binaries.
71/71 FPCUnit tests passing.