Przeglądaj źródła

AST module (Stage A) + fix array-of-record element stride

Stage A of the two-phase frontend refactor: add an arena-backed AST
node store (compiler/src/AST.def/.mod) with MakeNode/MakeLeaf/MakeBin/
MakeUn, child accessors, an interned text pool and a Dump.  Nothing in
the grammar calls it yet; it is the foundation for building the tree
while parsing and lowering in a later pass.  tests/t_ast.mod builds a
small tree and walks it (exit 42).

Fix (found while testing AST): ElemSize returned 4 for record/class/set
array elements, so an ARRAY OF RECORD-with-inline-array stride was
wrong and consecutive elements overlapped.  Record/class/set elements
now use SymTab.TypeSize, the inline footprint.

Suite 178/178; fixpoint OK (2,900,782 bytes).
Eric Streit 1 tydzień temu
rodzic
commit
53943fa2fe

+ 5 - 0
compiler/run_tests.sh

@@ -256,6 +256,11 @@ expect_run_files DOpaqueProg 55 d_opaque.def d_opaque.mod d_opaque_prog.mod
 expect_run_files TString 42 d_string.def d_string.mod t_string.mod
 expect_run_files FioProg 42 ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod ../runtime/syslib/FileIO.def ../runtime/syslib/FileIO.mod fio_prog.mod
 expect_run_files StorageProg 42 ../stdlib/storage.def ../stdlib/storage.mod storage_prog.mod
+expect_run_files TAst 42 \
+  ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
+  ../runtime/syslib/FileIO.def ../runtime/syslib/FileIO.mod \
+  src/AST.def src/AST.mod \
+  t_ast.mod
 expect_run_files WrapProg 42 \
   ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
   ../stdlib/iochan.def ../stdlib/iochan.mod \

+ 161 - 0
compiler/src/AST.def

