nfl/syntax

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 isSymbol(sx: Syntax; name: string): bool {....raises: [], tags: [],
    forbids: [].}
True when sx is a symbol node whose name is exactly name.
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 newBool(value: bool; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Constructs an sxBool node.
proc newFloat(value: BiggestFloat; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Constructs an sxFloat node.
proc newInt(value: BiggestInt; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Constructs an sxInt node.
proc newList(items: seq[Syntax]; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Constructs an sxList node.
proc newNil(span: Span): Syntax {....raises: [], tags: [], forbids: [].}
Constructs an sxNil node.
proc newString(value: string; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Constructs an sxString node.
proc newSymbol(value: string; span: Span; hygieneId = 0; escaped = false): Syntax {.
    ...raises: [], tags: [], forbids: [].}
Constructs an sxSymbol node. hygieneId is nonzero for gensym'd or hygiene-renamed symbols; escaped marks a symbol read via the reader's |...| syntax.
proc newVector(items: seq[Syntax]; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Constructs an sxVector node.
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 span(file: string; line, col, endLine, endCol: int): Span {....raises: [],
    tags: [], forbids: [].}
Builds a Span from explicit start/end coordinates.
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.

proc withEnd(start: Span; endLine, endCol: int): Span {....raises: [], tags: [],
    forbids: [].}
Returns a copy of start with its end coordinates replaced — used to extend a span once a form's closing delimiter has been read.
proc withSpan(sx: Syntax; span: Span): Syntax {....raises: [], tags: [],
    forbids: [].}
Returns a deep copy of sx with its top-level span replaced.