mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-02 03:43:41 +00:00
`genProcBody` is handed `BNode(bodyBuf.rootCursor)` under `-d:newIcBackend`, so
`expr` and the ~160 procs under it read the routine body through a cursor rather
than a tree. This had to land as one change: `expr` dispatches to all of them, so
they move together or the dispatch converts at every node.
The evidence that it works is not that it compiles. Cursor-driven and
`PNode`-driven builds emit BYTE-IDENTICAL `.c` (50/50 on an 89k-line target,
12/12 on the grind target), the built program runs and prints the right thing,
and — the part that makes the first number mean something — sabotaging
`bnode.intVal` changes all 12 files. The generator is genuinely reading through
the cursor, not quietly falling back.
Four kinds of site could not simply take `AnyNode`, and each is marked where it
sits rather than left for the next person to rediscover:
* THE GENERATOR REWRITES. `mAppendSeqElem`, `mNewSeq`, `genSetLengthSeq`,
`genWasMoved` and `genArrToSeq` replace a child or a type IN PLACE, and
`genEnumToStr`/`mAsgn`/`spawn` build fresh trees. Those run on `origin(n)` —
the very node the buffer was encoded from — so the mutation lands exactly
where it always did. Where the mutation is then READ (`genArrToSeq` retypes a
bracket, `genArg` replaces a `var` param's type), generation continues on the
origin too, because the buffer does not see the write and a cursor would keep
reading the slot as encoded.
* NILABLE NODES stay `PNode`: a cursor has no standalone nil. That is the
assignment DESTINATION throughout the call family (`genCall` passes nil), the
`check` of an object-constructor field, `exvar`, `stepNode`, the `fin` of a
try statement.
* `PNode`-KEYED TABLES AND ANALYSES take `origin`: `dataCache`, `isPartOf`,
`lhsDoesAlias`, `potentialAlias`, the type-record walkers.
* SHARED PREDICATES in `ast.nim` cannot see `BNode`, so `skipHiddenAddr`,
`isInfixAs` and `getStr` join `canRaise`/`getInt` as templates instantiated
for both. `skipPragmaExpr` is a deliberate exception: it sits above the point
in `ast.nim` where `firstSon` for a `PNode` exists, so `bnode` carries a
one-line spelling with a pointer back.
Two Nim details worth recording. Repeated occurrences of a type class in one
signature share ONE implicit generic, so any proc whose two node parameters can
differ in representation needs explicit params — `genSingleVar`,
`genFieldObjConstr`, `callGlobalVarCppCtor`. And a `{.dirty.}` template inside a
generic resolves its identifiers at instantiation, so `genClosureCall`'s local
`rawProc` had to be bound before the template that uses it or it lost to the
module-level proc of the same name.
Verified: grind clean (1431 bodies, 260_431 nodes, 0 disagreements, origins
exact); the default path is byte-identical to HEAD; all four build
configurations compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR
911 lines
42 KiB
Nim
911 lines
42 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2026 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## `BNode` — the backend's node type, and the seam for running codegen off a
|
|
## `.bif` `Cursor` instead of a deserialized `PNode` tree.
|
|
##
|
|
## Building those trees is the bulk of the `lower` and `cg` stages: their cost
|
|
## tracks the size of the dependency CLOSURE a stage loads, not the module it
|
|
## compiles (measured: a 370-byte module costs 0.20s/0.16s in lower/cg, the main
|
|
## module 3.40s/3.16s, and the two are ~85% of the serial backend critical path).
|
|
##
|
|
## With `-d:newIcBackend` `BNode` is a `distinct Cursor`; without it a plain
|
|
## `PNode`, which is what every build does today. Codegen migrates to the
|
|
## vocabulary below one area at a time and the compiler keeps building
|
|
## throughout, because on the `PNode` side the vocabulary is what `ast`/`astdef`
|
|
## already provide — `kind`, `len`, `safeLen`, `sym`, `typ`, `info`, `firstSon`,
|
|
## `secondSon`, `lastSon` and the `sons`/`isons`/`sonsFrom`/`sonsButLast`/
|
|
## `isonsButLast` iterators all exist. This module does not redefine those for
|
|
## `PNode`; it adds only what the AST lacks (`son`, `hasSons`, `isNilNode`) and
|
|
## supplies the whole vocabulary on the `Cursor` side.
|
|
##
|
|
## THE NODE ENCODING. This is the part that a "a `Cursor` is just a tree
|
|
## position" reading gets wrong, and getting it wrong is silent. `ast2nif`'s
|
|
## writer emits every node through `withNode`, which is
|
|
##
|
|
## (<kind-tag> <flags> <type> <child 0> <child 1> ...)
|
|
##
|
|
## so a node's own flags and its `typ` occupy the FIRST TWO raw children and the
|
|
## AST's child 0 is the THIRD. `nifcore.childCursor` — and `ast2nif`'s own
|
|
## `firstSon(n: Cursor)`, which is a RAW structural accessor used to reach the
|
|
## name inside an `(sd ...)`/`(td ...)` — return the flags slot, not child 0.
|
|
## Every accessor below therefore steps over that two-token prefix. The
|
|
## exceptions are exactly:
|
|
##
|
|
## * `(none)` and a type-less `(empty)`, written bare with NO prefix and no
|
|
## children — so "zero raw children" means "no prefix", never "prefix but no
|
|
## AST children", which is what makes the two cases distinguishable at all;
|
|
## * an `nkSym`, which is not a `TagLit` node: it is a bare `Symbol` token, or
|
|
## `(ht <type> <sym>)` when the node's type differs from the symbol's, or
|
|
## `(nflags <flags> <symnode>)` when persisted node flags need saying, or an
|
|
## `(sd <name> ...)` definition;
|
|
## * a nil child, written as a `DotToken` — `isNilNode` is the `n == nil` of the
|
|
## `PNode` world, which has no `Cursor` counterpart.
|
|
##
|
|
## `len` follows `safeLen`'s rule and answers 0 for `nkNone..nkNilLit`: the
|
|
## payload token of an `nkIntLit`/`nkStrLit`/`nkIdent` sits where a child would,
|
|
## and is not one.
|
|
##
|
|
## `BNode` is DISTINCT from `Cursor` for that same reason. The two accessor sets
|
|
## are both spelled `firstSon` and differ by two positions, so a raw structural
|
|
## cursor reaching a call site that wants AST children must not silently
|
|
## typecheck — and the distinction also keeps `ast2nif.firstSon(n: Cursor)` from
|
|
## colliding with this module's.
|
|
##
|
|
## THE COST MODEL DIFFERS from a `PNode`'s, and that is what the vocabulary is
|
|
## shaped around. A `Cursor` is a position in a token stream, and a child is
|
|
## reached by stepping over each preceding sibling. Stepping is cheap — a
|
|
## `TagLit` token stores the width of its whole subtree, so `nifcore.skip` is a
|
|
## single pointer add regardless of how big that subtree is — but there is no
|
|
## random access and no way to walk backwards:
|
|
##
|
|
## * `firstSon` / `secondSon` / `son(n, k)` — O(k) in the NUMBER of preceding
|
|
## siblings, not in their size. Cheap for the small constant `k` that nearly
|
|
## all structural access in the cgen files uses.
|
|
## * `for x in sons(n)` / `sonsFrom(n, k)` / `sonsButLast(n, k)`, and the
|
|
## index-yielding `isons` / `isonsButLast` — one pass. ALWAYS use these for a
|
|
## loop: `for i in 0..<n.len: n[i]` is O(children^2). A loop that stops at a
|
|
## computed position walks forward and breaks
|
|
## (`for i, it in isons(n): if i >= casePos: break`) rather than counting up
|
|
## to the bound.
|
|
## * `lastSon(n)` — O(len), because nothing points backwards. Fine once, a trap
|
|
## inside a loop; `sonsButLast` is the loop form.
|
|
## * `len(n)` — O(len) too: it counts. Do not put it in a loop condition; use
|
|
## the iterators, or `hasSons` for an emptiness test.
|
|
##
|
|
## `kind` is the one accessor whose cost is not structural. A `.bif` carries its
|
|
## OWN tag pool, so a tag id means nothing outside the file it came from and
|
|
## there can be no process-global id -> `TNodeKind` table; the answer is
|
|
## memoized per pool instead, and the memo is dropped when the pool changes.
|
|
##
|
|
## RESOLUTION CONTEXT. `sym`, `typ` and `info` cannot be answered by the cursor
|
|
## alone: a `Symbol` token holds only a NAME, a type slot only a type's name,
|
|
## and a packed line info a `FileId` in the `.bif`'s own filename pool. All
|
|
## three need the decoder's state, and the decision recorded here is that they
|
|
## take it from AMBIENT STATE rather than from a parameter:
|
|
##
|
|
## * the `DecodeContext` is the process-wide `ast.program`, which already exists
|
|
## and is already what `ast.loadSym`/`loadType` resolve through;
|
|
## * the per-body half is a `bodynav.BodyNav` on a STACK, pushed by
|
|
## `withBodyScope` for the span of one routine body. It has to be a stack and
|
|
## not a single slot because generating one routine can pull in another
|
|
## (`genProcNoForward`) before the first is finished.
|
|
##
|
|
## Threading a `DecodeContext` parameter through the ~230 `PNode`-taking procs
|
|
## in the cgen files instead would be exactly the churn the `BNode` seam exists
|
|
## to avoid, and codegen is already single-threaded within a stage process
|
|
## (`--icBackendStage:cg --icBackendModule:X` compiles one module per process).
|
|
##
|
|
## The nav is not merely a renamed snapshot: it is a SCOPE CHAIN THE TRAVERSAL
|
|
## MAINTAINS (`openScope` / `closeScope` / `registerDefHere`), ported from
|
|
## Nimony's `typenav`, so a reader descending a body always has exactly the
|
|
## definitions it has already walked past and nothing is copied ahead of time.
|
|
## `bodynav`'s module doc has the measurement that says how much of a live
|
|
## problem the old snapshot was — the honest answer is "none yet" — and why the
|
|
## mechanism is still the right shape. `sym` goes through it; `typ` does not,
|
|
## because `ast2nif` already materialises types lazily from the module's type
|
|
## index and a frame has nothing to add.
|
|
##
|
|
## HOW THIS IS VERIFIED. Two oracles, neither of them a hand-written
|
|
## expectation:
|
|
##
|
|
## * the `isMainModule` self-test at the bottom of this file checks the
|
|
## accessors against the raw token stream of real `.bif` files. Necessary but
|
|
## NOT sufficient, and the reason is worth remembering: a vocabulary that is
|
|
## uniformly wrong — every accessor off by the same two positions — satisfies
|
|
## every accessor-against-accessor check there is. This test passed 5.2M
|
|
## assertions on exactly that wrong model.
|
|
## * `cgen.grindBNode` (opt-in, `NIM_IC_BNODE_GRIND=1` on an `--ic:on` build)
|
|
## walks the cursor and the materialised `PNode` for the SAME body in
|
|
## LOCKSTEP and requires `kind`, `len`, `info`, `flags`, the literal payloads,
|
|
## `sym` and `typ` to agree at every node of every routine body in the
|
|
## dependency closure — and, separately, requires each migrated `AnyNode` proc
|
|
## to return the same answer for the whole body. The `PNode` is the oracle, so
|
|
## it compares everything rather than what someone thought to check. That walk
|
|
## also DRIVES the nav — it opens a scope per node and offers each child to
|
|
## `registerDefHere` before descending — so what is graded is the vocabulary as
|
|
## a cursor-native pass would actually use it, not a random-access shortcut.
|
|
##
|
|
## The second oracle is what found the `typ` bug (a bare `Symbol` answered `nil`
|
|
## where `ast.typ` falls back to the symbol's type — which silently turns
|
|
## `canRaise` into "cannot raise" and drops the goto-exception check after a
|
|
## call) and the `.sons` bug named below. A clean run means nothing until you
|
|
## have broken an accessor on purpose and watched the grinder fire.
|
|
##
|
|
## The cgen files hold to one invariant, which is what makes the eventual flip
|
|
## mechanical: NO `[]` AND NO `.sons` ON A `PNode` OUTSIDE OF TREE
|
|
## CONSTRUCTION. `.sons` is not merely the un-portable spelling of the walk: it
|
|
## is the raw FIELD, so it bypasses the `len` hook that materializes a deferred
|
|
## `nfLazyBody` body, and `for x in n.sons` over a routine body that is still a
|
|
## placeholder silently visits NOTHING. `for x in sons(n)` goes through
|
|
## `safeLen` and materializes. (This is not hypothetical — it is what the
|
|
## differential grinder in `cgen.grindBNode` reported the first time it ran, and
|
|
## it had been sitting in `containsResult` itself.) Every read is
|
|
## `firstSon`/`secondSon`/`lastSon`/`son(n, k)` or one of the iterators; the
|
|
## remaining subscripts are writes that build a fresh `nkProcDef`
|
|
## (`theProc[namePos] = ...`), which a `Cursor` backend will not do at all, and
|
|
## accesses to a `PType`, a `string`, a `seq` or a `Table`, none of which are
|
|
## `BNode`s.
|
|
##
|
|
## A `PType` has its own vocabulary and its own reason for preferring it: `t[0]`
|
|
## is the return type, the base class, the index type or the generic head
|
|
## depending on the kind, and `ast.sons(t: PType)` is a `proc` returning the raw
|
|
## seq — NOT the iterator of the same name — which for a `tyProc` does not hold
|
|
## the parameters at all. Reach for `returnType` / `baseClass` / `elementType` /
|
|
## `genericHead` and the `kids` / `ikids` / `paramTypes` / `signature`
|
|
## iterators, which name the child and go through `[]`.
|
|
##
|
|
## WHERE THE LEAF MIGRATION ENDS. Of the 191 `PNode`-taking procs across
|
|
## `cgen` and the `ccg*` files, 160 EMIT — they take a `Builder`/`TLoc`
|
|
## out-param or write into `p` directly. Those do not move one at a time: they
|
|
## are one mutual recursion rooted at `expr`/`genStmts`, so the whole generator
|
|
## has to move together, and it needs write-side capability this seam does not
|
|
## have. What is left over splits into named blockers rather than a backlog, and
|
|
## each is recorded at its own site:
|
|
##
|
|
## * A type's RECORD TREE is not a body. `asgnComplexity`, `isEmptyCaseObjectBranch`,
|
|
## `containsOpaqueImportcFieldAux`, `genRecordFieldsAux`, `fillResult` and the
|
|
## type-section walkers read `PType.n`, which stays a `PNode` by design (see
|
|
## the `BType` note below). They are not migration candidates at all.
|
|
## * RETURNS A NODE OR NIL — `ccgutils.getPragmaStmt`. `.bif` spells a missing
|
|
## child as a `DotToken` INSIDE a tree; there is no nil token to hand back as a
|
|
## return value and a `Cursor` is not nilable. The fix is to split the
|
|
## predicate out, as `stmtsContainPragma` does.
|
|
## * WRITES TO THE NODE — `cgen.easyResultAsgn` does `incl n.flags, nfPreventCg`.
|
|
## The seam is read-only and a `Cursor` points into a shared token buffer.
|
|
## * NEEDS RENDERING — `ccgcalls.preventNrvo` interpolates `$le` into
|
|
## `warnObservableStores`. Reconstructing source text is a different job from
|
|
## reading a node, and only a diagnostic wants it.
|
|
## * NEEDS STABLE FIELD IDENTITY — `lhsDoesAlias` and `potentialAlias` through
|
|
## `aliases.isPartOf`, which compares field `sym.id`. See the note on `sym`
|
|
## below: it is not idempotent for fields, so this one is not blocked on
|
|
## effort, it is blocked on a property the seam does not currently have.
|
|
## * MIXED REPRESENTATION — `potentialAlias` and `getPotentialReads` build and
|
|
## consume a `seq[PNode]` alongside the node, so both sides would have to be
|
|
## the same spelling.
|
|
##
|
|
## There is deliberately no `BType` alongside `BNode`. Types stay `PType`s even
|
|
## under `newIcBackend` — `typ` below returns one — because the backend asks
|
|
## them semantic questions (`skipTypes`, `getSize`, `lengthOrd`, the record
|
|
## walk over `t.n`) that a raw cursor cannot answer. `ast2nif` already
|
|
## materializes them lazily from the module's type index, which is the seam
|
|
## that matters on that side.
|
|
|
|
import ast, lineinfos, idents
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std / assertions
|
|
|
|
when defined(newIcBackend):
|
|
import "../dist/nimony/src/lib/nifcore" except pool
|
|
import ic / enum2nif
|
|
import ast2nif, icnifcore, bodynav
|
|
export ast2nif.BodyScope
|
|
export bodynav
|
|
# Imported only under the define: `cgen` is compiled during the koch
|
|
# bootstrap, where the nimony libs are unavailable (`ast2nif` is guarded the
|
|
# same way). `nifcore` — NOT `nifcursors` — is the reader half of NIF: `bif`
|
|
# loads a `.bif` into a `nifcore.TokenBuf`, and `ast2nif` decodes it with a
|
|
# `nifcore.Cursor`. `nifcursors` is the writer/builder cursor over
|
|
# `PackedToken`s and is a different type entirely.
|
|
|
|
type BNode* = distinct Cursor
|
|
## A cursor at an AST NODE, as distinct from a raw structural cursor: see
|
|
## "THE NODE ENCODING" above. The conversion is deliberately not implicit.
|
|
|
|
template raw*(n: BNode): Cursor = Cursor(n)
|
|
## Escape hatch to the underlying structural cursor. Only this module and
|
|
## the decoder should need it.
|
|
|
|
proc toBNode*(c: Cursor): BNode {.inline.} = BNode(c)
|
|
## A raw cursor known to sit on an AST node — e.g. the body cursor
|
|
## `ast2nif.lazyBodyCursor` hands back.
|
|
|
|
# `nifcore` also has a `kind(c: Cursor): NifKind` — the TOKEN kind (TagLit,
|
|
# SymUse, IntLit, ...). It and `kind(n: BNode): TNodeKind` below differ only
|
|
# in return type, which Nim cannot overload on, so inside this module the
|
|
# nifcore one is always spelled `nifcore.kind`. Modules that import `bnode`
|
|
# do not import `nifcore`, so they see only the `TNodeKind` one.
|
|
|
|
const
|
|
LeafKinds* = {nkNone..nkNilLit}
|
|
## Exactly `astdef.safeLen`'s set: these have no children, and any token
|
|
## sitting after their flags/type prefix is a PAYLOAD (an int, a string,
|
|
## an ident), not a child.
|
|
|
|
var kindCachePool: TagPool = nil
|
|
var kindCache: seq[int16] = @[]
|
|
## `TagId -> TNodeKind` for ONE tag pool, -1 where not yet resolved.
|
|
## Not a process-global table: a `.bif` carries its OWN tag pool, so ids
|
|
## only mean anything relative to the pool the cursor came from. Codegen
|
|
## works through one module at a time, so a single-entry memo is enough;
|
|
## a pool switch just drops the cache.
|
|
|
|
proc tagKind(c: Cursor): TNodeKind =
|
|
## The `TNodeKind` a `.bif` TAG encodes — the inverse of `toNifTag`, which
|
|
## is what wrote it (`ast2nif`: `pool.tags.getOrIncl(toNifTag(n.kind))`).
|
|
## `parse` is a compare against ~180 strings, far too much per node, so the
|
|
## answer is memoized per tag id. The three wrapper tags that encode an
|
|
## `nkSym` are folded into the memo; `parse` answers `nkNone` for them (and
|
|
## for every non-AST tag, such as the module-level `(unusedid ...)`).
|
|
let pool = c.tags
|
|
if pool != kindCachePool:
|
|
kindCachePool = pool
|
|
kindCache = @[]
|
|
let id = int(uint32(cursorTagId(c)))
|
|
if id >= kindCache.len:
|
|
let oldLen = kindCache.len
|
|
kindCache.setLen(id + 1)
|
|
for i in oldLen ..< kindCache.len: kindCache[i] = -1'i16
|
|
if kindCache[id] < 0:
|
|
let name = pool.tagName(cursorTagId(c))
|
|
let k = if name == hiddenTypeTagName or name == symDefTagName or
|
|
name == symNodeFlagsTagName or name == bridgeSymTagName: nkSym
|
|
else: parse(TNodeKind, name)
|
|
kindCache[id] = int16(ord(k))
|
|
result = TNodeKind(kindCache[id])
|
|
|
|
proc kind*(n: BNode): TNodeKind =
|
|
## The node kind. A bare `Symbol`/`SymbolDef` token IS an `nkSym` node — it
|
|
## is how the common symbol use is written — and a `DotToken` is the nil
|
|
## child, which has no kind at all and answers `nkNone`; test it with
|
|
## `isNilNode` rather than comparing kinds.
|
|
case nifcore.kind(n.raw)
|
|
of TagLit: tagKind(n.raw)
|
|
of Symbol, SymbolDef: nkSym
|
|
else: nkNone
|
|
|
|
proc isNilNode*(n: BNode): bool {.inline.} =
|
|
## The `n == nil` of the `PNode` world: `ast2nif` writes a nil child as a
|
|
## `DotToken`, and a cursor is never itself nil.
|
|
nifcore.kind(n.raw) == DotToken
|
|
|
|
proc hasPrefix(c: Cursor): bool {.inline.} =
|
|
## Whether this `TagLit` carries the `withNode` flags/type prefix. `(none)`
|
|
## and a type-less `(empty)` are written bare, and they are the only nodes
|
|
## with zero raw children, so the test is exact.
|
|
cursorJump(c) > 0
|
|
|
|
proc astChildren(n: BNode): Cursor =
|
|
## A cursor at AST child 0, or an exhausted cursor when there is none.
|
|
## Steps over the two-token flags/type prefix; see "THE NODE ENCODING".
|
|
result = childCursor(n.raw)
|
|
if result.hasMore: skip result # flags
|
|
if result.hasMore: skip result # type
|
|
|
|
template walkChildren(n: BNode; c, body: untyped) =
|
|
## Shared guard for every child accessor: only a `TagLit` node that is not a
|
|
## leaf kind has children at all.
|
|
if nifcore.kind(n.raw) == TagLit and kind(n) notin LeafKinds:
|
|
var c = astChildren(n)
|
|
body
|
|
|
|
proc hasSons*(n: BNode): bool =
|
|
walkChildren(n, c):
|
|
return c.hasMore
|
|
result = false
|
|
|
|
proc son*(n: BNode; i: int): BNode =
|
|
## Child `i`. O(i) — `skip` is a single pointer add, because a `TagLit`
|
|
## token carries the width of its whole subtree.
|
|
walkChildren(n, c):
|
|
for _ in 0 ..< i:
|
|
doAssert c.hasMore, "son: index out of range"
|
|
skip c
|
|
doAssert c.hasMore, "son: index out of range"
|
|
return BNode(c)
|
|
raiseAssert "son: node has no children"
|
|
|
|
proc firstSon*(n: BNode): BNode {.inline.} = son(n, 0)
|
|
proc secondSon*(n: BNode): BNode {.inline.} = son(n, 1)
|
|
|
|
proc len*(n: BNode): int =
|
|
## Counts the children — O(len). Never put this in a loop condition; the
|
|
## iterators below and `hasSons` exist so it is not needed there. Follows
|
|
## `safeLen`: a leaf kind answers 0 even though its payload token is there.
|
|
result = 0
|
|
walkChildren(n, c):
|
|
while c.hasMore:
|
|
inc result
|
|
skip c
|
|
|
|
proc safeLen*(n: BNode): int {.inline.} = len(n)
|
|
## Same as `len`: `len` already answers 0 for a leaf, so the `PNode`
|
|
## distinction (`len` faults on a literal, `safeLen` does not) has nothing
|
|
## to guard here.
|
|
|
|
proc lastSon*(n: BNode): BNode =
|
|
## O(len) — the token stream has no back pointer. Fine once per node, a
|
|
## trap inside a loop; `sonsButLast` is the loop form.
|
|
walkChildren(n, c):
|
|
while c.hasMore:
|
|
result = BNode(c)
|
|
skip c
|
|
return result
|
|
raiseAssert "lastSon: node has no children"
|
|
|
|
iterator sons*(n: BNode): BNode =
|
|
walkChildren(n, c):
|
|
while c.hasMore:
|
|
yield BNode(c)
|
|
skip c
|
|
|
|
iterator sonsFrom*(n: BNode; start: int): BNode =
|
|
walkChildren(n, c):
|
|
for _ in 0 ..< start:
|
|
if not c.hasMore: break
|
|
skip c
|
|
while c.hasMore:
|
|
yield BNode(c)
|
|
skip c
|
|
|
|
iterator isons*(n: BNode; start = 0): tuple[i: int, n: BNode] =
|
|
walkChildren(n, c):
|
|
var i = 0
|
|
while i < start and c.hasMore:
|
|
skip c
|
|
inc i
|
|
while c.hasMore:
|
|
yield (i, BNode(c))
|
|
skip c
|
|
inc i
|
|
|
|
iterator sonsButLast*(n: BNode; count = 1): BNode =
|
|
## One pass with `count` nodes of lookahead — the token stream cannot be
|
|
## walked backwards, so the tail is held back instead of subtracted.
|
|
walkChildren(n, c):
|
|
var pending: seq[Cursor] = @[]
|
|
while c.hasMore:
|
|
pending.add c
|
|
skip c
|
|
if pending.len > count:
|
|
yield BNode(pending[0])
|
|
pending.delete(0)
|
|
|
|
iterator isonsButLast*(n: BNode; count = 1): tuple[i: int, n: BNode] =
|
|
walkChildren(n, c):
|
|
var pending: seq[Cursor] = @[]
|
|
var i = 0
|
|
while c.hasMore:
|
|
pending.add c
|
|
skip c
|
|
if pending.len > count:
|
|
yield (i, BNode(pending[0]))
|
|
inc i
|
|
pending.delete(0)
|
|
|
|
# ---- resolution: the three that need more than the cursor -----------------
|
|
|
|
var navStack {.threadvar.}: seq[BodyNav]
|
|
## Stack, not a single slot: generating one routine can pull in another
|
|
## before the first is finished. See "RESOLUTION CONTEXT" above.
|
|
|
|
proc pushBodyNav*(nav: sink BodyNav) =
|
|
navStack.add nav
|
|
|
|
proc popBodyNav*(): BodyNav {.discardable.} =
|
|
doAssert navStack.len > 0, "popBodyNav without a matching push"
|
|
result = navStack.pop()
|
|
|
|
template withBodyScope*(scope: BodyScope; body: untyped) =
|
|
## Runs `body` with a fresh `BodyNav` over `scope` current, so `sym`/`typ`
|
|
## inside it resolve the body's names. Pops even if `body` raises, because a
|
|
## codegen error is reported and compilation continues.
|
|
pushBodyNav(initBodyNav(scope))
|
|
try:
|
|
body
|
|
finally:
|
|
popBodyNav()
|
|
|
|
template withBridge*(tables: BridgeTables; body: untyped) =
|
|
## Read a bridged buffer: the nav resolves `(bsym …)` / `(btyp …)` straight
|
|
## out of `tables`. `base` stays empty on purpose — a bridged buffer names
|
|
## nothing, so there is nothing for the decoder to resolve, and leaving a
|
|
## fallback in place would turn a corrupt index into a confusing name
|
|
## lookup instead of the assertion it should be.
|
|
pushBodyNav(initBridgeNav(tables))
|
|
try:
|
|
body
|
|
finally:
|
|
popBodyNav()
|
|
|
|
proc currentNav*(): ptr BodyNav =
|
|
## The nav for the body being read. A `ptr` because the accessors below
|
|
## MUTATE it (the frame cache), and because copying a nav per access would
|
|
## defeat the point. Valid only until the next push — nothing between taking
|
|
## it and using it pushes, and nothing may: `symAt` bottoms out in the
|
|
## decoder, which never re-enters codegen.
|
|
doAssert navStack.len > 0,
|
|
"BNode.sym/typ outside withBodyScope: a Symbol token is a NAME and needs " &
|
|
"the owning module plus the body's scope to resolve"
|
|
result = addr navStack[^1]
|
|
|
|
proc openScope*(kind = nsBlock) {.inline.} = openScope(currentNav()[], kind)
|
|
proc closeScope*() {.inline.} = closeScope(currentNav()[])
|
|
proc registerDefs*(n: BNode) {.inline.} = registerDefs(currentNav()[], n.raw)
|
|
proc registerDefHere*(n: BNode) {.inline.} =
|
|
discard registerDefHere(currentNav()[], n.raw)
|
|
proc navStats*(): tuple[hits, fallbacks, registered: int] =
|
|
let nav = currentNav()
|
|
result = (nav.hits, nav.fallbacks, nav.registered)
|
|
|
|
template withNodeScope*(kind: NavScopeKind; body: untyped) =
|
|
## The traversal-side half of the nav: a walk brackets each scope-bearing
|
|
## construct with this, and the definitions it passes are registered as it
|
|
## goes. See `bodynav`.
|
|
openScope(kind)
|
|
try:
|
|
body
|
|
finally:
|
|
closeScope()
|
|
|
|
proc sym*(n: BNode): PSym =
|
|
## The `PSym` an `nkSym` node names, resolved through the current nav: its
|
|
## scope chain first, the decoder second. The wrapper forms
|
|
## (`(nflags <flags> <symnode>)`, `(ht <type> <symnode>)`) are peeled by
|
|
## `bodynav.symToken`, beside the code that derives the lookup key from them,
|
|
## so the two cannot drift apart.
|
|
##
|
|
## NOT IDEMPOTENT FOR OBJECT FIELDS, and anything built on this accessor has
|
|
## to know it. Two calls on the SAME token yield two different `skField`
|
|
## `PSym`s with consecutive item ids: field uses deliberately bypass the
|
|
## nav's memo and go to `loadFieldStub`, which mints per use because two
|
|
## distinct fields can share a name AND a position across types, so one
|
|
## shared stub would mistype one of them (see `bodynav`). For every other
|
|
## symbol kind the answer is stable — the nav memoises it — and `cgen`'s
|
|
## grinder asserts that for the non-field case at every node.
|
|
##
|
|
## The consequence is not theoretical. A proc that reads a field sym twice
|
|
## and compares IDENTITY is correct on a `PNode` and wrong on a `Cursor`:
|
|
## `aliases.isPartOf` does exactly that (`a[1].sym.id != b[1].sym.id`, to
|
|
## decide whether two accessor chains touch the same field) and so CANNOT be
|
|
## migrated as written. What codegen actually consumes for a field is the
|
|
## name it re-navigates the reclist with (`lookupFieldAgain`) plus, for
|
|
## tuples, the position — which is also the tolerance the grinder applies —
|
|
## so the fix is either to compare fields that way or to give a field token
|
|
## a stable identity. The latter needs the token's own position as a key,
|
|
## and `nifcore.Cursor` keeps that pointer private, so it is not something
|
|
## this module can do alone.
|
|
result = symAt(currentNav()[], n.raw)
|
|
|
|
proc symTyp(n: BNode): PType =
|
|
## The type of the symbol a sym-shaped node names, or nil.
|
|
let s = sym(n)
|
|
result = if s == nil: nil else: s.typ
|
|
|
|
proc typ*(n: BNode): PType =
|
|
## The node's type, INCLUDING the lazy fallback `ast.typ` performs.
|
|
##
|
|
## A sym node whose node type equals its symbol's is written as a bare
|
|
## `Symbol` with no type slot at all (`writeSymNode`), and the `PNode`
|
|
## loader marks such a node `nfLazyType` so `ast.typ` answers `n.sym.typ`.
|
|
## Answering `nil` here instead is not a *smaller* answer, it is a DIFFERENT
|
|
## one, and silently: `ast.canRaise` asks `fn.typ.kind == tyProc` about a
|
|
## call's callee, so a nil type turns "this call can raise" into "it cannot"
|
|
## and the goto-exception check after the call is dropped. The
|
|
## `.bif`-vs-`PNode` grinder found exactly that.
|
|
let c = n.raw
|
|
case nifcore.kind(c)
|
|
of Symbol, SymbolDef:
|
|
result = symTyp(n)
|
|
of DotToken:
|
|
result = nil
|
|
of TagLit:
|
|
let name = c.tags.tagName(cursorTagId(c))
|
|
if name == hiddenTypeTagName:
|
|
# `(ht <type> <sym>)`: the node type is spelled out because it differed
|
|
# from the symbol's at write time.
|
|
result = typeAt(currentNav()[], childCursor(c))
|
|
# A nil here is `(ht . <sym>)`, an EXPLICITLY nil node type, and it is
|
|
# answered as nil — the writer only emits the wrapper when the node's
|
|
# type differed from its symbol's, so nil means the node really had
|
|
# none. Do NOT fall back to `sym.typ`: a type symbol used as a value
|
|
# (`newException(KeyError, ...)`) is exactly this shape, and giving it
|
|
# the symbol's type makes sem read the typedesc as an expression of the
|
|
# type it denotes. That was tried, and it broke `--ic:on` compilation of
|
|
# anything instantiating `tables.[]`.
|
|
#
|
|
# `ast.typ` may still answer `sym.typ` here, because the loader's
|
|
# `nfLazyType` marking depends on whether the symbol happened to be
|
|
# loaded already (see `ast2nif`). That is a pre-existing load-order
|
|
# dependence in the AST, not a disagreement this side can resolve, and
|
|
# the grinder excludes this shape for that reason.
|
|
elif name == symNodeFlagsTagName:
|
|
var inner = childCursor(c)
|
|
skip inner
|
|
result = typ(BNode(inner))
|
|
elif name == symDefTagName or name == bridgeSymTagName:
|
|
# A bare `(bsym …)` is the bridge's spelling of a bare `Symbol`, so it
|
|
# answers the same thing: the symbol's own type. The bridge encoder
|
|
# always wraps a sym node in `(ht …)`, so this is the belt to that
|
|
# braces rather than a path it relies on.
|
|
result = symTyp(n)
|
|
elif not hasPrefix(c):
|
|
result = nil
|
|
else:
|
|
var t = childCursor(c)
|
|
skip t # the flags slot
|
|
result = typeAt(currentNav()[], t)
|
|
else:
|
|
result = nil
|
|
|
|
proc hasExplicitNilType*(n: BNode): bool =
|
|
## Whether this is the `(ht . <sym>)` shape — a sym node the writer gave an
|
|
## EXPLICITLY nil type — after peeling any `(nflags ...)` wrapper, which is
|
|
## how it usually arrives. See `typ` for why nil is the faithful answer and
|
|
## why `ast.typ` may nonetheless say otherwise.
|
|
var c = n.raw
|
|
while nifcore.kind(c) == TagLit:
|
|
let tag = c.tags.tagName(cursorTagId(c))
|
|
if tag == symNodeFlagsTagName:
|
|
var inner = childCursor(c)
|
|
skip inner # the node flags
|
|
c = inner
|
|
elif tag == hiddenTypeTagName:
|
|
return nifcore.kind(childCursor(c)) == DotToken
|
|
else:
|
|
return false
|
|
result = false
|
|
|
|
proc rawDesc*(n: BNode): string =
|
|
## What the token stream literally says here — the NIF token kind and, for a
|
|
## tag, its name. Diagnostics only: the vocabulary above is the interface,
|
|
## this is the thing it is an interface TO, and a disagreement between the
|
|
## two spellings is almost always explained by the raw shape.
|
|
let c = n.raw
|
|
result = $nifcore.kind(c)
|
|
case nifcore.kind(c)
|
|
of TagLit: result.add "/" & c.tags.tagName(cursorTagId(c))
|
|
of Symbol, SymbolDef: result.add "/" & symName(c)
|
|
else: discard
|
|
|
|
# ---- leaf payloads --------------------------------------------------------
|
|
#
|
|
# A literal is `(<kind> <flags> <type> <atom>)`: the value is the single token
|
|
# after the prefix. These are the accessors `ccgexprs` reaches for on nearly
|
|
# every expression node, so nothing in the expression codegen can migrate
|
|
# until they exist.
|
|
|
|
proc origin*(n: BNode): PNode =
|
|
## The `PNode` this cursor was encoded from, when it is reading a bridged
|
|
## buffer. This is what lets a cursor-driven generator keep filling
|
|
## `TLoc.lode` with a `PNode`: the answer is the SAME OBJECT the encoder was
|
|
## handed, so the identity comparisons the backend already does still hold.
|
|
##
|
|
## A node head on a bridged buffer always has an origin, so a miss is a bug
|
|
## rather than a shrug — most likely a cursor that is not at a node head.
|
|
## Reading a FILE-backed body has no origins at all and answers nil, which is
|
|
## correct: there is no `PNode` those tokens came from.
|
|
let b = currentNav().bridge
|
|
if b == nil: return nil
|
|
result = originAt(b, n.raw)
|
|
doAssert result != nil or nifcore.kind(n.raw) == DotToken,
|
|
"bridged node has no origin: " & rawDesc(n)
|
|
|
|
template origin*(n: PNode): PNode = n
|
|
## The `PNode` spelling, so `AnyNode` code can ask for an origin without
|
|
## caring which representation it holds.
|
|
|
|
proc atom(n: BNode): Cursor {.inline.} =
|
|
## The payload token of a leaf node.
|
|
result = astChildren(n)
|
|
|
|
proc intVal*(n: BNode): BiggestInt =
|
|
let c = atom(n)
|
|
case nifcore.kind(c)
|
|
of IntLit: result = BiggestInt(nifcore.intVal(c))
|
|
of UIntLit: result = cast[BiggestInt](nifcore.uintVal(c))
|
|
of CharLit: result = BiggestInt(ord(charLit(c)))
|
|
else: raiseAssert "intVal on " & rawDesc(n)
|
|
|
|
proc floatVal*(n: BNode): BiggestFloat =
|
|
let c = atom(n)
|
|
doAssert nifcore.kind(c) == FloatLit, "floatVal on " & rawDesc(n)
|
|
result = BiggestFloat(nifcore.floatVal(c))
|
|
|
|
proc strVal*(n: BNode): string =
|
|
let c = atom(n)
|
|
doAssert nifcore.kind(c) == StrLit, "strVal on " & rawDesc(n)
|
|
result = nifcore.strVal(c)
|
|
|
|
proc ident*(n: BNode): PIdent =
|
|
let c = atom(n)
|
|
doAssert nifcore.kind(c) == Ident, "ident on " & rawDesc(n)
|
|
result = identFromCursor(program, c)
|
|
|
|
proc flags*(n: BNode): TNodeFlags =
|
|
## The node's own flags, MINUS the two the `PNode` side owns rather than the
|
|
## file: `nfHasComment` is never written (comment text lives in a process-
|
|
## local side channel) and `nfLazyType` is a marker the loader adds to say
|
|
## "ask the symbol for my type" — which is what `typ` above does here
|
|
## unconditionally, so on a cursor the flag has nothing to mark.
|
|
let c = n.raw
|
|
case nifcore.kind(c)
|
|
of TagLit:
|
|
let name = c.tags.tagName(cursorTagId(c))
|
|
if name == symNodeFlagsTagName:
|
|
# `(nflags <flags> <symuse>)`: the wrapper carries the flags the bare
|
|
# `Symbol` token had nowhere to put.
|
|
var inner = childCursor(c)
|
|
result = nodeFlagsFromCursor(inner)
|
|
skip inner
|
|
result = result + flags(BNode(inner))
|
|
elif name == hiddenTypeTagName or name == symDefTagName or
|
|
name == bridgeSymTagName:
|
|
result = {}
|
|
elif not hasPrefix(c):
|
|
result = {}
|
|
else:
|
|
result = nodeFlagsFromCursor(childCursor(c))
|
|
else:
|
|
result = {}
|
|
|
|
proc lazyBodyBNode*(node: PNode; scope: var BodyScope; body: var BNode): bool =
|
|
## The cursor for a routine body that is still a deferred `nfLazyBody`
|
|
## placeholder, plus the scope its names resolve in. This is where a `BNode`
|
|
## comes FROM: until codegen is driven off `.bif` cursors end to end, it is
|
|
## the only supply, and it is what lets a migrated proc be run against the
|
|
## `PNode` proc it replaces on the same input. Non-destructive — the
|
|
## placeholder is still materializable afterwards.
|
|
var c: Cursor = default(Cursor)
|
|
result = lazyBodyCursor(program, node, scope, c)
|
|
if result: body = BNode(c)
|
|
|
|
proc info*(n: BNode): TLineInfo =
|
|
## No body scope needed: the packed line info resolves through the
|
|
## `.bif`'s own filename pool plus the `ConfigRef`.
|
|
result = lineInfoFromCursor(program, n.raw)
|
|
|
|
proc isAtom*(n: BNode): bool {.inline.} =
|
|
## `ast.isAtom`, which is a pure `kind` test and so needs nothing from the
|
|
## body scope. It exists here only because `ast.isAtom` is typed `PNode`;
|
|
## the predicate itself is the same one.
|
|
result = n.kind >= nkNone and n.kind <= nkNilLit
|
|
|
|
# ---- predicates shared with the `PNode` spelling ---------------------------
|
|
#
|
|
# `ast.canRaise` / `ast.canRaiseConservative` only ever look at a node's
|
|
# `kind`, `sym` and `typ`, all three of which this module now answers off a
|
|
# `Cursor`. They cannot be written as `AnyNode` procs in `ast.nim` because
|
|
# `BNode` is defined HERE and this module imports `ast`; so `ast.nim` keeps
|
|
# the body in a template and these two instantiate it. There is no second
|
|
# copy of the logic — change the template and both spellings change.
|
|
|
|
proc getInt*(n: BNode): Int128 = getIntImpl(n)
|
|
|
|
proc skipHiddenAddr*(n: BNode): BNode {.inline.} = skipHiddenAddrImpl(n)
|
|
|
|
proc isInfixAs*(n: BNode): bool = isInfixAsImpl(n)
|
|
|
|
proc getStr*(n: BNode): string = getStrImpl(n)
|
|
|
|
proc skipPragmaExpr*(n: BNode): BNode {.inline.} =
|
|
## The `BNode` spelling of `ast.skipPragmaExpr`.
|
|
(if n.kind == nkPragmaExpr: n.firstSon else: n)
|
|
|
|
proc canRaiseConservative*(fn: BNode): bool = canRaiseConservativeImpl(fn)
|
|
|
|
proc canRaise*(fn: BNode): bool = canRaiseImpl(fn)
|
|
|
|
# ---- mixed-mode plumbing --------------------------------------------------
|
|
#
|
|
# `son` and `hasSons` are the two vocabulary members the AST does not already
|
|
# have, so during the migration they must exist for BOTH node types: the cgen
|
|
# files are full of call sites that read a `PSym.ast`, a `PType.n` or a
|
|
# freshly built tree, and those stay `PNode`s no matter how far codegen has
|
|
# moved. Without these the define does not compile at all and no proc can be
|
|
# migrated incrementally.
|
|
|
|
template son*(n: PNode; i: int): PNode = n[i]
|
|
template hasSons*(n: PNode): bool = n.safeLen > 0
|
|
template isNilNode*(n: PNode): bool = n == nil
|
|
|
|
type AnyNode* = PNode | BNode
|
|
## The migration vehicle. A proc written against the vocabulary and typed
|
|
## `AnyNode` serves BOTH representations from one body, which means it can
|
|
## be migrated without a flag day and — more usefully — that the two
|
|
## instantiations can be run against each other on real input.
|
|
|
|
else:
|
|
type BNode* = PNode
|
|
type AnyNode* = PNode
|
|
|
|
# Only the three the AST does not already have. Everything else in the
|
|
# vocabulary is `ast`/`astdef`'s own `PNode` API — see the module doc.
|
|
template origin*(n: BNode): BNode = n
|
|
## No bridge in this build: a node IS its own origin.
|
|
|
|
template son*(n: BNode; i: int): BNode =
|
|
## Named indexed access. Exists so a call site states "child i" in a form
|
|
## that survives `BNode` becoming a `Cursor`; keep `i` small and constant.
|
|
n[i]
|
|
|
|
template hasSons*(n: BNode): bool =
|
|
## Emptiness test that does not compute a length — `len` counts on a
|
|
## `Cursor`.
|
|
n.safeLen > 0
|
|
|
|
template isNilNode*(n: BNode): bool =
|
|
## `n == nil`, in the form that survives the flip: a `Cursor` is never nil,
|
|
## and a nil child is a `DotToken` in the token stream.
|
|
n == nil
|
|
|
|
when isMainModule and defined(newIcBackend):
|
|
## Self-test for the `Cursor` half. The backend still runs on `PNode`s, so
|
|
## these accessors have no call sites that a normal build type-checks, let
|
|
## alone executes. Run it against real `.bif` files:
|
|
##
|
|
## nim c -d:newIcBackend compiler/bnode.nim
|
|
## ./compiler/bnode <nimcache>/*.bif
|
|
##
|
|
## Pass SEVERAL files — each `.bif` carries its own tag pool, so one file
|
|
## alone cannot catch a `kind` cache that fails to notice the pool changed.
|
|
##
|
|
## What this checks that the previous version could not: the accessors agree
|
|
## with the ENCODING, not merely with each other. Checking `sons` against
|
|
## `len` and `firstSon` is satisfied just as well by a vocabulary that is
|
|
## uniformly off by two — which is what it was, and this is what caught it.
|
|
##
|
|
## `sym`/`typ` are NOT exercised here: they resolve through `ast.program` and
|
|
## a `BodyScope`, i.e. a live compiler, so their checks belong to the backend
|
|
## and not to a standalone binary. What IS checked is every shape assumption
|
|
## they rest on — that `(ht ...)`/`(nflags ...)` have exactly two raw children
|
|
## with the symbol second, and that `(sd ...)` opens with a `SymbolDef`.
|
|
import std / [os, syncio, assertions]
|
|
from "../dist/nimony/src/lib" / bif import load, BifModule
|
|
|
|
var nodes = 0
|
|
var checks = 0
|
|
var astNodes = 0
|
|
|
|
proc walk(c: Cursor; base: TokenBuf) =
|
|
inc nodes
|
|
let n = BNode(c)
|
|
template pos(x: Cursor): int = cursorToPosition(base, x)
|
|
|
|
# Every RAW child, which is what the traversal follows: the AST model below
|
|
# deliberately does not see the module-level metadata tags, and a walk that
|
|
# only followed `sons` would never reach most of the file.
|
|
var rawKids: seq[Cursor] = @[]
|
|
if nifcore.kind(c) == TagLit:
|
|
var ch = childCursor(c)
|
|
while ch.hasMore:
|
|
rawKids.add ch
|
|
skip ch
|
|
|
|
doAssert isNilNode(n) == (nifcore.kind(c) == DotToken), "isNilNode"
|
|
inc checks
|
|
|
|
if nifcore.kind(c) in {Symbol, SymbolDef}:
|
|
doAssert kind(n) == nkSym, "a Symbol token is an nkSym node"
|
|
doAssert not hasSons(n), "a Symbol node has no children"
|
|
doAssert len(n) == 0, "a Symbol node has length 0"
|
|
inc checks, 3
|
|
|
|
if nifcore.kind(c) == TagLit:
|
|
let k = kind(n)
|
|
let name = c.tags.tagName(cursorTagId(c))
|
|
|
|
# The encoding invariants the whole vocabulary rests on.
|
|
if name == symDefTagName:
|
|
doAssert k == nkSym, "(sd ...) is an nkSym node"
|
|
doAssert rawKids.len > 0 and nifcore.kind(rawKids[0]) == SymbolDef,
|
|
"(sd ...) opens with a SymbolDef"
|
|
inc checks, 2
|
|
elif name == symNodeFlagsTagName or name == hiddenTypeTagName:
|
|
doAssert k == nkSym, name & " wraps an nkSym"
|
|
doAssert rawKids.len == 2, name & " is exactly (payload, symnode)"
|
|
doAssert nifcore.kind(rawKids[1]) in {Symbol, SymbolDef, TagLit},
|
|
name & "'s second child is the symbol"
|
|
inc checks, 3
|
|
elif k != nkNone:
|
|
# A real AST node written through `withNode`: either bare (no prefix and
|
|
# no children) or prefix + children. "Exactly one raw child" is
|
|
# impossible, and that is what pins the prefix down.
|
|
doAssert rawKids.len != 1,
|
|
"AST node " & name & " has a flags/type prefix or nothing at all"
|
|
inc checks
|
|
if rawKids.len == 0:
|
|
doAssert not hasSons(n), "bare " & name & " has no children"
|
|
doAssert len(n) == 0, "bare " & name & " has length 0"
|
|
inc checks, 2
|
|
else:
|
|
inc astNodes
|
|
let want =
|
|
if k in LeafKinds: newSeq[int]()
|
|
else: (block:
|
|
var s: seq[int] = @[]
|
|
for i in 2 ..< rawKids.len: s.add pos(rawKids[i])
|
|
s)
|
|
|
|
var listed: seq[int] = @[]
|
|
for ch in sons(n): listed.add pos(ch.raw)
|
|
doAssert listed == want,
|
|
"sons of " & name & " must start AFTER the flags/type prefix"
|
|
doAssert listed.len == len(n), "sons/len disagree"
|
|
doAssert (listed.len > 0) == hasSons(n), "hasSons/len disagree"
|
|
inc checks, 3
|
|
|
|
if listed.len > 0:
|
|
doAssert pos(firstSon(n).raw) == listed[0], "firstSon"
|
|
doAssert pos(lastSon(n).raw) == listed[^1], "lastSon"
|
|
inc checks, 2
|
|
if listed.len > 1:
|
|
doAssert pos(secondSon(n).raw) == listed[1], "secondSon"
|
|
inc checks
|
|
for i in 0 ..< listed.len:
|
|
doAssert pos(son(n, i).raw) == listed[i], "son " & $i
|
|
inc checks, listed.len
|
|
|
|
for start in 0 .. min(3, listed.len):
|
|
var got: seq[int] = @[]
|
|
for ch in sonsFrom(n, start): got.add pos(ch.raw)
|
|
doAssert got == listed[start .. ^1], "sonsFrom " & $start
|
|
var gotI: seq[int] = @[]
|
|
for i, ch in isons(n, start):
|
|
doAssert i == start + gotI.len, "isons index"
|
|
gotI.add pos(ch.raw)
|
|
doAssert gotI == listed[start .. ^1], "isons " & $start
|
|
inc checks, 2
|
|
|
|
for count in 1 .. 2:
|
|
let wantB = if listed.len > count: listed[0 ..< listed.len - count]
|
|
else: newSeq[int]()
|
|
var got: seq[int] = @[]
|
|
for ch in sonsButLast(n, count): got.add pos(ch.raw)
|
|
doAssert got == wantB, "sonsButLast " & $count
|
|
var gotI: seq[int] = @[]
|
|
for i, ch in isonsButLast(n, count):
|
|
doAssert i == gotI.len, "isonsButLast index"
|
|
gotI.add pos(ch.raw)
|
|
doAssert gotI == wantB, "isonsButLast " & $count
|
|
inc checks, 2
|
|
|
|
# `kind` against an uncached lookup — this is what catches a stale cache
|
|
# when the tag pool changes from one file to the next.
|
|
let direct =
|
|
if name == hiddenTypeTagName or name == symDefTagName or
|
|
name == symNodeFlagsTagName: nkSym
|
|
else: parse(TNodeKind, name)
|
|
doAssert k == direct, "kind"
|
|
inc checks
|
|
|
|
for ch in rawKids: walk(ch, base)
|
|
|
|
let files = commandLineParams()
|
|
if files.len == 0:
|
|
quit "usage: bnode <file.bif> [more.bif ...]"
|
|
for f in files:
|
|
var m = bif.load(f)
|
|
var c = beginRead(m.buf)
|
|
walk(c, m.buf)
|
|
endRead c
|
|
echo "bnode: files=", files.len, " nodes=", nodes, " astNodes=", astNodes,
|
|
" checks=", checks, " OK"
|