@@ -0,0 +1,161 @@
+DEFINITION MODULE AST;
+(* Abstract syntax tree for m2compiler-V3 (Stage A: node store only).
+
+   This is the foundation for a two-phase frontend: productions build
+   nodes here while parsing, and a later walk resolves names and
+   lowers to QBE.  Until the grammar is converted, nothing calls it.
+
+   Design notes
+   ------------
+   - Nodes live in a flat, pre-allocated arena and are referenced by an
+     integer index (`Node`), exactly like `SymTab.TypeIndex`.  This
+     avoids pointers in the interface and keeps the structure
+     self-hosting-friendly (no GC, bounded memory).
+   - Every node has a `kind` (NKind) and a `ty` (a SymTab.TypeIndex set
+     by the checker), plus up to `MaxChild` children.  This mirrors the
+     `armornick/CocoR` AST.java pattern (a Node base with per-construct
+     subclasses) but flattened into one record array so Modula-2 can
+     store it without variant-class machinery.
+   - Text (identifiers, literals, operator spellings) is kept in a
+     pooled string table; `TxtIndex` is an index into it.
+   - `NoNode` is the null node. *)
+
+CONST
+  NoNode = -1;
+  NoTxt  = -1;
+
+  (* how many children a node may carry.  Statements/expressions in
+     Modula-2 nest shallowly (a body is a list, an expression is a
+     spine); 8 covers every construct in the current grammar with room
+     to spare. *)
+  MaxChild = 8;
+
+  (* ---- node kinds ----
+     Grouped by role; the numeric values are stable and may be stored
+     in listings. *)
+
+  (* module / unit structure *)
+  NkUnit        = 0;   (* [0]=name txt, [1]=decls, [2]=body *)
+  NkDefUnit     = 1;
+  NkImplUnit    = 2;
+  NkDeclSeq     = 3;   (* [0..n)=declarations *)
+  NkImport      = 4;   (* [0]=module txt, [1..n)=names (or FROM list) *)
+
+  (* declarations *)
+  NkConstDecl   = 10;  (* [0]=name txt, [1]=value expr *)
+  NkTypeDecl    = 11;  (* [0]=name txt, [1]=type *)
+  NkVarDecl     = 12;  (* [0..n)=name txt, [1]=type  (names then type) *)
+  NkProcDecl    = 13;  (* [0]=name, [1]=formals, [2]=result type,
+                          [3]=body, [4]=forward flag node *)
+  NkClassDecl   = 14;
+  NkFieldDecl   = 15;
+
+  (* type constructors *)
+  NkTypeIdent   = 20;  (* [0]=name txt (possibly qualified: a.b) *)
+  NkSubrange    = 21;  (* [0]=lo expr, [1]=hi expr *)
+  NkEnum        = 22;  (* [0..n)=literal name txts *)
+  NkArray       = 23;  (* [0..n-2)=index types, [last]=element type *)
+  NkRecord      = 24;  (* [0..n)=fields *)
+  NkSet         = 25;  (* [0]=base type *)
+  NkPointer     = 26;  (* [0]=base type *)
+  NkProcType    = 27;  (* [0]=formals, [1]=result type *)
+
+  (* statements *)
+  NkAssign      = 40;  (* [0]=designator, [1]=value *)
+  NkCall        = 41;  (* [0]=callee, [1..n)=actuals *)
+  NkIf          = 42;  (* [0]=cond, [1]=then, [2]=else (or NoNode) *)
+  NkWhile       = 43;  (* [0]=cond, [1]=body *)
+  NkRepeat      = 44;  (* [0]=body, [1]=until cond *)
+  NkLoop        = 45;  (* [0]=body *)
+  NkExit        = 46;
+  NkFor         = 47;  (* [0]=control var, [1]=lo, [2]=hi, [3]=by, [4]=body *)
+  NkCase        = 48;  (* [0]=selector, [1..n)=case arms *)
+  NkCaseArm     = 49;  (* [0..n-1)=labels, [n]=body *)
+  NkWith        = 50;  (* [0..n-1)=designators, [n]=body *)
+  NkReturn      = 51;  (* [0]=expr (or NoNode) *)
+  NkBlock       = 52;  (* [0..n)=statements *)
+  NkHalt        = 53;
+
+  (* expressions *)
+  NkBinExpr     = 70;  (* [0]=left, [1]=right; `op` holds the operator *)
+  NkUnary       = 71;  (* [0]=operand; `op` holds the operator *)
+  NkDesignator  = 72;  (* [0]=base, [1..n)=selectors *)
+  NkSelector    = 73;  (* .field / [idx] / ^ ; `op` distinguishes *)
+  NkIdent       = 74;  (* [0]=name txt, [1]=qualifier txt (or NoTxt) *)
+  NkIntLit      = 75;  (* [0]=digits txt *)
+  NkRealLit     = 76;
+  NkStrLit      = 77;
+  NkCharLit     = 78;
+  NkBraceLit    = 79;  (* [0]=type, [1..n)=elements *)
+  NkSetLit      = 80;
+
+  (* ---- relational / arithmetic / selector operator codes ---- *)
+  OpAdd = 0;  OpSub = 1;  OpOr  = 2;
+  OpMul = 3;  OpDiv = 4;  OpMod = 5;  OpAnd = 6;
+  OpEq  = 7;  OpNe  = 8;  OpLt  = 9;  OpLe  = 10;  OpGt = 11;  OpGe = 12;
+  OpIn  = 13;
+
+  (* selector codes (NkSelector.op) *)
+  SelField = 0;  SelIndex = 1;  SelDeref = 2;
+
+  TYPE
+    Node    = INTEGER;   (* index into the arena, or NoNode *)
+    TxtIndex = INTEGER;  (* index into the text pool, or NoTxt *)
+
+  (* ---------------- lifecycle ---------------- *)
+
+PROCEDURE Init;
+(* Clears the arena and text pool. *)
+
+  (* ---------------- text pool ---------------- *)
+
+PROCEDURE AddTxt (s : ARRAY OF CHAR) : TxtIndex;
+(* Interns s, returning its index (deduplicated). *)
+
+PROCEDURE Txt (i : TxtIndex; VAR s : ARRAY OF CHAR);
+(* Copies the i-th text into s (empty when out of range). *)
+
+PROCEDURE TxtLen (i : TxtIndex) : CARDINAL;
+
+  (* ---------------- node construction ---------------- *)
+
+PROCEDURE MakeNode (kind : INTEGER) : Node;
+(* A fresh node of the given kind, no children, ty = SymTab.InvalidType
+   (the caller sets it).  NoNode when the arena is full. *)
+
+PROCEDURE MakeLeaf (kind : INTEGER; name : ARRAY OF CHAR) : Node;
+(* A fresh node with child[0] set to the interned text of name. *)
+
+PROCEDURE MakeBin (kind, op : INTEGER; l, r : Node) : Node;
+(* A fresh node with op and two children (binary expr, assign, …). *)
+
+PROCEDURE MakeUn (kind, op : INTEGER; e : Node) : Node;
+(* A fresh node with op and one child (unary expr, selector, …). *)
+
+  (* ---------------- node accessors ---------------- *)
+
+PROCEDURE Kind (n : Node) : INTEGER;
+PROCEDURE SetKind (n : Node; kind : INTEGER);
+
+PROCEDURE Op (n : Node) : INTEGER;
+PROCEDURE SetOp (n : Node; op : INTEGER);
+
+PROCEDURE Ty (n : Node) : INTEGER;
+(* The SymTab.TypeIndex attached by the checker (InvalidType until
+   then).  Declared as INTEGER to avoid a circular import with
+   SymTab. *)
+PROCEDURE SetTy (n : Node; t : INTEGER);
+
+PROCEDURE NChild (n : Node) : CARDINAL;
+PROCEDURE Child (n : Node; i : CARDINAL) : Node;
+PROCEDURE AddChild (n : Node; c : Node) : BOOLEAN;
+PROCEDURE SetChild (n : Node; i : CARDINAL; c : Node);
+(* Replaces child i, extending the node when i = NChild. *)
+
+  (* ---------------- listing (debug) ---------------- *)
+
+PROCEDURE Dump (n : Node; depth : CARDINAL);
+(* Writes a one-line-per-node indented tree to StdOut (via FileIO).
+   Used by the Stage-A self-test and future debugging. *)
+
+END AST.

