The Syntax s-expression tree produced by the reader and consumed by the macro expander, lowering, and backend passes, plus the Span source-range type attached to every node for diagnostics.
Types
Span = object file*: string line*, col*: int endLine*, endCol*: int
- A source range, used to anchor diagnostics.
Syntax = ref object span*: Span case kind*: SyntaxKind of sxNil: nil of sxBool: boolVal*: bool of sxInt: intVal*: BiggestInt of sxFloat: floatVal*: BiggestFloat of sxString: strVal*: string of sxSymbol: sym*: string hygieneId*: int escaped*: bool ## true when read via the reader's `|...|` escaped-symbol ## syntax — marks `sym` as literal, so a trailing `*` ## is never interpreted as an export marker (#46). of sxList, sxVector: items*: seq[Syntax]
- A single s-expression node.
SyntaxKind = enum sxNil, sxBool, sxInt, sxFloat, sxString, sxSymbol, sxList, sxVector
- The shape of a Syntax node.
Procs
proc blockLabelName(sx: Syntax): string {....raises: [], tags: [], forbids: [].}
- The label name with its leading : stripped. Only valid to call when isBlockLabel(sx) holds.
proc copySyntax(sx: Syntax): Syntax {....raises: [], tags: [], forbids: [].}
- Deep-copies sx and its children, preserving spans.
proc isBlockLabel(sx: Syntax): bool {....raises: [], tags: [], forbids: [].}
- True for a :name symbol used as a block/break-from label — e.g. the :search in (block :search …) or (break-from :search expr). Requires a symbol strictly longer than the bare : (which is reserved as the named-argument marker, see isNamedArg) whose first character is : and whose remainder contains no further : or *.
proc isCaseValueList(sx: Syntax): bool {....raises: [], tags: [], forbids: [].}
-
True when a parenthesized form after of should be read as a multi-value / mixed value-and-range list ((of (1 (.. 3 5) 7) body…)) rather than a range ((of (.. lo hi) body…)).
There is no syntactic way to distinguish a value list from a compound call expression — (+ 1 2) and (1 2) have the same shape, and a value list of symbols (e.g. (Red Green) enum labels) looks exactly like a call. Per the ticket, any non-empty, non-range-shaped list after of is read as a value list; a single computed value must be wrapped, e.g. (of ((+ 1 2)) body…).
proc isOperatorName(s: string): bool {....raises: [], tags: [], forbids: [].}
- True when s is non-empty and every character is a Nim operator character — the shape Nim requires an accent-quoted proc name for (e.g. backtick-quoted +), as opposed to a plain identifier.
proc isPlainIdentifier(s: string): bool {....raises: [], tags: [], forbids: [].}
- True for an ordinary Nim-style identifier: starts with a letter or underscore, followed by letters/digits/underscores.
proc isRangeForm(sx: Syntax): bool {....raises: [], tags: [], forbids: [].}
- True for (.. lo hi) — the range form used by for loops and, per ticket #22, case of-branches ((of (.. lo hi) body…)).
proc isRangeShaped(sx: Syntax): bool {....raises: [], tags: [], forbids: [].}
- True for any list headed by the .. symbol, regardless of arity. Used to recognize (and validate the arity of) an intended range form — distinct from isRangeForm, which also checks the arity is exactly 2.
proc isSetterName(s: string): bool {....raises: [], tags: [], forbids: [].}
- ident= — Nim's setter-proc name form, callable as self.foo = v when s is foo=. Requires at least one base character before the trailing = so the base is itself a plain identifier.
proc isValidRoutineName(s: string): bool {....raises: [], tags: [], forbids: [].}
- A routine (proc/method/func/…) name must be a plain identifier (optionally with a trailing * export marker), a setter name (ident=, see #75), or entirely operator characters (see #29) — not a mix of plain-identifier and operator characters otherwise, which Nim cannot express as an ident, an nnkAccQuoted operator, or a setter.
proc renderSyntax(sx: Syntax): string {....raises: [], tags: [], forbids: [].}
- Renders sx back to NFL source text, escaping symbols and strings as needed so the result reads back to an equivalent Syntax tree.
proc sameSyntax(a, b: Syntax): bool {....raises: [], tags: [], forbids: [].}
- Structural equality: same kind and same value(s), recursively for lists/vectors. Symbols must also share a hygieneId.
proc splitExportMarker(sym: string; escaped, allowOperator: bool): tuple[ base: string, exported: bool, err: string] {....raises: [], tags: [], forbids: [].}
-
Determines whether sym's trailing * is an export marker or part of the name itself, and splits accordingly. err is non-empty when the name is malformed; callers attach a span and raise. (Named err, not error — error collides with diagnostics.error, the proc that builds a Diagnostic, imported into every caller.)
escaped is true for a name read via the reader's |...| syntax (#46) — inside |...| a trailing * is never an export marker, so |**| is the unexported two-char operator ** rather than exported *. An escaped non-operator name ending in * (e.g. |foo*|) is rejected: there is no way to apply an export marker inside |...|.
allowOperator (routine names only — see #29) additionally accepts operator names (+, +*, **, …) when unescaped, which are made entirely of operator characters, so a trailing * is ambiguous between "the operator itself" and "export marker". The marker only applies when stripping it leaves a nonempty operator name — +* is exported +, ** is exported *, but a bare * is the unexported * operator.