Appearance
aowlparser — Nim → AIF parser
▶️ Try
aoughwl/aowlparserlive in the Playground — clones the repo into the in-browser IDE, no install.
Pure-nimony recursive-descent parser: Nim source to parse-dialect AIF (.p.aif). Produces the same output as the classic compiler's nifler, but is self-hosted — no dependency on the classic Nim compiler, so it compiles through the nimony JS backend and runs in the browser. Output is byte-identical to nifler except the one header line it owns, (.vendor "aowlparser").
Scope
Purely syntactic. No semantic checking, no symbol resolution — every symbol is emitted as a bare identifier. The output is the parse dialect of AIF: a line-info-annotated s-expression of the Nim parse tree. Name binding, overload resolution and generic instantiation run in later phases (aowlsem onward) that read this file.
It exists in nimony so the browser playground can run the compiler frontend (source → aowlparser → aowlsem → aowli) client-side. nifler can't: it is built on the Nim-2 classic compiler and does not compile under nimony.
Conformance
Differential-tested against native nifler. Structural = token trees equal with line-info stripped; byte-exact = identical .p.aif including line-info, modulo the (.vendor) line.
| corpus | files | structural | byte-exact |
|---|---|---|---|
| nimony/src (compiler tree) | 184 | 184 | 184 |
| nimony/lib (stdlib) | 105 | 105 | 91 |
| upstream Nim/lib | 310 | 310 | 283 |
| curated | 172 | 172 | 156 |
0 crashes and 0 hangs across all four. The upstream Nim/lib pass — reached by differential fuzzing — covers term-rewriting template patterns, Inf/NaN hex-bit literals, custom numeric literals (1'big), method-chain continuations, multi-do calls and pragma-decorated lambda sugar. The line-info model was reverse-engineered per construct against the oracle; see Known gaps.
Diagnostics
Recoverable, unlike nifler's abort-on-first-error: records every error with a source span, keeps parsing, and still emits best-effort AIF — so one run surfaces every problem with no phantom end-of-file cascade. On the Nim compiler test corpus, on files where both report errors, nifler emits ~2× the error lines.
- Fix-its per error (
help: insert ':',help: did you mean '=='?). - Related locations as structured fields (a mismatched bracket points at both ends).
aowlparser check <file>lint mode;--diagnostics:jsonemits{severity, code, message, line, col, endCol, fix, related}per diagnostic.- Full classic-lexer error parity, recovering past each: bad char literals, illegal tabs, unterminated block/triple/raw strings, malformed escapes, malformed numbers (including an exponent with no digits like
1e, and a C/Java/JS suffix or stray letter glued to a number like100L/100n— Nim's typed literal uses an apostrophe,100'i64), unterminated accent-quoted identifiers. - Detections
niflerlacks: assignment-in-condition (if/elif/while/when x = 5:), comparison-in-binding (its mirror —let/const x == 5), the cross-language habitslet x := 5(Pascal/Go walrus),proc f() -> int(Rust/Python-3/C++ return arrow),std::vector(C++ scope resolution),proc f<T>()(C++/Java/Rust/TS angle-bracket generics),let mut x(the Rust mutable-binding habit — Nim's isvar),var x int(the Go/Java/C#/Swiftname typebinding, missing Nim's:),fn/function/fun name() { … }(a routine written with a Rust/JS/Kotlin function keyword and a brace body),class/struct/interface/impl/trait/namespace/module name { … }(an OO/type/module block from another language — Nim types aretype Name = objectand modules are files),switch/match x { … }(Nim's iscase x:withofbranches), a C/JSdo { } whileloop and Rubydo |x|block params, a C-style/* … */block comment (Nim's is#[ … ]#), a Javathrowsor Rust/Swift/C#where/override/noexceptroutine clause (Nim uses a{.raises.}pragma,[T: Constraint]andmethod), a Java/TStype Foo extends Barinheritance clause (Nim's isobject of Bar),yield from xs(Nim iterates and yields:for x in xs: yield x), anasync procprefix (Nim marks async with a{.async.}pragma), a strayend(Ruby/Pascal/Lua block terminator) and a C-style{ }body;else ifused forelif, empty conditions (elif:), empty comma slots (foo(a,,b)), missing-introducer bodies (proc f()then an indented line with no=;type Namewith a body but no= object), and precise grammar diagnostics wherenifleris terse:funcin a type description, a keyword where an enum member belongs, an empty object-variant branch,of/formissing their value (of:,for x of xs). - UTF-8 identifiers, and
#? stdtmplfilter files (recognized as non-Nim). - Opt-in idiomatic lints on valid code (off by default, so the zero-FP corpus stays clean):
--idioms:warnflags a redundant bool comparex == true/x != false(redundant-bool-literal), anot not xdouble negation (double-negation), and the precedence trapsnot x in y→ parses as(not x) in ywhenx notin ywas meant (not-in-precedence) andnot x == y→ parses as(not x) == ywhenx != ywas meant (not-compare-precedence), and anif c: return true else: return falsethat just returns the condition (simplify-boolean-return, the one structural check — it also matches theresult =and bool-swapped variants and stays silent on any richer branch);--float-equality:warn(also folded into aowlsuggest's--pedantic) flags an exact==/!=against a float literal (float-equality). Twelve further opinion checks are off by default and meant to be turned on per project (via aowlsuggest's[rules]), each with its own flag:--nil-comparison:warnflagsx == nil(a project may preferisNil);--yoda:warnflags a literal on the left of a compare (0 == x);--redundant-parens:warnflagsif (cond):— a condition wrapped entirely in parentheses Nim doesn't need (never a load-bearing group like(let y = f(); …)or a sub-expression(a or b) and c);--empty-string:warnflagss & ""(concatenating an empty string is a no-op);--debug-echo:warnflags a statement-positionecho(a debug print a project may want out of committed code);--range-index:warnflags0 .. n - 1(Nim's half-open0 ..< nsays the same with no off-by-one);--broad-exception:warnflagsexcept Exception/newException(Exception, …)(too broad — it also catches Defects; preferCatchableErroror a specific type);--bare-except:warnflags a bareexcept:(catches everything, Defects included);--cast:warnflagscast[T](x)(an unchecked reinterpret a project may want to audit); and--converter:warnflags aconverterdefinition (implicit conversions surprise overload resolution);--addr:warnflagsaddr/unsafeAddr(a raw address-of that escapes memory safety); and--asm:warnflags an inlineasmblock (non-portable and unchecked). These arehints — the code compiles, it just isn't how a Nim programmer would write it — and each is exactly the pattern it names (184 bool compares and 91 float compares surface across the 599-file corpus, alongside 1012 nil compares, 200.. n - 1ranges, 20 fully-wrapped conditions, 9 broad + 93 bareExceptions, 2233 casts and 1212 address-of ops; thenot … intrap,not notands & ""are absent from valid code but caught when they appear — with zero error-level diagnostics).
The error-level checks are proven zero-false-positive against the 599 valid files; the opt-in idiom hints fire only on the precise construct they name. No check ever changes the emitted AIF.
Pages
| Page | Covers |
|---|---|
| Architecture | fused parse+emit, range-splitter, include-file module map, line-info model, nifler oracle |
| Grammar coverage | lexer / expression / statement / section / type constructs reproduced |
| Dialects | nine byte-exact front ends on one core — nim, css, html, py, js, json, vds, md, yaml |
| JSON reader | jsonfast: 963–2248 MB/s, held to CPython on 10k files and 494k prefixes |
| The .p.aif format | header directives, base62 line-info suffix, operator escaping, tag vocabulary |
| Browser & JS | client-side build, the globalThis.__np_* contract, webdiag |
| Differential testing | oracle harness, canon.py, structural vs byte-exact |
| Configuration | --curly block bodies, whitespace policy switches |
| Known gaps | remaining corpus edge cases |