+ 285 - 0
compiler/src/AST.mod

@@ -0,0 +1,285 @@
+IMPLEMENTATION MODULE AST;
+(* Arena-backed abstract syntax tree (Stage A).
+
+   Layout:
+     - `arena` is a fixed array of nodes; live count `nNodes`.
+     - each node stores kind/op/ty, a child count and child indices.
+     - `pool` is a fixed text area; `poolOff[i]`/`poolLen[i]` delimit
+       the i-th interned string (NUL-free, so no terminator needed).
+   No dynamic allocation: the sizes mirror SymTab's fixed tables and
+   are ample for the compiler's own source when self-hosting. *)
+
+IMPORT FileIO;
+
+CONST
+  MaxNodes = 65536;
+  MaxTxt   = 16384;
+  PoolSize = 1048576;   (* 1 MiB of pooled text *)
+  MaxTxtLen = 255;
+
+  (* arena has MaxNodes slots, indices 0..MaxNodes-1 *)
+  HighNode = MaxNodes - 1;
+  HighTxt  = MaxTxt - 1;
+
+TYPE
+  NodeRec = RECORD
+    kind   : INTEGER;
+    op     : INTEGER;
+    ty     : INTEGER;
+    nch    : CARDINAL;
+    child  : ARRAY [0 .. MaxChild - 1] OF Node;
+  END;
+
+VAR
+  arena  : ARRAY [0 .. HighNode] OF NodeRec;
+  nNodes : CARDINAL;
+  pool   : ARRAY [0 .. PoolSize - 1] OF CHAR;
+  poolUse: CARDINAL;
+  poolOff: ARRAY [0 .. HighTxt] OF CARDINAL;
+  poolLen: ARRAY [0 .. HighTxt] OF CARDINAL;
+  nTxt   : CARDINAL;
+
+PROCEDURE Init;
+  VAR i: CARDINAL;
+  BEGIN
+    nNodes := 0;
+    nTxt := 0;
+    poolUse := 0;
+    (* nodes are zeroed lazily via InitNode; nothing else needed *)
+  END Init;
+
+PROCEDURE AddTxt (s : ARRAY OF CHAR) : TxtIndex;
+  VAR i, k, L, off: CARDINAL; dup: BOOLEAN;
+  BEGIN
+    L := 0;
+    WHILE (L <= HIGH(s)) AND (s[L] # CHR(0)) DO INC(L) END;
+    IF L > MaxTxtLen THEN L := MaxTxtLen END;
+    (* dedupe *)
+    i := 0;
+    WHILE i < nTxt DO
+      IF poolLen[i] = L THEN
+        dup := TRUE;
+        k := 0;
+        WHILE (k < L) AND dup DO
+          IF pool[poolOff[i] + k] # s[k] THEN dup := FALSE END;
+          INC(k)
+        END;
+        IF dup THEN RETURN VAL(TxtIndex, i) END
+      END;
+      INC(i)
+    END;
+    IF (nTxt > HIGH(poolOff)) OR (poolUse + L > PoolSize) THEN
+      RETURN NoTxt
+    END;
+    off := poolUse;
+    k := 0;
+    WHILE k < L DO pool[poolUse] := s[k]; INC(poolUse); INC(k) END;
+    poolOff[nTxt] := off;
+    poolLen[nTxt] := L;
+    INC(nTxt);
+    RETURN VAL(TxtIndex, nTxt - 1)
+  END AddTxt;
+
+PROCEDURE Txt (i : TxtIndex; VAR s : ARRAY OF CHAR);
+  VAR k, L: CARDINAL;
+  BEGIN
+    s[0] := CHR(0);
+    IF (i < 0) OR (i >= VAL(INTEGER, nTxt)) THEN RETURN END;
+    L := poolLen[i];
+    k := 0;
+    WHILE (k < L) AND (k < HIGH(s)) DO
+      s[k] := pool[poolOff[i] + k]; INC(k)
+    END;
+    s[k] := CHR(0)
+  END Txt;
+
+PROCEDURE TxtLen (i : TxtIndex) : CARDINAL;
+  BEGIN
+    IF (i < 0) OR (i >= VAL(INTEGER, nTxt)) THEN RETURN 0 END;
+    RETURN poolLen[i]
+  END TxtLen;
+
+PROCEDURE InitNode (n : Node);
+  VAR j: CARDINAL;
+  BEGIN
+    arena[n].kind := 0;
+    arena[n].op := -1;
+    arena[n].ty := -1;   (* SymTab.InvalidType, by value *)
+    arena[n].nch := 0;
+    j := 0;
+    WHILE j < MaxChild DO arena[n].child[j] := NoNode; INC(j) END
+  END InitNode;
+
+PROCEDURE NewSlot (): Node;
+  BEGIN
+    IF nNodes > HighNode THEN RETURN NoNode END;
+    InitNode(VAL(INTEGER, nNodes));
+    INC(nNodes);
+    RETURN VAL(INTEGER, nNodes - 1)
+  END NewSlot;
+
+PROCEDURE MakeNode (kind : INTEGER) : Node;
+  VAR n: Node;
+  BEGIN
+    n := NewSlot();
+    IF n # NoNode THEN arena[n].kind := kind END;
+    RETURN n
+  END MakeNode;
+
+PROCEDURE MakeLeaf (kind : INTEGER; name : ARRAY OF CHAR) : Node;
+  VAR n: Node;
+  BEGIN
+    n := MakeNode(kind);
+    IF n # NoNode THEN
+      arena[n].child[0] := VAL(INTEGER, AddTxt(name));
+      arena[n].nch := 1
+    END;
+    RETURN n
+  END MakeLeaf;
+
+PROCEDURE MakeBin (kind, op : INTEGER; l, r : Node) : Node;
+  VAR n: Node;
+  BEGIN
+    n := MakeNode(kind);
+    IF n # NoNode THEN
+      arena[n].op := op;
+      arena[n].child[0] := l;
+      arena[n].child[1] := r;
+      arena[n].nch := 2
+    END;
+    RETURN n
+  END MakeBin;
+
+PROCEDURE MakeUn (kind, op : INTEGER; e : Node) : Node;
+  VAR n: Node;
+  BEGIN
+    n := MakeNode(kind);
+    IF n # NoNode THEN
+      arena[n].op := op;
+      arena[n].child[0] := e;
+      arena[n].nch := 1
+    END;
+    RETURN n
+  END MakeUn;
+
+PROCEDURE Kind (n : Node) : INTEGER;
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1) THEN RETURN -1 END;
+    RETURN arena[n].kind
+  END Kind;
+
+PROCEDURE SetKind (n : Node; kind : INTEGER);
+  BEGIN
+    IF (n >= 0) AND (n <= VAL(INTEGER, nNodes) - 1) THEN
+      arena[n].kind := kind END
+  END SetKind;
+
+PROCEDURE Op (n : Node) : INTEGER;
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1) THEN RETURN -1 END;
+    RETURN arena[n].op
+  END Op;
+
+PROCEDURE SetOp (n : Node; op : INTEGER);
+  BEGIN
+    IF (n >= 0) AND (n <= VAL(INTEGER, nNodes) - 1) THEN
+      arena[n].op := op END
+  END SetOp;
+
+PROCEDURE Ty (n : Node) : INTEGER;
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1) THEN RETURN -1 END;
+    RETURN arena[n].ty
+  END Ty;
+
+PROCEDURE SetTy (n : Node; t : INTEGER);
+  BEGIN
+    IF (n >= 0) AND (n <= VAL(INTEGER, nNodes) - 1) THEN
+      arena[n].ty := t END
+  END SetTy;
+
+PROCEDURE NChild (n : Node) : CARDINAL;
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1) THEN RETURN 0 END;
+    RETURN arena[n].nch
+  END NChild;
+
+PROCEDURE Child (n : Node; i : CARDINAL) : Node;
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1)
+       OR (i >= arena[n].nch) THEN
+      RETURN NoNode
+    END;
+    RETURN arena[n].child[i]
+  END Child;
+
+PROCEDURE AddChild (n : Node; c : Node) : BOOLEAN;
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1) THEN RETURN FALSE END;
+    IF arena[n].nch >= MaxChild THEN RETURN FALSE END;
+    arena[n].child[arena[n].nch] := c;
+    INC(arena[n].nch);
+    RETURN TRUE
+  END AddChild;
+
+PROCEDURE SetChild (n : Node; i : CARDINAL; c : Node);
+  BEGIN
+    IF (n < 0) OR (n > VAL(INTEGER, nNodes) - 1) THEN RETURN END;
+    IF i < arena[n].nch THEN
+      arena[n].child[i] := c
+    ELSIF (i = arena[n].nch) AND (arena[n].nch < MaxChild) THEN
+      arena[n].child[i] := c;
+      INC(arena[n].nch)
+    END
+  END SetChild;
+
+PROCEDURE WriteSp (d : CARDINAL);
+  VAR i: CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE i < d DO
+      FileIO.WriteString(FileIO.StdOut, "  "); INC(i)
+    END
+  END WriteSp;
+
+PROCEDURE IsLeafKind (k : INTEGER) : BOOLEAN;
+  BEGIN
+    RETURN (k = NkIdent) OR (k = NkIntLit) OR (k = NkRealLit)
+        OR (k = NkStrLit) OR (k = NkCharLit) OR (k = NkTypeIdent)
+        OR (k = NkImport)
+  END IsLeafKind;
+
+PROCEDURE Dump (n : Node; depth : CARDINAL);
+  VAR i: CARDINAL; s: ARRAY [0 .. MaxTxtLen] OF CHAR;
+  BEGIN
+    IF n = NoNode THEN
+      WriteSp(depth); FileIO.WriteString(FileIO.StdOut, "(nil)");
+      FileIO.WriteLn(FileIO.StdOut); RETURN
+    END;
+    WriteSp(depth);
+    FileIO.WriteString(FileIO.StdOut, "kind=");
+    FileIO.WriteInt(FileIO.StdOut, arena[n].kind, 1);
+    FileIO.WriteString(FileIO.StdOut, " op=");
+    FileIO.WriteInt(FileIO.StdOut, arena[n].op, 1);
+    FileIO.WriteString(FileIO.StdOut, " ty=");
+    FileIO.WriteInt(FileIO.StdOut, arena[n].ty, 1);
+    IF IsLeafKind(arena[n].kind) AND (arena[n].nch > 0)
+       AND (arena[n].child[0] # NoNode) THEN
+      Txt(VAL(TxtIndex, arena[n].child[0]), s);
+      FileIO.WriteString(FileIO.StdOut, " '");
+      FileIO.WriteString(FileIO.StdOut, s);
+      FileIO.WriteString(FileIO.StdOut, "'")
+    END;
+    FileIO.WriteLn(FileIO.StdOut);
+    i := 0;
+    WHILE i < arena[n].nch DO
+      IF NOT IsLeafKind(arena[n].kind) THEN
+        Dump(arena[n].child[i], depth + 1)
+      END;
+      INC(i)
+    END
+  END Dump;
+
+BEGIN
+  Init
+END AST.

+ 9 - 4
compiler/src/QbeGen.mod

@@ -1963,17 +1963,22 @@ PROCEDURE ElemCls (t: SymTab.TypeIndex): INTEGER;
   END ElemCls;
 
 PROCEDURE ElemSize (t: SymTab.TypeIndex): CARDINAL;
-(* Storage size of t's elements: CHAR 1, REAL 8, nested/pointer 8,
-   else 4. t is an array (or string) descriptor. *)
-  VAR cls: INTEGER;
+(* Storage size of t's elements: CHAR 1, REAL 8, nested/pointer/long 8,
+   records/classes/sets their inline footprint, else 4.  t is an array
+   (or string) descriptor. *)
+  VAR cls: INTEGER; et: SymTab.TypeIndex;
   BEGIN
     cls := SymTab.ClassOf(t);
     IF cls = SymTab.ClStr THEN RETURN 1 END;
-    cls := ElemCls(t);
+    et := SymTab.ArrayElem(t);
+    cls := SymTab.ClassOf(et);
     IF cls = SymTab.ClChar THEN RETURN 1
     ELSIF cls = SymTab.ClReal THEN RETURN 8
     ELSIF (cls = SymTab.ClArray) OR (cls = SymTab.ClPtr)
        OR (cls = SymTab.ClLong) THEN RETURN 8
+    ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass)
+       OR (cls = SymTab.ClSet) THEN
+      RETURN SymTab.TypeSize(et)
     ELSE RETURN 4
     END
   END ElemSize;

+ 46 - 0
compiler/tests/t_ast.mod

@@ -0,0 +1,46 @@
+MODULE TAst;
+// Stage-A self-test for the AST module: build a small tree and walk it.
+// Prints "ok=<n>"; exit 42 when all 10 checks pass.
+IMPORT AST, FileIO;
+
+VAR ExitCode : INTEGER;
+VAR root, e1, e2, e3 : AST.Node;
+VAR s : ARRAY [0..63] OF CHAR;
+VAR ok : INTEGER;
+
+BEGIN
+  ok := 0;
+  AST.Init;
+
+  e2 := AST.MakeLeaf(AST.NkIdent, "b");
+  e3 := AST.MakeLeaf(AST.NkIntLit, "2");
+  e1 := AST.MakeBin(AST.NkBinExpr, AST.OpAdd, e2, e3);
+  root := AST.MakeBin(AST.NkAssign, 0,
+             AST.MakeLeaf(AST.NkIdent, "a"), e1);
+
+  IF AST.Kind(root) = AST.NkAssign THEN INC(ok) END;
+  IF AST.NChild(root) = 2 THEN INC(ok) END;
+  IF AST.Kind(AST.Child(root, 0)) = AST.NkIdent THEN INC(ok) END;
+  IF AST.Kind(AST.Child(root, 1)) = AST.NkBinExpr THEN INC(ok) END;
+  IF AST.Op(AST.Child(root, 1)) = AST.OpAdd THEN INC(ok) END;
+  IF AST.Kind(AST.Child(AST.Child(root, 1), 1)) = AST.NkIntLit THEN
+    INC(ok)
+  END;
+
+  AST.Txt(AST.Child(AST.Child(root, 0), 0), s);
+  IF s[0] = "a" THEN INC(ok) END;
+  IF AST.AddTxt("b") = AST.AddTxt("b") THEN INC(ok) END;
+
+  e1 := AST.MakeUn(AST.NkUnary, AST.OpSub,
+           AST.MakeLeaf(AST.NkIntLit, "1"));
+  root := AST.MakeNode(AST.NkBlock);
+  IF AST.AddChild(root, e1) THEN INC(ok) END;
+  IF AST.AddChild(root, e2) THEN INC(ok) END;
+  IF AST.NChild(root) = 2 THEN INC(ok) END;
+
+  FileIO.WriteString(FileIO.StdOut, "ok=");
+  FileIO.WriteInt(FileIO.StdOut, ok, 1);
+  FileIO.WriteLn(FileIO.StdOut);
+
+  IF ok = 11 THEN ExitCode := 42 ELSE ExitCode := ok END
+END TAst.