[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  build:\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        node-version: [24.x]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v4\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: npm i\n    - run: npm run build --if-present\n    - run: npm test\n\n\n  build-go:\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: '1.24'\n    - name: Build\n      run: go build ./...\n      working-directory: ./go\n    - name: Test\n      run: go test ./...\n      working-directory: ./go\n\n"
  },
  {
    "path": ".gitignore",
    "content": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n\npids\nlogs\nresults\n\nnpm-debug.log\n\n*~\nnode_modules\n\n.idea/\n\n\ntrial\n\ntest/coverage.html\n\ncoverage\n\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": "CNAME",
    "content": "jsonic.richardrodger.com"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013-2020 Richard Rodger\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: all build test clean build-ts build-go test-ts test-go clean-ts clean-go publish-go tags-go reset\n\nall: build test\n\nbuild: build-ts build-go\n\ntest: test-ts test-go\n\nclean: clean-ts clean-go\n\n# TypeScript\nbuild-ts:\n\tnpm run build\n\ntest-ts:\n\tnpm test\n\nclean-ts:\n\trm -rf dist dist-test\n\n# Go\nbuild-go:\n\tcd go && go build ./...\n\ntest-go:\n\tcd go && go test -v ./...\n\nclean-go:\n\tcd go && go clean\n\n# Publish Go module: make publish-go V=0.1.7\npublish-go: test-go\n\t@test -n \"$(V)\" || (echo \"Usage: make publish-go V=x.y.z\" && exit 1)\n\tsed -i '' 's/^const Version = \".*\"/const Version = \"$(V)\"/' go/jsonic.go\n\tsed -i '' 's/^Version: .*/Version: $(V)/' go/README.md\n\tgit add go/jsonic.go go/README.md\n\tgit commit -m \"go: v$(V)\"\n\tgit tag go/v$(V)\n\tgit push origin main go/v$(V)\n\tif command -v gh >/dev/null 2>&1; then gh release create go/v$(V) --title \"go/v$(V)\" --notes \"Go module release v$(V)\"; fi\n\ntags-go:\n\tgit tag -l 'go/v*' --sort=-version:refname\n\nreset:\n\tnpm run reset\n\tcd go && go clean -cache\n\tcd go && go build ./...\n\tcd go && go test -v ./...\n"
  },
  {
    "path": "README.md",
    "content": "# jsonic\n\nJSON is great. JSON parsers are not. They punish you for every missing\nquote and misplaced comma. You're a professional -- you know what you\nmeant. jsonic knows too.\n\n```\na:1,foo:bar  →  {\"a\": 1, \"foo\": \"bar\"}\n```\n\nIt's a JSON parser that isn't strict. And it's very, very extensible.\n\nAvailable for [TypeScript/JavaScript](#install) and [Go](go/).\n\n## Install\n\n```bash\nnpm install jsonic\n```\n\n## Quick Example\n\n```js\nconst { Jsonic } = require('jsonic')\n\n// Relaxed syntax, just works\nJsonic('a:1, b:2')           // {\"a\": 1, \"b\": 2}\nJsonic('x, y, z')            // [\"x\", \"y\", \"z\"]\nJsonic('{a: {b: 1, c: 2}}') // {\"a\": {\"b\": 1, \"c\": 2}}\n```\n\n```ts\nimport { Jsonic } from 'jsonic'\n\nJsonic('a:1, b:2') // {\"a\": 1, \"b\": 2}\n```\n\n## What Syntax Does jsonic Accept?\n\nMore than you'd expect. All of the following parse to `{\"a\": 1, \"b\": \"B\"}`:\n\n```\na:1,b:B\n```\n\n```\na:1\nb:B\n```\n\n```\na:1\n// a:2\n# a:3\n/* b wants\n * to B\n */\nb:B\n```\n\n```\n{ \"a\": 100e-2, '\\u0062':`\\x42`, }\n```\n\nThat last one mixes double quotes, single quotes, backticks, unicode\nescapes, hex escapes, and scientific notation. It doesn't matter. jsonic\nhandles it.\n\nHere's the full set of relaxations:\n\n- **Unquoted keys and values**: `a:1` &rarr; `{\"a\": 1}`\n- **Implicit top-level object**: `a:1,b:2` &rarr; `{\"a\": 1, \"b\": 2}`\n- **Implicit top-level array**: `a,b` &rarr; `[\"a\", \"b\"]`\n- **Trailing commas**: `{a:1,b:2,}` &rarr; `{\"a\": 1, \"b\": 2}`\n- **Single-quoted strings**: `'hello'` works like `\"hello\"`\n- **Backtick strings**: `` `hello` `` works like `\"hello\"`\n- **Multiline strings**: backtick strings preserve newlines\n- **Indent-adjusted strings**: `'''...\\n'''` trims leading indent\n- **Comments**: `//`, `#` (line), `/* */` (block)\n- **Object merging**: `a:{b:1},a:{c:2}` &rarr; `{\"a\": {\"b\": 1, \"c\": 2}}`\n- **Path diving**: `a:b:1,a:c:2` &rarr; `{\"a\": {\"b\": 1, \"c\": 2}}`\n- **All number formats**: `1e1 === 0xa === 0o12 === 0b1010`, plus `1_000` separators\n- **Auto-close at EOF**: unclosed `{` or `[` close automatically\n\nFor the full syntax reference, see [doc/syntax.md](doc/syntax.md).\n\n## Customization\n\nYou might be tempted to think a lenient parser is a simple thing. It\nisn't. jsonic is built around a rule-based parser and a matcher-based\nlexer. Both are fully customizable through options and plugins. You can\nchange almost anything about how parsing works -- and you don't have to\nunderstand the internals to do it.\n\n### Options\n\nLet's start simple. Create a configured instance with `Jsonic.make()`:\n\n```js\nconst lenient = Jsonic.make({\n  comment: { lex: false },         // disable comments\n  number: { hex: false },          // disable hex numbers\n  value: {\n    def: { yes: { val: true }, no: { val: false } }\n  }\n})\n\nlenient('yes')  // true\n```\n\nOptions compose. You turn things off, you turn things on, you define new\nvalue tokens. That's it.\n\nSee [doc/options.md](doc/options.md) for the full options reference.\n\n### Plugins\n\nWhen options aren't enough, plugins let you reach deeper. They can\nmodify the grammar, add matchers, or hook into parse events:\n\n```js\nfunction myPlugin(jsonic, options) {\n  // Register a custom fixed token\n  jsonic.options({ fixed: { token: { '#TL': '~' } } })\n  const T_TILDE = jsonic.token('#TL')\n\n  // Modify grammar rules\n  jsonic.rule('val', (rs) => {\n    rs.open([{\n      s: [T_TILDE],\n      a: (rule) => { rule.node = options.tildeValue ?? null }\n    }])\n  })\n}\n\nconst j = Jsonic.make()\nj.use(myPlugin, { tildeValue: 42 })\nj('~')  // 42\n```\n\nConsider what just happened: we invented a new syntax element (`~`),\ntold the parser what to do when it encounters one, and wired it up with\na configurable value. The parser itself doesn't care what symbols you\nuse. It only cares about rules.\n\nSee [doc/plugins.md](doc/plugins.md) for the plugin authoring guide.\n\n## API Reference\n\nSee [doc/api.md](doc/api.md) for the full API.\n\nThe essentials:\n\n| Function / Property | Description |\n|---|---|\n| `Jsonic(src)` | Parse a string with default settings |\n| `Jsonic.make(options?)` | Create a configured parser instance |\n| `instance.use(plugin, opts?)` | Register a plugin |\n| `instance.rule(name, definer)` | Modify a grammar rule |\n| `instance.token(ref)` | Get or create a token type |\n| `instance.sub({lex?, rule?})` | Subscribe to parse events |\n| `instance.options` | Current options |\n\n## Go Version\n\nThere's a Go port with the same core parsing behavior. Same syntax,\nsame relaxations, same results. See the [Go documentation](go/) for\ninstallation and usage.\n\n```go\nimport \"github.com/jsonicjs/jsonic/go\"\n\nresult, err := jsonic.Parse(\"a:1, b:2\")\n```\n\n## License\n\nMIT. Copyright (c) Richard Rodger.\n\n"
  },
  {
    "path": "TODO.md",
    "content": "# TODO\n\n* P1; exception inside matcher needs own error code - too easy to miss!\n* P1; remove console colors in browser? option\n* P2; quotes are value enders - x:a\"a\" is an err! not 'a\"a\"', option?\n* P2: fix type chaining with jsonic.rule\n* P3; Consider: option to control comma null insertion\n* P3; YAML quoted strings: https://yaml-multiline.info/ - via options \n  * provide in yaml plugin\n* P3; cli - less ambiguous merging at top level\n* P3; consistent use of clean on options to allow null to mean 'remove property'\n* P3; data file to diff exhaust changes\n* P3; define explicitly: p in close, r in open, behaviour \n  * r in open means a run of opens with one close - see TOML - needs unit test \n  * p in close means ??? - needs unit test\n* P3; docs: nice tree diagram of rules (generate?)\n* P3; docs: ref https://wiki.alopex.li/OnParsers\n* P3; document standard g names: open, close, step, start, end, imp, top, val, map, etc\n* P3; error if fixed tokens clash\n* P3; http://seriot.ch/projects/parsing_json.html \n  * implement as tests\n* P3; if token recognized, error needs to be about token, not characters\n* P3; implicit lists in pair values: \"a:1,2 b:3\" -> {a:[1,2], b:3} - pair key terminates (A)\n* P3; internal errors - e.g. adding a null rulespec\n* P3; is s:[] needed? different from s:undefined ?\n* P3; line continuation (\"\\\" at end) should be a feature of standard JSONIC text\n* P3; option for sparse arrays: https://dmitripavlutin.com/javascript-sparse-dense-arrays/\n* P3; perhaps remove the # prefix from token names?\n* P3; rename tokens to be user friendly - maybe?\n* P3; specific error if rule name not found when parsing\n* P3; string format for rule def: s:'ST,NR' -> s:[ST,NR], also \"s:ST,NR,p:foo,...\" - needs (A) - can only used post standard definition (thus not in grammar.ts)\n* P3; support BigInt numbers: 123n\n* P3; unit test for custom alt error: eg.  { e: (r: Rule) => r.close[0] } ??? bug: r.close empty!\n\n\n"
  },
  {
    "path": "bin/jsonic",
    "content": "#!/usr/bin/env node\nrequire('../dist/jsonic-cli').run(process.argv, console).catch((e) => console.error(e.message))\n"
  },
  {
    "path": "bin/jsonic-bnf",
    "content": "#!/usr/bin/env node\nrequire('../dist/jsonic-bnf-cli').run(process.argv, console).catch((e) => console.error(e.message))\n"
  },
  {
    "path": "dist/bnf.d.ts",
    "content": "import type { BnfConvertOptions, GrammarSpec, Rule } from './types';\ntype BnfElement = {\n    kind: 'term';\n    literal: string;\n    caseSensitive?: boolean;\n} | {\n    kind: 'ref';\n    name: string;\n} | {\n    kind: 'regex';\n    pattern: string;\n    flags: string;\n} | {\n    kind: 'opt';\n    inner: BnfElement;\n} | {\n    kind: 'star';\n    inner: BnfElement;\n} | {\n    kind: 'plus';\n    inner: BnfElement;\n} | {\n    kind: 'rep';\n    min: number;\n    max: number;\n    inner: BnfElement;\n} | {\n    kind: 'group';\n    alts: BnfSequence[];\n};\ntype BnfSequence = BnfElement[];\ntype BnfProduction = {\n    name: string;\n    alts: BnfSequence[];\n    incremental?: boolean;\n    probeDispatch?: ProbeDispatchSpec;\n    probeHelper?: {\n        vocabElements: BnfElement[];\n    };\n    nodeKind?: 'user' | 'core' | 'helper';\n};\ntype ProbeDispatchSpec = {\n    probeRule: string;\n    disambiguator: BnfElement;\n    withBranch: string;\n    noBranch: string;\n};\ntype BnfGrammar = {\n    productions: BnfProduction[];\n    ambiguities?: AmbiguityReport[];\n};\ntype AmbiguityReport = {\n    rule: string;\n    altIdx: number;\n    optIdx: number;\n    reason: string;\n    resolved: boolean;\n};\ndeclare const bnfRules: Record<string, {\n    bo?: (r: Rule) => void;\n    bc?: (r: Rule) => void;\n    open?: any[];\n    close?: any[];\n}>;\ndeclare function eliminateLeftRecursion(grammar: BnfGrammar): BnfGrammar;\ndeclare class BnfParseError extends Error {\n    readonly line?: number;\n    readonly column?: number;\n    readonly cause?: unknown;\n    constructor(message: string, location?: {\n        line?: number;\n        column?: number;\n    }, cause?: unknown);\n}\ndeclare function parseBnf(src: string): BnfGrammar;\ndeclare function emitGrammarSpec(grammar: BnfGrammar, opts?: BnfConvertOptions): GrammarSpec;\ndeclare function bnf(src: string, opts?: BnfConvertOptions): GrammarSpec;\nexport { bnf, parseBnf, emitGrammarSpec, eliminateLeftRecursion, bnfRules, BnfParseError, };\n"
  },
  {
    "path": "dist/bnf.js",
    "content": "\"use strict\";\n/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.BnfParseError = exports.bnfRules = void 0;\nexports.bnf = bnf;\nexports.parseBnf = parseBnf;\nexports.emitGrammarSpec = emitGrammarSpec;\nexports.eliminateLeftRecursion = eliminateLeftRecursion;\n// Declarative definition of the BNF grammar itself, expressed as\n// jsonic rules. Each rule names its `open`/`close` alt list and, where\n// necessary, a `bo`/`bc` state hook for AST assembly.\n//\n// Stage 8: incremental alternatives via `name =/ alt` now fold\n// into the earlier production with the same name. Quoted strings\n// default to case-insensitive (ABNF semantics), `%s` / `%i` force\n// sensitivity explicitly, numeric values and repetition prefixes\n// work as in previous stages.\n//\n// Token vocabulary:\n//   #DEF   `=`  (rule-definition operator)\n//   #DEFA  `=/` (incremental-alternatives operator)\n//   #ALT   `/`  (alternation)\n//   #STAR  `*`  (repetition separator)\n//   #NUM   decimal repetition count (matched via match.token)\n//   #NV    `%[xdb]NN[(-NN|(.NN)*)]` numeric value (match.token)\n//   #SS    `%s` (case-sensitive string prefix)\n//   #SI    `%i` (case-insensitive string prefix — same as default)\n//   #LP    `(`\n//   #RP    `)`\n//   #OB    `[` (optional-group open)\n//   #CB    `]` (optional-group close)\n//   #TX    bare identifier (jsonic default text token)\n//   #ST    quoted string literal (jsonic default string token)\n//   #ZZ    end-of-source\n//\n// Grammar:\n//   bnf        = production*\n//   production = IDENT ('=' / '=/') alts\n//   alts       = seq ('/' seq)*\n//   seq        = element*\n//   element    = repetition? atom\n//   repetition = NUM '*' NUM / NUM '*' / '*' NUM / '*' / NUM\n//   atom       = IDENT | STRING | ['%s' | '%i'] STRING | NUMVAL\n//              | '(' alts ')' | '[' alts ']'\n//   numval     = '%' ('x' / 'd' / 'b') DIGITS [ '-' DIGITS | ('.' DIGITS)* ]\nconst bnfRules = {\n    // Top-level: accumulates productions into r.node.\n    bnf: {\n        bo: (r) => { r.node = []; },\n        open: [\n            { s: '#ZZ', g: 'empty' },\n            { p: 'prod' },\n        ],\n        close: [{ s: '#ZZ' }],\n    },\n    // One production per invocation; tail-recurses (r:'prod') for the\n    // next. Inherits its parent's node (the productions array) and\n    // appends to it in `bc` once its `alts` child has returned.\n    // Production header is `IDENT =` — a bareword rule name followed\n    // by the `=` definition operator.\n    prod: {\n        open: [\n            // Standalone definition:   name = alts\n            {\n                s: '#TX #DEF',\n                a: (r) => {\n                    r.u.name = r.o[0].val;\n                    r.u.incremental = false;\n                },\n                p: 'alts',\n            },\n            // Incremental alternatives:   name =/ alts\n            {\n                s: '#TX #DEFA',\n                a: (r) => {\n                    r.u.name = r.o[0].val;\n                    r.u.incremental = true;\n                },\n                p: 'alts',\n            },\n        ],\n        close: [\n            // A TX followed by `=` or `=/` means the next production has\n            // begun — back up 2 tokens so a fresh `prod` invocation sees\n            // them.\n            { s: '#TX #DEF', b: 2, r: 'prod' },\n            { s: '#TX #DEFA', b: 2, r: 'prod' },\n            { b: 1 },\n        ],\n        bc: (r) => {\n            if (r.child && r.child.node !== undefined) {\n                const prod = { name: r.u.name, alts: r.child.node };\n                if (r.u.incremental)\n                    prod.incremental = true;\n                r.node.push(prod);\n            }\n        },\n    },\n    // A list of alternative sequences separated by `/` (ABNF\n    // alternation). Owns its own array (`bo` resets it) and pushes\n    // each seq result in `bc`.\n    alts: {\n        bo: (r) => { r.node = []; },\n        open: [{ p: 'seq' }],\n        close: [\n            { s: '#ALT', p: 'seq' },\n            { b: 1 },\n        ],\n        bc: (r) => {\n            if (r.child && r.child.node !== undefined) {\n                r.node.push(r.child.node);\n            }\n        },\n    },\n    // A (possibly empty) sequence of elements. The 2-token lookahead\n    // `#TX #DEF` detects a following production boundary and bails\n    // out without consuming the tokens; a plain `#TX` at the leading\n    // position (tried later so the longer alt wins) is a rule\n    // reference inside the current sequence.\n    seq: {\n        bo: (r) => { r.node = []; },\n        open: [\n            { s: '#TX #DEF', b: 2, g: 'end' },\n            { s: '#TX #DEFA', b: 2, g: 'end' },\n            { s: '#ALT', b: 1, g: 'end' },\n            { s: '#ZZ', b: 1, g: 'end' },\n            { s: '#RP', b: 1, g: 'end' },\n            { s: '#CB', b: 1, g: 'end' },\n            // Listing element-starter tokens in `s:` here ensures the\n            // tcol-driven matcher considers each one when lexing.\n            { s: '#ST', b: 1, p: 'elem' },\n            { s: '#NV', b: 1, p: 'elem' },\n            { s: '#SS', b: 1, p: 'elem' },\n            { s: '#SI', b: 1, p: 'elem' },\n            { s: '#TX', b: 1, p: 'elem' },\n            { s: '#LP', b: 1, p: 'elem' },\n            { s: '#OB', b: 1, p: 'elem' },\n            { s: '#STAR', b: 1, p: 'elem' },\n            { s: '#NUM', b: 1, p: 'elem' },\n            { p: 'elem' },\n        ],\n        close: [\n            { s: '#TX #DEF', b: 2, g: 'end' },\n            { s: '#TX #DEFA', b: 2, g: 'end' },\n            { s: '#ALT', b: 1, g: 'end' },\n            { s: '#ZZ', b: 1, g: 'end' },\n            { s: '#RP', b: 1, g: 'end' },\n            { s: '#CB', b: 1, g: 'end' },\n            { s: '#ST', b: 1, p: 'elem' },\n            { s: '#NV', b: 1, p: 'elem' },\n            { s: '#SS', b: 1, p: 'elem' },\n            { s: '#SI', b: 1, p: 'elem' },\n            { s: '#TX', b: 1, p: 'elem' },\n            { s: '#LP', b: 1, p: 'elem' },\n            { s: '#OB', b: 1, p: 'elem' },\n            { s: '#STAR', b: 1, p: 'elem' },\n            { s: '#NUM', b: 1, p: 'elem' },\n            { b: 1 },\n        ],\n    },\n    // One element: an optional ABNF repetition prefix (`*A`, `1*A`,\n    // `m*nA`, `*nA`, `m*A`, `nA`) followed by an atom. The prefix is\n    // matched up front, stored on `r.u.min`/`r.u.max`; then `atom` is\n    // pushed to parse the actual element body, whose result is wrapped\n    // into an AST node and appended to the parent seq's array in close.\n    elem: {\n        bo: (r) => { r.u.min = 1; r.u.max = 1; },\n        open: [\n            // NUM '*' NUM — bounded repetition, followed by the atom\n            // itself (listed via the ATOM tokenset so every atom-starter\n            // tin — including `#NV` — is in tcol for this position).\n            {\n                s: '#NUM #STAR #NUM #ATOM',\n                b: 1,\n                a: (r) => {\n                    r.u.min = parseInt(r.o[0].src, 10);\n                    r.u.max = parseInt(r.o[2].src, 10);\n                },\n                p: 'atom',\n            },\n            // NUM '*' — at-least-NUM repetition followed by an atom.\n            {\n                s: '#NUM #STAR #ATOM',\n                b: 1,\n                a: (r) => {\n                    r.u.min = parseInt(r.o[0].src, 10);\n                    r.u.max = Infinity;\n                },\n                p: 'atom',\n            },\n            // '*' NUM — at-most-NUM repetition.\n            {\n                s: '#STAR #NUM #ATOM',\n                b: 1,\n                a: (r) => {\n                    r.u.min = 0;\n                    r.u.max = parseInt(r.o[1].src, 10);\n                },\n                p: 'atom',\n            },\n            // '*' — zero-or-more.\n            {\n                s: '#STAR #ATOM',\n                b: 1,\n                a: (r) => { r.u.min = 0; r.u.max = Infinity; },\n                p: 'atom',\n            },\n            // NUM — exact repetition count.\n            {\n                s: '#NUM #ATOM',\n                b: 1,\n                a: (r) => {\n                    const n = parseInt(r.o[0].src, 10);\n                    r.u.min = n;\n                    r.u.max = n;\n                },\n                p: 'atom',\n            },\n            // No prefix — push atom directly (min = max = 1).\n            { p: 'atom' },\n        ],\n        close: [{\n                // Wrap the returned atom (r.child.node) based on r.u.min/max\n                // and append to the parent seq's array.\n                a: (r) => {\n                    const item = r.child.node;\n                    const { min, max } = r.u;\n                    if (min === 1 && max === 1) {\n                        r.node.push(item);\n                    }\n                    else if (min === 0 && max === Infinity) {\n                        r.node.push({ kind: 'star', inner: item });\n                    }\n                    else if (min === 1 && max === Infinity) {\n                        r.node.push({ kind: 'plus', inner: item });\n                    }\n                    else if (min === 0 && max === 1) {\n                        r.node.push({ kind: 'opt', inner: item });\n                    }\n                    else {\n                        r.node.push({ kind: 'rep', min, max, inner: item });\n                    }\n                },\n            }],\n    },\n    // The atom body — a bareword ref, quoted-string terminal,\n    // parenthesised group, or bracketed optional. Sets its OWN r.node\n    // to the AST element so the enclosing `elem` rule can read it\n    // from `r.child.node` in its close state.\n    atom: {\n        bo: (r) => { r.node = undefined; },\n        open: [\n            // Case-sensitive string:   %s\"foo\"\n            {\n                s: '#SS #ST',\n                a: (r) => {\n                    r.node = {\n                        kind: 'term',\n                        literal: r.o[1].val,\n                        caseSensitive: true,\n                    };\n                },\n            },\n            // Case-insensitive string: %i\"foo\" (same as bare \"foo\" below,\n            // but spelled explicitly).\n            {\n                s: '#SI #ST',\n                a: (r) => {\n                    r.node = { kind: 'term', literal: r.o[1].val };\n                },\n            },\n            // Bare quoted string — case-insensitive per ABNF default.\n            {\n                s: '#ST',\n                a: (r) => {\n                    r.node = { kind: 'term', literal: r.o[0].val };\n                },\n            },\n            {\n                s: '#NV',\n                a: (r) => {\n                    r.node = parseNumericValue(r.o[0].src);\n                },\n            },\n            {\n                s: '#TX',\n                a: (r) => {\n                    r.node = { kind: 'ref', name: r.o[0].val };\n                },\n            },\n            {\n                s: '#LP',\n                a: (r) => { r.u.groupKind = 'group'; },\n                p: 'alts',\n            },\n            {\n                s: '#OB',\n                a: (r) => { r.u.groupKind = 'opt'; },\n                p: 'alts',\n            },\n        ],\n        close: [\n            {\n                s: '#RP',\n                c: (r) => r.u.groupKind === 'group',\n                a: (r) => {\n                    r.node = { kind: 'group', alts: r.child.node };\n                },\n            },\n            {\n                s: '#CB',\n                c: (r) => r.u.groupKind === 'opt',\n                a: (r) => {\n                    r.node = {\n                        kind: 'opt',\n                        inner: { kind: 'group', alts: r.child.node },\n                    };\n                },\n            },\n            // For simple atoms (string/ref), r.node is already set by\n            // open; we want to pop without consuming the next token.\n            // List every token that can legitimately follow an atom so\n            // the lexer's tcol-driven match-matcher emits #NUM, #STAR,\n            // and friends as their proper types here — otherwise the\n            // default number-matcher would lex `1` as #NR and the\n            // enclosing seq.close wouldn't recognise the digit as the\n            // start of a repetition prefix.\n            { s: '#TX', b: 1 },\n            { s: '#ST', b: 1 },\n            { s: '#NV', b: 1 },\n            { s: '#SS', b: 1 },\n            { s: '#SI', b: 1 },\n            { s: '#NUM', b: 1 },\n            { s: '#STAR', b: 1 },\n            { s: '#LP', b: 1 },\n            { s: '#OB', b: 1 },\n            { s: '#RP', b: 1 },\n            { s: '#CB', b: 1 },\n            { s: '#ALT', b: 1 },\n            { s: '#DEF', b: 1 },\n            { s: '#ZZ', b: 1 },\n            { b: 1 },\n        ],\n    },\n};\nexports.bnfRules = bnfRules;\n// Lazily built jsonic instance that parses BNF source. Deferred\n// construction avoids a circular-import failure at module load time.\nlet _bnfParser = null;\nfunction getBnfParser() {\n    if (_bnfParser)\n        return _bnfParser;\n    const { Jsonic } = require('./jsonic');\n    const j = Jsonic.make({\n        rule: { start: 'bnf' },\n        fixed: {\n            token: {\n                // Clear JSON-oriented defaults we're not using so `:`, `,`\n                // and `{` have no special meaning inside BNF source.\n                '#OS': null,\n                '#CS': null,\n                '#CL': null,\n                '#CA': null,\n                // Re-map `#OB` / `#CB` from JSON's `{` / `}` to ABNF's\n                // `[` / `]` optional-group brackets.\n                '#OB': '[',\n                '#CB': ']',\n                '#DEF': '=',\n                // `=/` — ABNF's incremental-alternatives operator. Longer\n                // than `=`, so jsonic's longest-match-wins fixed matcher\n                // tries it first.\n                '#DEFA': '=/',\n                '#ALT': '/',\n                '#STAR': '*',\n                '#LP': '(',\n                '#RP': ')',\n            },\n        },\n        match: {\n            token: {\n                // ABNF repetition counts: decimal integers.\n                '#NUM': /^[0-9]+/,\n                // ABNF numeric value notation:\n                //   %xNN        single hex code point\n                //   %dNN        single decimal code point\n                //   %bNN        single binary code point\n                //   %xNN-NN     hex range\n                //   %xNN.NN.NN  concatenated hex code points (= string)\n                // Digits are permissive (hex covers the decimal / binary\n                // subsets); `parseNumericValue` re-validates against the\n                // actual base.\n                '#NV': /^%[xdbXDB][0-9a-fA-F]+(?:[-.][0-9a-fA-F]+)*/,\n                // `%s` / `%i` prefixes on a quoted string. The lookahead\n                // requires `\"` so they don't steal the `%` of `%xNN`.\n                '#SS': /^%[sS](?=\")/,\n                '#SI': /^%[iI](?=\")/,\n            },\n        },\n        tokenSet: {\n            // Tokens that can legitimately open an atom. Declaring this\n            // as a set lets elem.open use `#ATOM` inside its `s:` patterns\n            // — that way the tcol at the atom-starter position includes\n            // every matcher tin (notably #NV), so the lexer doesn't fall\n            // through to #TX when the actual atom is `%xNN`.\n            ATOM: ['#ST', '#NV', '#TX', '#LP', '#OB', '#SS', '#SI'],\n        },\n        comment: {\n            // ABNF uses `;` to start a line comment. Override jsonic's\n            // default `hash` definition (which used `#`) and disable the\n            // other comment styles so `//` and `/* */` aren't confused\n            // with the alternation operator.\n            def: {\n                hash: { line: true, start: ';', lex: true, eatline: false },\n                slash: null,\n                multi: null,\n            },\n        },\n    });\n    // Drop the default JSON rules — they would otherwise compete with\n    // ours for the starting token set.\n    const existing = j.rule();\n    for (const name of Object.keys(existing)) {\n        j.rule(name, null);\n    }\n    for (const name of Object.keys(bnfRules)) {\n        const spec = bnfRules[name];\n        j.rule(name, (rs) => {\n            if (spec.bo)\n                rs.bo(spec.bo);\n            if (spec.bc)\n                rs.bc(spec.bc);\n            if (spec.open)\n                rs.open(spec.open);\n            if (spec.close)\n                rs.close(spec.close);\n        });\n    }\n    _bnfParser = (src) => j(src);\n    return _bnfParser;\n}\n// Rewrite a grammar so that the only element kinds remaining are\n// `term` and `ref`. Each `X?`, `X*`, `X+` occurrence is replaced by a\n// reference to a newly-generated helper production that expresses the\n// same language in plain BNF.\n// Eliminate left recursion — both direct (P → P α) and indirect\n// (P → Q α, Q → P β) — via Paull's algorithm.\n//\n// Order the productions, and for each A_i walk back over A_1..A_{i-1}\n// inlining any leading reference into A_i's alternatives. Once the\n// only remaining leading self-reference on A_i is direct, rewrite to\n// the iterative form\n//   P → (β_1 | … | β_m) (α_1 | … | α_n)*\n// which jsonic's push-down parser can execute without re-entering P\n// at the same source position.\n//\n// The substitution step can duplicate alternatives, so pathological\n// grammars will enlarge — caller is expected to keep the grammar\n// reasonably small (this is a first-step converter, not a full\n// toolchain).\nfunction eliminateLeftRecursion(grammar) {\n    const originalOrder = grammar.productions.map((p) => p.name);\n    // Order productions so that rules referenced at a leading position\n    // are processed before the rules that reference them. Paull's\n    // substitution inlines A_j's alts into A_i for j < i, so putting\n    // dependencies first is what makes nullable-prefixed hidden left\n    // recursion reachable by the substitution step.\n    //\n    // Note: substitution here always runs, even for cycle-free\n    // grammars. The reason is pragmatic rather than theoretical —\n    // populating tcol from multi-token altPrefixes (needed so the\n    // lexer's regex matchers fire with the right tin in nested\n    // contexts) requires the full inlined shape. A future refactor\n    // could compute tcol from the un-substituted grammar and only\n    // apply Paull's to the cyclic SCCs, which would preserve more\n    // named-rule structure in the emitted AST.\n    let prods = topoOrderForPaull(grammar.productions.map((p) => ({\n        name: p.name,\n        alts: p.alts.map((a) => a.slice()),\n        nodeKind: p.nodeKind,\n    })));\n    for (let i = 0; i < prods.length; i++) {\n        // For each earlier production A_j, inline any alternative of\n        // A_i whose leading element is a reference to A_j.\n        for (let j = 0; j < i; j++) {\n            prods[i] = substituteLeadingRef(prods[i], prods[j]);\n        }\n        prods[i] = eliminateDirectLeftRec(prods[i]);\n    }\n    // Restore the caller's declared order, so the start rule still\n    // ends up first (and the user sees their rule names in a\n    // recognisable order when inspecting the spec).\n    const byName = new Map(prods.map((p) => [p.name, p]));\n    const ordered = [];\n    for (const name of originalOrder) {\n        const p = byName.get(name);\n        if (p) {\n            ordered.push(p);\n            byName.delete(name);\n        }\n    }\n    // Any generated productions created during substitution (none in\n    // the current implementation) would fall through here.\n    for (const p of byName.values())\n        ordered.push(p);\n    return { productions: ordered };\n}\n// Tarjan-flavoured SCC scan over the leading-reference graph:\n// returns the names of productions that participate in at least one\n// cycle (self-loop or longer). Used to scope Paull's substitution to\n// only the rules that actually need it.\nfunction findLeadingRefCycleMembers(prods) {\n    const byName = new Map(prods.map((p) => [p.name, p]));\n    const leadingRefs = (p) => {\n        const out = [];\n        for (const alt of p.alts) {\n            if (alt.length === 0)\n                continue;\n            const first = alt[0];\n            if (first.kind === 'ref' && byName.has(first.name))\n                out.push(first.name);\n        }\n        return out;\n    };\n    // Tarjan's SCC algorithm.\n    let index = 0;\n    const stack = [];\n    const onStack = new Set();\n    const indices = new Map();\n    const lowlinks = new Map();\n    const cyclic = new Set();\n    function strongConnect(name) {\n        indices.set(name, index);\n        lowlinks.set(name, index);\n        index++;\n        stack.push(name);\n        onStack.add(name);\n        const prod = byName.get(name);\n        if (prod) {\n            for (const target of leadingRefs(prod)) {\n                if (!indices.has(target)) {\n                    strongConnect(target);\n                    lowlinks.set(name, Math.min(lowlinks.get(name), lowlinks.get(target)));\n                }\n                else if (onStack.has(target)) {\n                    lowlinks.set(name, Math.min(lowlinks.get(name), indices.get(target)));\n                }\n            }\n        }\n        if (lowlinks.get(name) === indices.get(name)) {\n            // Pop the SCC. If it has more than one member, or it's a\n            // single member with a self-loop, mark as cyclic.\n            const scc = [];\n            let w;\n            do {\n                w = stack.pop();\n                onStack.delete(w);\n                scc.push(w);\n            } while (w !== name);\n            const isCycle = scc.length > 1 ||\n                (scc.length === 1 && leadingRefs(byName.get(scc[0])).includes(scc[0]));\n            if (isCycle)\n                for (const n of scc)\n                    cyclic.add(n);\n        }\n    }\n    for (const p of prods) {\n        if (!indices.has(p.name))\n            strongConnect(p.name);\n    }\n    return cyclic;\n}\n// Topological order over the \"leading-position reference\" graph:\n// an edge A → B exists when A has at least one alternative whose\n// first element is a reference to B. Cycles are preserved as-is\n// (Paull's handles them via the substitution + direct-LR rewrite).\nfunction topoOrderForPaull(prods) {\n    const byName = new Map(prods.map((p) => [p.name, p]));\n    const colour = new Map(); // 0 unseen, 1 in-progress, 2 done\n    const order = [];\n    function visit(name) {\n        const c = colour.get(name) ?? 0;\n        if (c !== 0)\n            return; // already seen or on the current path\n        colour.set(name, 1);\n        const p = byName.get(name);\n        if (p) {\n            for (const alt of p.alts) {\n                if (alt.length > 0 && alt[0].kind === 'ref' && byName.has(alt[0].name)) {\n                    visit(alt[0].name);\n                }\n            }\n            colour.set(name, 2);\n            order.push(p);\n        }\n        else {\n            colour.set(name, 2);\n        }\n    }\n    for (const p of prods)\n        visit(p.name);\n    return order;\n}\n// For every alternative of `target` that begins with a ref to\n// `source`, replace that alt with |source.alts| copies — each one\n// with the leading source-ref expanded to one of source's alts.\nfunction substituteLeadingRef(target, source) {\n    const newAlts = [];\n    for (const alt of target.alts) {\n        if (alt.length > 0 &&\n            alt[0].kind === 'ref' &&\n            alt[0].name === source.name) {\n            const tail = alt.slice(1);\n            for (const srcAlt of source.alts) {\n                newAlts.push([...srcAlt, ...tail]);\n            }\n        }\n        else {\n            newAlts.push(alt);\n        }\n    }\n    return { name: target.name, alts: newAlts, nodeKind: target.nodeKind };\n}\n// Rewrite a single production's direct left recursion to its\n// iterative equivalent. Equivalent to the previous version of\n// `eliminateLeftRecursion` but scoped to one production.\nfunction eliminateDirectLeftRec(prod) {\n    const recursive = [];\n    const seeds = [];\n    for (const alt of prod.alts) {\n        if (alt.length > 0 &&\n            alt[0].kind === 'ref' &&\n            alt[0].name === prod.name) {\n            recursive.push(alt.slice(1));\n        }\n        else {\n            seeds.push(alt);\n        }\n    }\n    // A trivial recursive alt `[P]` (P ::= P, nothing else) would\n    // derive P from P with no progress — semantically a no-op. Drop\n    // them silently, since nullable-prefix expansion in Paull's can\n    // legitimately produce them and erroring would hide a legal\n    // grammar.\n    const nonTrivialRecursive = recursive.filter((t) => t.length > 0);\n    if (nonTrivialRecursive.length === 0) {\n        // Either no recursion at all, or only trivial self-refs — keep\n        // just the seeds.\n        return { name: prod.name, alts: seeds, nodeKind: prod.nodeKind };\n    }\n    if (seeds.length === 0) {\n        throw new Error(`bnf: rule '${prod.name}' is purely left-recursive ` +\n            `(no seed alternative); cannot eliminate`);\n    }\n    const seedElement = seeds.length === 1 && seeds[0].length === 1\n        ? seeds[0][0]\n        : { kind: 'group', alts: seeds };\n    const tailInner = nonTrivialRecursive.length === 1 && nonTrivialRecursive[0].length === 1\n        ? nonTrivialRecursive[0][0]\n        : { kind: 'group', alts: nonTrivialRecursive };\n    return {\n        name: prod.name,\n        alts: [[seedElement, { kind: 'star', inner: tailInner }]],\n        nodeKind: prod.nodeKind,\n    };\n}\nfunction desugar(grammar) {\n    const extra = [];\n    const used = new Set(grammar.productions.map((p) => p.name));\n    function freshName(hint) {\n        // Collision-avoiding name like `_gen1`, `_gen2`, …\n        let i = extra.length;\n        let name;\n        do {\n            i++;\n            name = `_gen${i}_${hint}`;\n        } while (used.has(name));\n        used.add(name);\n        return name;\n    }\n    function desugarAlt(alt) {\n        return alt.map(desugarElement);\n    }\n    function desugarElement(el) {\n        if (el.kind === 'term' || el.kind === 'ref' || el.kind === 'regex') {\n            return el;\n        }\n        if (el.kind === 'group') {\n            // Recurse into the group's alts so nested sugar is flattened,\n            // then emit a helper production whose body is those alts.\n            const innerAlts = el.alts.map((a) => desugarAlt(a));\n            const name = freshName('group');\n            extra.push({ name, alts: innerAlts, nodeKind: 'helper' });\n            return { kind: 'ref', name };\n        }\n        // `opt`, `star`, `plus` all wrap a single inner element.\n        const inner = desugarElement(el.inner);\n        const hint = inner.kind === 'ref' ? inner.name :\n            inner.kind === 'term' ? 'term' : 'x';\n        if (el.kind === 'opt') {\n            // H ::= inner | (empty)\n            const name = freshName('opt_' + hint);\n            extra.push({ name, alts: [[inner], []], nodeKind: 'helper' });\n            return { kind: 'ref', name };\n        }\n        if (el.kind === 'star') {\n            // H = inner H / (empty)\n            const name = freshName('star_' + hint);\n            const selfRef = { kind: 'ref', name };\n            extra.push({ name, alts: [[inner, selfRef], []], nodeKind: 'helper' });\n            return { kind: 'ref', name };\n        }\n        if (el.kind === 'plus') {\n            // H = inner Tail   where   Tail = inner Tail / (empty)\n            const tailName = freshName('star_' + hint);\n            const plusName = freshName('plus_' + hint);\n            const tailRef = { kind: 'ref', name: tailName };\n            extra.push({\n                name: tailName,\n                alts: [[inner, tailRef], []],\n                nodeKind: 'helper',\n            });\n            extra.push({\n                name: plusName,\n                alts: [[inner, tailRef]],\n                nodeKind: 'helper',\n            });\n            return { kind: 'ref', name: plusName };\n        }\n        // ABNF m*n bounded repetition. Desugars to a concatenation of\n        // `min` mandatory copies of the inner element followed by a\n        // tail that accepts up to `(max - min)` more.\n        //   m*n A   =>   A{m}  [A[A[A...[A]]]]   (nested optionals)\n        //   m*  A   =>   A{m}  *A                 (mandatory prefix + star)\n        //   *n  A   =>   [A [A ... [A]]]          (n nested optionals)\n        // The helper's single alt has `min` repetitions of inner, then\n        // either a star-helper for (min, ∞) or `max - min` nested\n        // optionals for a finite range.\n        const { min, max } = el;\n        const repName = freshName('rep_' + hint);\n        const repAlt = [];\n        for (let i = 0; i < min; i++)\n            repAlt.push(inner);\n        if (max === Infinity) {\n            // Tail: unbounded star of inner.\n            const tailStarName = freshName('star_' + hint);\n            const tailStarRef = { kind: 'ref', name: tailStarName };\n            extra.push({\n                name: tailStarName,\n                alts: [[inner, tailStarRef], []],\n                nodeKind: 'helper',\n            });\n            repAlt.push(tailStarRef);\n        }\n        else {\n            // Nest (max - min) optionals: [A [A [A ...]]].\n            let nested = [];\n            for (let i = 0; i < max - min; i++) {\n                // Wrap current `nested` into an optional and prepend `inner`.\n                if (nested.length === 0) {\n                    nested = [{ kind: 'opt', inner: { kind: 'group', alts: [[inner]] } }];\n                }\n                else {\n                    nested = [{\n                            kind: 'opt',\n                            inner: { kind: 'group', alts: [[inner, ...nested]] },\n                        }];\n                }\n            }\n            repAlt.push(...nested);\n        }\n        extra.push({ name: repName, alts: [desugarAlt(repAlt)], nodeKind: 'helper' });\n        return { kind: 'ref', name: repName };\n    }\n    const rewritten = grammar.productions.map((p) => {\n        const out = {\n            name: p.name,\n            alts: p.alts.map(desugarAlt),\n            nodeKind: p.nodeKind,\n        };\n        // Probe-dispatch flags survive desugar unchanged — the emitter\n        // routes around the standard alt-compilation path for these.\n        if (p.probeDispatch)\n            out.probeDispatch = p.probeDispatch;\n        if (p.probeHelper)\n            out.probeHelper = p.probeHelper;\n        return out;\n    });\n    return { productions: [...rewritten, ...extra] };\n}\n// Error raised when the BNF source itself can't be parsed.  Surfaces\n// line and column from the underlying jsonic error so the caller can\n// report them directly. The original error is kept on `.cause`.\nclass BnfParseError extends Error {\n    constructor(message, location, cause) {\n        super(message);\n        this.name = 'BnfParseError';\n        this.line = location?.line;\n        this.column = location?.column;\n        this.cause = cause;\n    }\n}\nexports.BnfParseError = BnfParseError;\n// Parse BNF source into a grammar AST via the jsonic-based parser.\nfunction parseBnf(src) {\n    const parser = getBnfParser();\n    let productions;\n    try {\n        productions = parser(src) ?? [];\n    }\n    catch (e) {\n        // JsonicError carries `lineNumber` / `columnNumber`; fall back to\n        // ad-hoc extraction from the error message otherwise.\n        const line = e?.lineNumber ?? e?.row;\n        const column = e?.columnNumber ?? e?.col;\n        const loc = (line != null && column != null)\n            ? ` at line ${line}, column ${column}`\n            : '';\n        const raw = e?.message ? String(e.message).split('\\n')[0] : String(e);\n        throw new BnfParseError(`bnf: parse error${loc}: ${raw}`, { line, column }, e);\n    }\n    if (!Array.isArray(productions) || productions.length === 0) {\n        throw new BnfParseError('bnf: no productions found');\n    }\n    const merged = mergeIncrementals(productions);\n    return { productions: withCoreRules(merged) };\n}\n// RFC 5234 Appendix B.1 core rules. Parsed lazily on first use\n// and spliced into any user grammar that references them but\n// doesn't define them locally.\nconst CORE_RULES_ABNF = `\nALPHA  = %x41-5A / %x61-7A\nBIT    = \"0\" / \"1\"\nCHAR   = %x01-7F\nCR     = %x0D\nLF     = %x0A\nCRLF   = CR LF\nCTL    = %x00-1F / %x7F\nDIGIT  = %x30-39\nDQUOTE = %x22\nHEXDIG = DIGIT / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\"\nHTAB   = %x09\nOCTET  = %x00-FF\nSP     = %x20\nVCHAR  = %x21-7E\nWSP    = SP / HTAB\n`;\nlet _coreRules = null;\nfunction getCoreRules() {\n    if (_coreRules)\n        return _coreRules;\n    const parser = getBnfParser();\n    const raw = parser(CORE_RULES_ABNF);\n    // Core rules flatten to `src` in the output AST — they're\n    // character-class bricks, not structural nodes users want to see\n    // one-per-matched-character.\n    for (const p of raw)\n        p.nodeKind = 'core';\n    _coreRules = new Map(raw.map((p) => [p.name, p]));\n    return _coreRules;\n}\nfunction refsIn(alt, out) {\n    for (const el of alt) {\n        if (el.kind === 'ref')\n            out.add(el.name);\n        else if (el.kind === 'opt' || el.kind === 'star' ||\n            el.kind === 'plus' || el.kind === 'rep') {\n            refsIn([el.inner], out);\n        }\n        else if (el.kind === 'group') {\n            for (const a of el.alts)\n                refsIn(a, out);\n        }\n    }\n}\n// Add each RFC 5234 core rule that the user's grammar references\n// but doesn't define locally. Resolution is transitive: if the\n// user mentions HEXDIG, DIGIT is pulled in too. User definitions\n// always win — a local `DIGIT = …` is left untouched.\nfunction withCoreRules(user) {\n    const core = getCoreRules();\n    const defined = new Set(user.map((p) => p.name));\n    const needed = new Set();\n    const scan = (prods) => {\n        for (const p of prods) {\n            for (const alt of p.alts)\n                refsIn(alt, needed);\n        }\n    };\n    scan(user);\n    const out = [];\n    // Transitively add core rules, in declaration order.\n    let added = true;\n    while (added) {\n        added = false;\n        for (const [name, prod] of core) {\n            if (defined.has(name))\n                continue;\n            if (!needed.has(name))\n                continue;\n            defined.add(name);\n            out.push(prod);\n            scan([prod]);\n            added = true;\n        }\n    }\n    return [...user, ...out];\n}\n// Fold every `name =/ alt` production into the earlier production\n// with the same name by appending its alternatives. Throws if an\n// incremental references a name that hasn't been defined yet — ABNF\n// requires the base production to appear first.\nfunction mergeIncrementals(prods) {\n    const out = [];\n    const byName = new Map();\n    for (const p of prods) {\n        if (p.incremental) {\n            const base = byName.get(p.name);\n            if (!base) {\n                throw new BnfParseError(`bnf: '${p.name} =/ …' has no earlier '${p.name} = …' to extend`);\n            }\n            base.alts.push(...p.alts);\n            continue;\n        }\n        // Strip the (absent) flag on a cleanly-written production so\n        // downstream code never sees it.\n        const clean = { name: p.name, alts: p.alts };\n        if (p.nodeKind)\n            clean.nodeKind = p.nodeKind;\n        out.push(clean);\n        byName.set(p.name, clean);\n    }\n    return out;\n}\n// -- Probe-dispatch analyser + rewriter -----------------------------\n//\n// ABNF has a large family of grammars that aren't LL(k) for any\n// bounded k. The canonical example is RFC 3986's `authority`:\n//\n//   authority = [ userinfo \"@\" ] host [ \":\" port ]\n//   userinfo  = *( unreserved / pct-encoded / sub-delims / \":\" )\n//   host      = IP-literal / IPv4address / reg-name\n//   reg-name  = *( unreserved / pct-encoded / sub-delims )\n//\n// `userinfo` and `reg-name` share a character vocabulary, so a\n// FIRST-set dispatcher can't decide which branch the optional\n// `[ userinfo \"@\" ]` belongs to — the disambiguating `@` can be\n// arbitrarily far from the start.\n//\n// For the common pattern `[X D] Y` — an optional group whose body\n// ends with a terminal D, followed by a sequence Y whose leading\n// terminals overlap with X's — we handle the ambiguity by rewriting\n// the rule to a probe+phase-retry dispatcher:\n//\n//   1. On first entry (phase 0), mark the token position and push a\n//      failure-proof probe rule that greedily consumes every token\n//      in the joint vocabulary of X and Y.\n//   2. When the probe returns, peek ctx.t[0]:\n//        D seen   → phase = 1 (take the `X D Y` branch)\n//        D absent → phase = 2 (take the `Y` branch)\n//      Rewind to the mark and `r:` back into the dispatcher.\n//   3. The dispatcher's open has a `c:`-guarded alt for each phase\n//      that pushes the corresponding committed branch.\n//\n// The primitives used (`r:`, `k:`, `c:`, `ctx.mark`, `ctx.rewind`,\n// `ctx.t`) are the same building blocks rules/parser already exposes\n// — no new jsonic machinery is needed.\n// Predicate: element is `[ X D ]` where X is one or more elements\n// and D is a terminal literal or a regex terminal.\nfunction isProbeableOpt(el) {\n    if (el.kind !== 'opt')\n        return null;\n    const inner = el.inner;\n    if (inner.kind !== 'group')\n        return null;\n    if (inner.alts.length !== 1)\n        return null;\n    const seq = inner.alts[0];\n    if (seq.length < 2)\n        return null;\n    const last = seq[seq.length - 1];\n    if (last.kind !== 'term' && last.kind !== 'regex')\n        return null;\n    return { xSeq: seq.slice(0, -1), disambiguator: last };\n}\n// Union of every terminal reachable by walking an element's subtree,\n// following refs transitively. Cycles are broken by the visited set.\n// Returns terminals as BnfElements so the caller isn't tied to the\n// emitter's token-allocation step.\nfunction collectTerminalVocabElements(el, grammar, out, visited) {\n    if (el.kind === 'term') {\n        const k = termKey(el);\n        if (!out.has(k))\n            out.set(k, el);\n        return;\n    }\n    if (el.kind === 'regex') {\n        const k = regexKey(el);\n        if (!out.has(k))\n            out.set(k, el);\n        return;\n    }\n    if (el.kind === 'ref') {\n        if (visited.has(el.name))\n            return;\n        visited.add(el.name);\n        const prod = grammar.productions.find((p) => p.name === el.name);\n        if (!prod)\n            return;\n        for (const alt of prod.alts)\n            for (const sub of alt)\n                collectTerminalVocabElements(sub, grammar, out, visited);\n        return;\n    }\n    if (el.kind === 'opt' || el.kind === 'star' || el.kind === 'plus' ||\n        el.kind === 'rep') {\n        collectTerminalVocabElements(el.inner, grammar, out, visited);\n        return;\n    }\n    if (el.kind === 'group') {\n        for (const alt of el.alts)\n            for (const sub of alt)\n                collectTerminalVocabElements(sub, grammar, out, visited);\n        return;\n    }\n}\nfunction collectSeqVocabElements(seq, grammar) {\n    const out = new Map();\n    const visited = new Set();\n    for (const el of seq)\n        collectTerminalVocabElements(el, grammar, out, visited);\n    return out;\n}\nfunction mapsOverlap(a, b) {\n    for (const x of a.keys())\n        if (b.has(x))\n            return true;\n    return false;\n}\n// Rewrite every ambiguous `[X D] Y` subsequence in `grammar` into a\n// probe-dispatch pattern. The grammar at this point still has `opt`,\n// `group`, `star`, `plus`, `rep` sugar — intentionally, since that's\n// where the pattern is easy to recognise. Runs BEFORE token\n// allocation; probe metadata stores BnfElements, and the emitter\n// resolves them to token names at emit time.\nfunction rewriteProbeDispatches(grammar) {\n    const reports = grammar.ambiguities ?? [];\n    const extra = [];\n    const used = new Set(grammar.productions.map((p) => p.name));\n    function freshName(hint) {\n        let name = hint;\n        let i = 1;\n        while (used.has(name)) {\n            name = hint + i;\n            i++;\n        }\n        used.add(name);\n        return name;\n    }\n    const rewritten = [];\n    for (const prod of grammar.productions) {\n        let newAlts = [];\n        let touched = false;\n        for (let altIdx = 0; altIdx < prod.alts.length; altIdx++) {\n            const alt = prod.alts[altIdx];\n            let resultAlt = [];\n            for (let i = 0; i < alt.length; i++) {\n                const el = alt[i];\n                const info = isProbeableOpt(el);\n                if (!info) {\n                    resultAlt.push(el);\n                    continue;\n                }\n                const ySeq = alt.slice(i + 1);\n                if (ySeq.length === 0) {\n                    // `[X D]` is the last thing in the alt — nothing follows, so\n                    // there's nothing to disambiguate against. Standard emit.\n                    resultAlt.push(el);\n                    continue;\n                }\n                const xVocab = collectSeqVocabElements(info.xSeq, grammar);\n                const yVocab = collectSeqVocabElements(ySeq, grammar);\n                if (!mapsOverlap(xVocab, yVocab)) {\n                    // The optional's leading tokens don't overlap with the tail's\n                    // leading tokens, so the normal FIRST-based dispatcher can\n                    // decide. No rewrite needed.\n                    resultAlt.push(el);\n                    continue;\n                }\n                // Joint vocab: union of everything the probe might need to\n                // consume. Includes the disambiguator, which we then remove so\n                // the probe stops on it and the peek works.\n                const vocab = new Map([...xVocab, ...yVocab]);\n                const d = info.disambiguator;\n                const dKey = d.kind === 'term' ? termKey(d)\n                    : d.kind === 'regex' ? regexKey(d)\n                        : null;\n                if (dKey)\n                    vocab.delete(dKey);\n                const dispatchName = freshName(`${prod.name}$pd${i}`);\n                const probeName = freshName(`${dispatchName}$probe`);\n                const withName = freshName(`${dispatchName}$with`);\n                const noName = freshName(`${dispatchName}$no`);\n                // Synthesise the probe helper.\n                extra.push({\n                    name: probeName,\n                    alts: [],\n                    probeHelper: { vocabElements: [...vocab.values()] },\n                    nodeKind: 'helper',\n                });\n                // Synthesise the committed branches. `with` = X D Y, `no` = Y.\n                extra.push({\n                    name: withName,\n                    alts: [[...info.xSeq, info.disambiguator, ...ySeq]],\n                    nodeKind: 'helper',\n                });\n                extra.push({\n                    name: noName,\n                    alts: [ySeq],\n                    nodeKind: 'helper',\n                });\n                // Synthesise the dispatcher. The `alts` list is a \"virtual\"\n                // spec — two ref-only alts — that exists solely to feed\n                // computeFirstSets the right FIRST/nullable answers (FIRST\n                // = FIRST(with) ∪ FIRST(no)). The emitter checks\n                // `probeDispatch` first and emits the phase-retry body\n                // instead of compiling `alts`.\n                extra.push({\n                    name: dispatchName,\n                    alts: [\n                        [{ kind: 'ref', name: withName }],\n                        [{ kind: 'ref', name: noName }],\n                    ],\n                    probeDispatch: {\n                        probeRule: probeName,\n                        disambiguator: info.disambiguator,\n                        withBranch: withName,\n                        noBranch: noName,\n                    },\n                    nodeKind: 'helper',\n                });\n                reports.push({\n                    rule: prod.name, altIdx, optIdx: i,\n                    reason: `optional prefix shares vocabulary with tail`,\n                    resolved: true,\n                });\n                resultAlt.push({ kind: 'ref', name: dispatchName });\n                // Everything that followed the opt is now inside the dispatcher\n                // (withBranch / noBranch), so skip the rest of the alt.\n                i = alt.length;\n                touched = true;\n            }\n            newAlts.push(resultAlt);\n        }\n        if (touched) {\n            rewritten.push({\n                name: prod.name,\n                alts: newAlts,\n                nodeKind: prod.nodeKind,\n            });\n        }\n        else {\n            rewritten.push(prod);\n        }\n    }\n    return {\n        productions: [...rewritten, ...extra],\n        ambiguities: reports,\n    };\n}\n// Emit a probe helper production. A self-looping rule that matches any\n// one of the vocab tokens and restarts; a final empty-alt fallback\n// ensures the rule NEVER fails — if the current lookahead isn't in the\n// vocab (or we're at #ZZ), the rule pops cleanly. This is the\n// failure-proof property the probe pattern relies on.\nfunction emitProbeHelper(prod, tag, ruleSpec, literals, regexTokens) {\n    const elems = prod.probeHelper.vocabElements;\n    const opens = [];\n    for (const el of elems) {\n        const tok = el.kind === 'term'\n            ? literals.get(termKey(el))\n            : el.kind === 'regex' ? regexTokens.get(regexKey(el))\n                : undefined;\n        if (tok)\n            opens.push({ s: tok, r: prod.name, g: tag });\n    }\n    // Empty fallback — pops without consuming anything. Must be last.\n    opens.push({ g: tag });\n    ruleSpec[prod.name] = { open: opens };\n}\n// Emit a probe-dispatch production. Encodes the three-phase retry\n// pattern; uses only standard jsonic primitives (r:, p:, c:, k:,\n// ctx.mark/rewind/t).\nfunction emitProbeDispatch(prod, tag, ruleSpec, refs, literals, regexTokens) {\n    const { probeRule, disambiguator, withBranch, noBranch } = prod.probeDispatch;\n    const disambiguatorToken = disambiguator.kind === 'term'\n        ? literals.get(termKey(disambiguator))\n        : disambiguator.kind === 'regex'\n            ? regexTokens.get(regexKey(disambiguator))\n            : undefined;\n    if (!disambiguatorToken) {\n        throw new Error(`bnf: probe-dispatch rule '${prod.name}' has unresolvable ` +\n            `disambiguator (kind=${disambiguator.kind})`);\n    }\n    const initMark = refs.register((r, ctx) => {\n        r.k.pd_phase = 0;\n        r.k.pd_mark = ctx.mark();\n    });\n    const decide = refs.register((r, ctx) => {\n        // ctx.t[0] is the first token the probe didn't consume. The probe\n        // never fails, so this always reflects a real position.\n        const peek = ctx.t[0];\n        ctx.rewind(r.k.pd_mark);\n        const matched = peek && peek.name === disambiguatorToken;\n        r.k.pd_phase = matched ? 1 : 2;\n    });\n    const bubble = refs.register((r) => {\n        if (r.child && r.child.node !== undefined)\n            r.node = r.child.node;\n    });\n    ruleSpec[prod.name] = {\n        open: [\n            // Phase 0 — first pass: mark and probe.\n            {\n                c: refs.register((r) => !r.k.pd_phase),\n                a: initMark,\n                p: probeRule,\n                g: tag,\n            },\n            // Phase 1 — disambiguator was seen: commit to X D Y.\n            {\n                c: refs.register((r) => r.k.pd_phase === 1),\n                p: withBranch,\n                g: tag,\n            },\n            // Phase 2 — disambiguator was not seen: commit to Y alone.\n            {\n                c: refs.register((r) => r.k.pd_phase === 2),\n                p: noBranch,\n                g: tag,\n            },\n        ],\n        close: [\n            // Phase 0 close: decide phase based on peek, rewind, retry self.\n            {\n                c: refs.register((r) => r.k.pd_phase === 0),\n                a: decide,\n                r: prod.name,\n                g: tag,\n            },\n            // Phase 1 / 2 close: lift the committed child's node up.\n            { a: bubble, g: tag },\n        ],\n    };\n}\n// Convert a BNF grammar AST into a jsonic GrammarSpec.\nfunction emitGrammarSpec(grammar, opts) {\n    const start = opts?.start ?? grammar.productions[0].name;\n    const tag = opts?.tag ?? 'bnf';\n    // Eliminate direct left recursion (P → P α | β) by rewriting to\n    // the equivalent right-recursive form P → β (α)*, then detect\n    // ambiguous `[X D] Y` optional-prefix patterns and rewrite them\n    // into probe-dispatch helpers; finally flatten any EBNF sugar\n    // (`?`, `*`, `+`, grouping) into plain BNF.\n    grammar = eliminateLeftRecursion(grammar);\n    grammar = rewriteProbeDispatches(grammar);\n    grammar = desugar(grammar);\n    // Allocate a fixed token for each unique literal, and a match\n    // token for each unique regex terminal. Literals are keyed by\n    // (literal, effective-case-sensitivity) so a `%s\"foo\"` (sensitive)\n    // and a bare `\"foo\"` (insensitive) produce distinct tokens.\n    const literals = new Map(); // literal-key -> token name\n    const regexTokens = new Map(); // regex key -> token name\n    const usedNames = new Set();\n    const fixedTokens = {};\n    const matchTokens = {};\n    for (const prod of grammar.productions) {\n        for (const alt of prod.alts) {\n            for (const el of alt) {\n                if (el.kind === 'term') {\n                    const key = termKey(el);\n                    if (!literals.has(key)) {\n                        const name = allocTokenName(el.literal, usedNames);\n                        literals.set(key, name);\n                        if (isEffectivelyCaseSensitive(el)) {\n                            fixedTokens[name] = el.literal;\n                        }\n                        else {\n                            // Insensitive literal with at least one letter — emit\n                            // as an anchored regex with the `i` flag. Mark the\n                            // matcher `eager$` so jsonic's lexer fires it even\n                            // when the current rule's tcol doesn't list its tin.\n                            const re = new RegExp('^' + escapeRegExp(el.literal), 'i');\n                            re.eager$ = true;\n                            matchTokens[name] = re;\n                        }\n                    }\n                }\n                else if (el.kind === 'regex') {\n                    const key = regexKey(el);\n                    if (!regexTokens.has(key)) {\n                        const name = allocTokenName('rx_' + el.pattern, usedNames);\n                        regexTokens.set(key, name);\n                        matchTokens[name] = new RegExp('^' + el.pattern, el.flags);\n                    }\n                }\n            }\n        }\n        // Probe-helper productions store their vocab as BnfElements —\n        // walk those too so the required tokens get allocated.\n        if (prod.probeHelper) {\n            for (const el of prod.probeHelper.vocabElements) {\n                if (el.kind === 'term') {\n                    const key = termKey(el);\n                    if (!literals.has(key)) {\n                        const name = allocTokenName(el.literal, usedNames);\n                        literals.set(key, name);\n                        if (isEffectivelyCaseSensitive(el)) {\n                            fixedTokens[name] = el.literal;\n                        }\n                        else {\n                            const re = new RegExp('^' + escapeRegExp(el.literal), 'i');\n                            re.eager$ = true;\n                            matchTokens[name] = re;\n                        }\n                    }\n                }\n                else if (el.kind === 'regex') {\n                    const key = regexKey(el);\n                    if (!regexTokens.has(key)) {\n                        const name = allocTokenName('rx_' + el.pattern, usedNames);\n                        regexTokens.set(key, name);\n                        matchTokens[name] = new RegExp('^' + el.pattern, el.flags);\n                    }\n                }\n            }\n        }\n    }\n    const knownRules = new Set(grammar.productions.map((p) => p.name));\n    const { firstSets, nullable } = computeFirstSets(grammar, literals, regexTokens);\n    const refs = new RefRegistry();\n    const ruleSpec = {};\n    for (const prod of grammar.productions) {\n        if (prod.probeHelper) {\n            emitProbeHelper(prod, tag, ruleSpec, literals, regexTokens);\n            continue;\n        }\n        if (prod.probeDispatch) {\n            emitProbeDispatch(prod, tag, ruleSpec, refs, literals, regexTokens);\n            continue;\n        }\n        // Standard path: a (possibly single-segment) set of alternatives\n        // compiled to jsonic alts. Simple alts collapse into `open` alts\n        // directly; multi-segment alts emit a chain of aux rules.\n        emitProduction(prod, grammar, literals, regexTokens, knownRules, tag, ruleSpec, firstSets, nullable, refs);\n    }\n    // Wrap the user-visible start rule in a synthetic rule that\n    // explicitly consumes #ZZ. Without this, a user rule that pops\n    // without matching the end-of-source token lets trailing content\n    // slip past jsonic's post-loop endtkn check (the lookahead buffer\n    // outlives the parse loop).\n    const startWrapper = '__start__';\n    ruleSpec[startWrapper] = {\n        open: [{\n                p: start,\n                g: tag,\n            }],\n        close: [{\n                s: '#ZZ',\n                // Return the start rule's AST node directly — the `__start__`\n                // wrapper exists only to ensure end-of-source gets consumed.\n                // The caller of `jsonic(src)` receives the tagged user-rule\n                // node (e.g. `{rule: 'URI', src, kids: [...]}`) unadorned.\n                a: refs.register((r) => {\n                    if (r.child && r.child.node !== undefined) {\n                        r.node = r.child.node;\n                    }\n                }),\n                g: tag,\n            }],\n    };\n    const options = {\n        fixed: { token: fixedTokens },\n        rule: { start: startWrapper },\n    };\n    if (Object.keys(matchTokens).length > 0) {\n        options.match = { token: matchTokens };\n    }\n    const spec = {\n        ref: refs.map,\n        options,\n        rule: ruleSpec,\n    };\n    return spec;\n}\n// Break an alternative into segments. Each segment is a (possibly\n// empty) run of terminal tokens followed by at most one rule\n// reference. A single-segment alt has at most one ref, located at the\n// very end; everything else has two or more segments.\nfunction segmentize(alt, literals, regexTokens) {\n    const segs = [];\n    let current = { terms: [], ref: null };\n    for (const el of alt) {\n        if (el.kind === 'term') {\n            current.terms.push(literals.get(termKey(el)));\n        }\n        else if (el.kind === 'regex') {\n            const key = regexKey(el);\n            current.terms.push(regexTokens.get(key));\n        }\n        else if (el.kind === 'ref') {\n            current.ref = el.name;\n            segs.push(current);\n            current = { terms: [], ref: null };\n        }\n        else {\n            // `opt`, `star`, `plus`, `group` must have been desugared\n            // before reaching the emitter.\n            throw new Error(`bnf: internal — unexpected element kind '${el.kind}' in emitter`);\n        }\n    }\n    if (current.terms.length > 0 || segs.length === 0) {\n        segs.push(current);\n    }\n    return segs;\n}\nfunction regexKey(el) {\n    return `/${el.pattern}/${el.flags}`;\n}\nfunction isSingleSegment(alt) {\n    let sawRef = false;\n    for (const el of alt) {\n        if (el.kind === 'ref') {\n            if (sawRef)\n                return false;\n            sawRef = true;\n        }\n        else if (el.kind === 'term' || el.kind === 'regex') {\n            if (sawRef)\n                return false; // terminal after a ref — multi-segment\n        }\n        else {\n            // Desugar should have eliminated sugar kinds.\n            return false;\n        }\n    }\n    return true;\n}\nfunction validateRefs(alt, knownRules, ruleName) {\n    for (const el of alt) {\n        if (el.kind === 'ref' && !knownRules.has(el.name)) {\n            throw new Error(`bnf: rule '${ruleName}' references unknown rule '${el.name}'`);\n        }\n    }\n}\n// Registry used by the emitter to allocate unique `@`-prefixed\n// FuncRef names for inline action functions. The resulting spec is\n// still declarative: every function appears once, keyed by name,\n// under the spec's `ref` map.\nclass RefRegistry {\n    constructor() {\n        this.refs = {};\n        this.counter = 0;\n    }\n    register(fn) {\n        const name = `@bnf_a${this.counter++}`;\n        this.refs[name] = fn;\n        return name;\n    }\n    get map() {\n        return this.refs;\n    }\n}\nfunction mkAstNode(ruleName, nodeKind) {\n    return nodeKind === 'user'\n        ? { rule: ruleName, src: '', kids: [] }\n        : { src: '', kids: [] };\n}\nfunction segmentToAlt(seg, tag, refs, initNode, ruleName, nodeKind) {\n    const spec = { g: tag };\n    if (seg.terms.length > 0)\n        spec.s = seg.terms.join(' ');\n    if (seg.ref)\n        spec.p = seg.ref;\n    // Default tree-building: accumulate each matched terminal's source\n    // text into `r.node.src`. Head alts also allocate a fresh AST node\n    // so the child doesn't inherit (and then mutate) its parent's.\n    const nterms = seg.terms.length;\n    if (nterms > 0 || initNode) {\n        spec.a = refs.register((r) => {\n            if (initNode)\n                r.node = mkAstNode(ruleName, nodeKind);\n            const n = r.node;\n            for (let i = 0; i < nterms; i++)\n                n.src += r.o[i].src;\n        });\n    }\n    return spec;\n}\n// Close-state action: merge the just-returned child rule's AST node\n// into the current rule's. Tagged children (user rules) get pushed\n// verbatim into `kids`; untagged (helper / core) flatten — their\n// `src` appends and their `kids` extend. Either way `src`\n// concatenates so every ancestor's `.src` reflects everything it\n// matched.\nfunction captureChildRef(refs, ruleName, nodeKind) {\n    return refs.register((r) => {\n        if (r.node == null)\n            r.node = mkAstNode(ruleName, nodeKind);\n        const n = r.node;\n        const c = r.child && r.child.node;\n        if (c == null)\n            return;\n        if (typeof c !== 'object' || !('src' in c)) {\n            // Legacy shape — wrap as a leaf kid.\n            n.kids.push(c);\n            return;\n        }\n        // Defensive: if the child somehow shares this rule's node\n        // object, skip the merge rather than push a self-reference. (A\n        // properly-emitted grammar always allocates fresh child nodes.)\n        if (c === n)\n            return;\n        n.src += c.src;\n        if (c.rule)\n            n.kids.push(c);\n        else if (Array.isArray(c.kids))\n            n.kids.push(...c.kids);\n    });\n}\nfunction emitProduction(prod, grammar, literals, regexTokens, knownRules, tag, ruleSpec, firstSets, nullable, refs) {\n    for (const alt of prod.alts) {\n        validateRefs(alt, knownRules, prod.name);\n    }\n    const allSimple = prod.alts.every(isSingleSegment);\n    if (allSimple) {\n        // Every alternative collapses to one jsonic alt — emit them\n        // directly into the production's open state. This is a head\n        // rule, so each alt initialises its own node array. Empty alts\n        // are sorted to the end so jsonic's first-match-wins doesn't let\n        // them short-circuit non-empty alternatives.\n        const ordered = [\n            ...prod.alts.filter((alt) => alt.length > 0),\n            ...prod.alts.filter((alt) => alt.length === 0),\n        ];\n        // Ref-only alternatives have no terminal to discriminate on, so\n        // jsonic's first-match-wins would silently let them shadow any\n        // later alternative. Guard them with FIRST-set peeks when the\n        // production has more than one alt.\n        const needsPeek = ordered.length > 1;\n        const opens = [];\n        for (const alt of ordered) {\n            const segs = segmentize(alt, literals, regexTokens);\n            const seg = segs[0];\n            const isRefOnly = alt.length >= 1 &&\n                alt.every((el) => el.kind === 'ref') &&\n                seg.terms.length === 0 &&\n                seg.ref != null;\n            const prodKind = prod.nodeKind ?? 'user';\n            if (needsPeek && isRefOnly) {\n                const firstTokens = firstOfAlt(alt, literals, regexTokens, firstSets, nullable);\n                if (firstTokens) {\n                    for (const tok of firstTokens) {\n                        opens.push({\n                            s: tok,\n                            b: 1,\n                            p: seg.ref,\n                            a: refs.register((r) => {\n                                r.node = mkAstNode(prod.name, prodKind);\n                            }),\n                            g: tag,\n                        });\n                    }\n                    continue;\n                }\n            }\n            opens.push(segmentToAlt(seg, tag, refs, true, prod.name, prodKind));\n        }\n        const rs = { open: opens };\n        // If any alt has a push, the close state must capture the\n        // returned child. Add a universal fallback close alt whose\n        // action is a no-op when there was no push.\n        if (prod.alts.some((alt) => alt.some((el) => el.kind === 'ref'))) {\n            rs.close = [{\n                    a: captureChildRef(refs, prod.name, prod.nodeKind ?? 'user'),\n                    g: tag,\n                }];\n        }\n        ruleSpec[prod.name] = rs;\n        return;\n    }\n    if (prod.alts.length === 1) {\n        // Single-alt, multi-segment: chain rules directly on the\n        // production.\n        emitChain(prod.name, prod.alts[0], literals, regexTokens, tag, ruleSpec, refs, prod.nodeKind ?? 'user');\n        return;\n    }\n    // Multi-alt with at least one multi-segment alternative: emit a\n    // dispatcher. Each alt becomes its own chained impl rule\n    // (`<prodname>$alt<i>`); the main rule's open peeks the first token\n    // and pushes the matching impl rule. Using `p:` (not `r:`) keeps\n    // the parent's `child` pointer valid so the parent can read the\n    // impl's node in its close-state action.\n    const dispatchOpen = [];\n    let emptyAltSeen = false;\n    for (let i = 0; i < prod.alts.length; i++) {\n        const alt = prod.alts[i];\n        const implName = `${prod.name}$alt${i}`;\n        if (alt.length === 0) {\n            // Empty alt acts as fallback — handled after the loop.\n            emptyAltSeen = true;\n            continue;\n        }\n        emitChain(implName, alt, literals, regexTokens, tag, ruleSpec, refs, 'helper');\n        // Fan out this alt into one dispatch entry per concrete token\n        // sequence it can start with. Up to LOOKAHEAD_K tokens per\n        // prefix is enough for the grammars this converter targets; a\n        // ref with multiple alts produces one prefix per sub-alt so\n        // overlapping FIRST sets between competing alts can still be\n        // separated by their second (or later) token.\n        // The dispatcher itself is a user (or helper) rule — it must\n        // allocate its own AST node on every dispatch alt, otherwise the\n        // node inherited from the parent via makeRule(ctx, rule.node)\n        // would be shared and the dispatcher's captureChildRef would\n        // mutate the parent's tree.\n        const dispatchKind = prod.nodeKind ?? 'user';\n        const initDispatchNode = refs.register((r) => {\n            r.node = mkAstNode(prod.name, dispatchKind);\n        });\n        const LOOKAHEAD_K = 4;\n        const prefixes = altPrefixes(alt, grammar, literals, regexTokens, LOOKAHEAD_K);\n        const usable = prefixes.filter((p) => p.length > 0);\n        if (usable.length > 0) {\n            for (const p of usable) {\n                dispatchOpen.push({\n                    s: p.join(' '),\n                    b: p.length,\n                    p: implName,\n                    a: initDispatchNode,\n                    g: tag,\n                });\n            }\n        }\n        else {\n            const firstTokens = firstOfAlt(alt, literals, regexTokens, firstSets, nullable);\n            if (firstTokens === null) {\n                throw new Error(`bnf: rule '${prod.name}' alternative ${i} is nullable ` +\n                    `but is not the only empty alt; FIRST set is ambiguous`);\n            }\n            for (const tok of firstTokens) {\n                dispatchOpen.push({\n                    s: tok, b: 1, p: implName, a: initDispatchNode, g: tag,\n                });\n            }\n        }\n    }\n    if (emptyAltSeen) {\n        // Fallback: matches any token (or none), pops immediately with\n        // an empty tree. Tagged with the user rule name so a consumer\n        // walking the tree still gets a placeholder node for the empty\n        // alternative.\n        const fallbackKind = prod.nodeKind ?? 'user';\n        dispatchOpen.push({\n            a: refs.register((r) => {\n                r.node = mkAstNode(prod.name, fallbackKind);\n            }),\n            g: tag,\n        });\n    }\n    ruleSpec[prod.name] = {\n        open: dispatchOpen,\n        close: [{\n                // Merge the chosen impl's result up into the dispatcher's node,\n                // tagged with the user rule name (so the enclosing rule sees a\n                // `{rule, src, kids}` child, not the impl chain's transparent\n                // `{src, kids}`).\n                a: captureChildRef(refs, prod.name, prod.nodeKind ?? 'user'),\n                g: tag,\n            }],\n    };\n}\n// Emit a (possibly single-step) chain of rules for one alt under the\n// given head rule name. Segment 0 goes into `headName`; later\n// segments get synthetic `<headName>$stepN` continuations.\n//\n// `headKind` controls the head rule's AST node shape: 'user' tags\n// the head's node with the rule name; 'helper' leaves it untagged\n// (transparent to the enclosing user rule). Step rules are always\n// helpers — they inherit and accumulate into the head's node via\n// `r:` replacement.\nfunction emitChain(headName, alt, literals, regexTokens, tag, ruleSpec, refs, headKind = 'helper') {\n    const segs = segmentize(alt, literals, regexTokens);\n    const chainName = (i) => i === 0 ? headName : `${headName}$step${i}`;\n    for (let i = 0; i < segs.length; i++) {\n        const name = chainName(i);\n        const seg = segs[i];\n        const kind = i === 0 ? headKind : 'helper';\n        // Only the head of the chain initialises the node object; later\n        // steps inherit and continue to accumulate into it via `r:`.\n        const open = [segmentToAlt(seg, tag, refs, i === 0, name, kind)];\n        const rs = { open };\n        const isLast = i === segs.length - 1;\n        if (!isLast) {\n            // Non-last step: after the push returns, capture the child's\n            // node and replace with the next step rule.\n            rs.close = [{\n                    r: chainName(i + 1),\n                    a: captureChildRef(refs, name, kind),\n                    g: tag,\n                }];\n        }\n        else if (seg.ref) {\n            // Last step, but it had a push — we still need to capture the\n            // final child before popping.\n            rs.close = [{ a: captureChildRef(refs, name, kind), g: tag }];\n        }\n        ruleSpec[name] = rs;\n    }\n}\n// Compute FIRST(ref) for every production, plus which productions\n// are nullable (can derive the empty string). Iterates to a fixed\n// point. Terminals in FIRST sets are represented by their allocated\n// token names (e.g. `#X`).\nfunction computeFirstSets(grammar, literals, regexTokens) {\n    const firstSets = new Map();\n    const nullable = new Set();\n    for (const p of grammar.productions)\n        firstSets.set(p.name, new Set());\n    let changed = true;\n    while (changed) {\n        changed = false;\n        for (const prod of grammar.productions) {\n            const first = firstSets.get(prod.name);\n            for (const alt of prod.alts) {\n                // Walk the alt, accumulating FIRST until a non-nullable\n                // position is hit.\n                let altNullable = true;\n                for (const el of alt) {\n                    if (el.kind === 'term' || el.kind === 'regex') {\n                        const tok = el.kind === 'term'\n                            ? literals.get(termKey(el))\n                            : regexTokens.get(regexKey(el));\n                        if (!first.has(tok)) {\n                            first.add(tok);\n                            changed = true;\n                        }\n                        altNullable = false;\n                        break;\n                    }\n                    if (el.kind === 'ref') {\n                        const refFirst = firstSets.get(el.name) ?? new Set();\n                        for (const tok of refFirst) {\n                            if (!first.has(tok)) {\n                                first.add(tok);\n                                changed = true;\n                            }\n                        }\n                        if (!nullable.has(el.name)) {\n                            altNullable = false;\n                            break;\n                        }\n                        continue;\n                    }\n                    // Desugar should have eliminated other kinds.\n                    throw new Error(`bnf: internal — unexpected kind in FIRST: ${el.kind}`);\n                }\n                if (altNullable && !nullable.has(prod.name)) {\n                    nullable.add(prod.name);\n                    changed = true;\n                }\n            }\n        }\n    }\n    return { firstSets, nullable };\n}\n// FIRST set for a specific alternative (not the whole production).\n// Returns null if the alt is nullable — the caller must treat that\n// case separately (typically as a fallback empty alt).\nfunction firstOfAlt(alt, literals, regexTokens, firstSets, nullable) {\n    const out = new Set();\n    for (const el of alt) {\n        if (el.kind === 'term' || el.kind === 'regex') {\n            const tok = el.kind === 'term'\n                ? literals.get(termKey(el))\n                : regexTokens.get(regexKey(el));\n            out.add(tok);\n            return out;\n        }\n        if (el.kind === 'ref') {\n            const rf = firstSets.get(el.name) ?? new Set();\n            for (const tok of rf)\n                out.add(tok);\n            if (!nullable.has(el.name))\n                return out;\n            // else keep walking into the next element\n            continue;\n        }\n        throw new Error(`bnf: internal — unexpected kind in firstOfAlt: ${el.kind}`);\n    }\n    // Alt is nullable — no non-empty prefix.\n    return null;\n}\n// Longest deterministic terminal prefix of a rule — the longest\n// sequence of tokens that every alternative of the rule starts\n// with. Refs are followed into their target rule, with a `visited`\n// set guarding cycles. An empty array means there's no confident\n// prefix (the rule either has divergent alts, starts with a multi-\n// alt ref, or hits a cycle), so the caller should fall back to a\n// single-token FIRST-set lookahead instead.\nfunction ruleLiteralPrefix(name, grammar, literals, regexTokens, visited) {\n    if (visited.has(name))\n        return [];\n    const next = new Set(visited);\n    next.add(name);\n    const prod = grammar.productions.find((p) => p.name === name);\n    if (!prod || prod.alts.length === 0)\n        return [];\n    const prefixes = prod.alts.map((alt) => altLiteralPrefix(alt, grammar, literals, regexTokens, next));\n    if (prefixes.some((p) => p.length === 0))\n        return [];\n    const minLen = Math.min(...prefixes.map((p) => p.length));\n    const common = [];\n    for (let i = 0; i < minLen; i++) {\n        const tok = prefixes[0][i];\n        if (prefixes.every((p) => p[i] === tok))\n            common.push(tok);\n        else\n            break;\n    }\n    return common;\n}\nfunction altLiteralPrefix(alt, grammar, literals, regexTokens, visited) {\n    const out = [];\n    for (const el of alt) {\n        if (el.kind === 'term') {\n            out.push(literals.get(termKey(el)));\n        }\n        else if (el.kind === 'regex') {\n            out.push(regexTokens.get(regexKey(el)));\n        }\n        else if (el.kind === 'ref') {\n            const sub = ruleLiteralPrefix(el.name, grammar, literals, regexTokens, visited);\n            // Take the ref's literal prefix and stop — we can't see past\n            // the ref without more expensive analysis.\n            out.push(...sub);\n            return out;\n        }\n        else {\n            return out;\n        }\n    }\n    return out;\n}\n// Enumerate concrete token-sequence prefixes an alternative can\n// start with, each at most `maxK` tokens long. Refs with multiple\n// alternatives fan out into one prefix per sub-alternative so the\n// caller can emit a dedicated dispatch alt for each path. When a\n// ref cycles back or exhausts depth, the path is *terminated* at\n// the tokens accumulated so far — the `done` flag is propagated\n// out of nested calls so a truncated sub-prefix is never extended\n// with tokens from elements the outer alt happens to list after the\n// cycled ref.\nfunction altPrefixesRaw(alt, grammar, literals, regexTokens, maxK, visited = new Set()) {\n    let paths = [{ tokens: [], done: false }];\n    for (const el of alt) {\n        const next = [];\n        for (const p of paths) {\n            if (p.done || p.tokens.length >= maxK) {\n                next.push(p);\n                continue;\n            }\n            if (el.kind === 'term') {\n                next.push({\n                    tokens: [...p.tokens, literals.get(termKey(el))],\n                    done: false,\n                });\n            }\n            else if (el.kind === 'regex') {\n                next.push({\n                    tokens: [...p.tokens, regexTokens.get(regexKey(el))],\n                    done: false,\n                });\n            }\n            else if (el.kind === 'ref') {\n                if (visited.has(el.name)) {\n                    next.push({ tokens: p.tokens, done: true });\n                    continue;\n                }\n                const childVisited = new Set(visited);\n                childVisited.add(el.name);\n                const target = grammar.productions.find((pr) => pr.name === el.name);\n                if (!target || target.alts.length === 0) {\n                    next.push({ tokens: p.tokens, done: true });\n                    continue;\n                }\n                for (const sub of target.alts) {\n                    const subPaths = altPrefixesRaw(sub, grammar, literals, regexTokens, maxK - p.tokens.length, childVisited);\n                    for (const sp of subPaths) {\n                        next.push({\n                            tokens: [...p.tokens, ...sp.tokens],\n                            // Propagate `done` so the outer loop won't extend a\n                            // cycle-truncated sub-prefix.\n                            done: sp.done,\n                        });\n                    }\n                }\n            }\n            else {\n                // Desugar should have eliminated group/star/etc. at this point.\n                next.push({ tokens: p.tokens, done: true });\n            }\n        }\n        paths = next;\n        if (paths.every((p) => p.done || p.tokens.length >= maxK))\n            break;\n    }\n    return paths;\n}\nfunction altPrefixes(alt, grammar, literals, regexTokens, maxK) {\n    const raw = altPrefixesRaw(alt, grammar, literals, regexTokens, maxK);\n    const seen = new Set();\n    const out = [];\n    for (const p of raw) {\n        const key = p.tokens.join(' ');\n        if (!seen.has(key)) {\n            seen.add(key);\n            out.push(p.tokens);\n        }\n    }\n    return out;\n}\n// A quoted-string literal is effectively case-sensitive either\n// when the user explicitly wrote `%s\"…\"` or when it contains no\n// ASCII letters (there's nothing to fold — `\"+\"` matches `+` in\n// any \"case\").\nfunction isEffectivelyCaseSensitive(el) {\n    if (el.caseSensitive === true)\n        return true;\n    return !/[A-Za-z]/.test(el.literal);\n}\n// Map a term element to the key used to look up (or allocate) its\n// emitted token. The key folds together the literal and its\n// effective case-sensitivity so a sensitive and an insensitive\n// occurrence of the same string are distinct tokens.\nfunction termKey(el) {\n    return (isEffectivelyCaseSensitive(el) ? 'cs:' : 'ci:') + el.literal;\n}\nfunction escapeRegExp(s) {\n    return s.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\n}\n// Decode an ABNF numeric value (`%xNN`, `%dNN`, `%bNN`, or one of\n// the range/concatenation forms) into a `BnfElement`.\n//\n//   %x61            => single-char term \"a\"\n//   %x66.6f.6f      => concatenated term \"foo\"\n//   %x30-39         => regex character class [\\u0030-\\u0039]\n//\n// Hex is case-insensitive; decimal and binary accept only digits\n// in their respective ranges. Range endpoints must be the same\n// base as the prefix (RFC 5234 doesn't allow mixing).\nfunction parseNumericValue(src) {\n    const base = src[1].toLowerCase();\n    const radix = base === 'x' ? 16 : base === 'd' ? 10 : 2;\n    const body = src.slice(2);\n    if (body.includes('-')) {\n        const [loStr, hiStr] = body.split('-');\n        const lo = parseInt(loStr, radix);\n        const hi = parseInt(hiStr, radix);\n        if (lo === hi) {\n            return { kind: 'term', literal: String.fromCharCode(lo) };\n        }\n        const toEsc = (n) => '\\\\u' + n.toString(16).padStart(4, '0');\n        return {\n            kind: 'regex',\n            pattern: '[' + toEsc(lo) + '-' + toEsc(hi) + ']',\n            flags: '',\n        };\n    }\n    const parts = body.split('.');\n    const chars = parts.map((n) => String.fromCharCode(parseInt(n, radix)));\n    return { kind: 'term', literal: chars.join('') };\n}\nfunction allocTokenName(literal, used) {\n    const base = literal\n        .replace(/[^A-Za-z0-9]/g, '_')\n        .toUpperCase()\n        .replace(/^_+|_+$/g, '');\n    const candidate = base.length > 0 ? '#' + base : '#T';\n    if (!used.has(candidate)) {\n        used.add(candidate);\n        return candidate;\n    }\n    let i = 1;\n    while (used.has(candidate + i))\n        i++;\n    const chosen = candidate + i;\n    used.add(chosen);\n    return chosen;\n}\n// Public entry point: take BNF source and return a jsonic GrammarSpec.\nfunction bnf(src, opts) {\n    const grammar = parseBnf(src);\n    return emitGrammarSpec(grammar, opts);\n}\n//# sourceMappingURL=bnf.js.map"
  },
  {
    "path": "dist/debug.d.ts",
    "content": "import type { Plugin } from './jsonic';\ndeclare const Debug: Plugin;\nexport { Debug };\n"
  },
  {
    "path": "dist/debug.js",
    "content": "\"use strict\";\n/* Copyright (c) 2021-2023 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.Debug = void 0;\nconst jsonic_1 = require(\"./jsonic\");\nconst DEFAULTS = {\n    print: true,\n    trace: {\n        step: true,\n        rule: true,\n        lex: true,\n        parse: true,\n        node: true,\n        stack: true,\n    },\n};\nconst { entries, tokenize } = jsonic_1.util;\nconst Debug = (jsonic, options) => {\n    options.trace =\n        true === options.trace ? { ...DEFAULTS.trace } : options.trace;\n    const { keys, values, entries } = jsonic.util;\n    jsonic.debug = {\n        describe: function () {\n            let cfg = jsonic.internal().config;\n            let match = cfg.lex.match;\n            let rules = jsonic.rule();\n            return [\n                '========= TOKENS ========',\n                Object.entries(cfg.t)\n                    .filter((te) => 'string' === typeof te[1])\n                    .map((te) => {\n                    return ('  ' +\n                        te[0] +\n                        '\\t' +\n                        te[1] +\n                        '\\t' +\n                        ((s) => (s ? '\"' + s + '\"' : ''))(cfg.fixed.ref[te[0]] || ''));\n                })\n                    .join('\\n'),\n                '\\n',\n                Object.entries(cfg.tokenSet)\n                    .map((te) => {\n                    return ('    ' +\n                        te[0] +\n                        '\\t' +\n                        Object.keys(cfg.tokenSetTins[te[0]] ?? []));\n                })\n                    .join('\\n'),\n                '\\n',\n                ,\n                '========= RULES =========',\n                ruleTree(jsonic, keys(rules), rules),\n                '\\n',\n                '========= ALTS =========',\n                values(rules)\n                    .map((rs) => '  ' +\n                    rs.name +\n                    ':\\n' +\n                    descAlt(jsonic, rs, 'open') +\n                    descAlt(jsonic, rs, 'close'))\n                    .join('\\n\\n'),\n                '\\n',\n                '========= LEXER =========',\n                '  ' +\n                    ((match &&\n                        match.map((m) => m.order + ': ' + m.matcher + ' (' + m.make.name + ')')) ||\n                        []).join('\\n  '),\n                '\\n',\n                '\\n',\n                '========= PLUGIN =========',\n                '  ' +\n                    jsonic\n                        .internal()\n                        .plugins.map((p) => p.name +\n                        (p.options\n                            ? entries(p.options).reduce((s, e) => (s += '\\n    ' + e[0] + ': ' + JSON.stringify(e[1])), '')\n                            : ''))\n                        .join('\\n  '),\n                '\\n',\n            ].join('\\n');\n        },\n    };\n    const origUse = jsonic.use.bind(jsonic);\n    jsonic.use = (...args) => {\n        let self = origUse(...args);\n        if (options.print) {\n            self\n                .internal()\n                .config.debug.get_console()\n                .log('USE:', args[0].name, '\\n\\n', self.debug.describe());\n        }\n        return self;\n    };\n    if (options.trace) {\n        jsonic.options({\n            parse: {\n                prepare: {\n                    debug: (_jsonic, ctx, _meta) => {\n                        const console_log = ctx.cfg.debug.get_console().log;\n                        console_log('\\n========= TRACE ==========');\n                        ctx.log =\n                            ctx.log ||\n                                ((kind, ...rest) => {\n                                    if (LOGKIND[kind] && options.trace[kind]) {\n                                        console_log(LOGKIND[kind](...rest)\n                                            .filter((item) => 'object' != typeof item)\n                                            .map((item) => 'function' == typeof item ? item.name : item)\n                                            .join('  '));\n                                    }\n                                });\n                    },\n                },\n            },\n        });\n    }\n};\nexports.Debug = Debug;\nfunction descAlt(jsonic, rs, kind) {\n    const { entries } = jsonic.util;\n    return 0 === rs.def[kind].length\n        ? ''\n        : '    ' +\n            kind.toUpperCase() +\n            ':\\n' +\n            rs.def[kind]\n                .map((a, i) => '      ' +\n                ('' + i).padStart(5, ' ') +\n                ' ' +\n                ('[' +\n                    (a.s || [])\n                        .map((tin) => null == tin\n                        ? '***INVALID***'\n                        : 'number' === typeof tin\n                            ? jsonic.token[tin]\n                            : Array.isArray(tin) ? '[' + tin.map((t) => jsonic.token[t]) + ']'\n                                : ('' + tin))\n                        .join(' ') +\n                    '] ').padEnd(32, ' ') +\n                (a.r ? ' r=' + ('string' === typeof a.r ? a.r : '<F>') : '') +\n                (a.p ? ' p=' + ('string' === typeof a.p ? a.p : '<F>') : '') +\n                (!a.r && !a.p ? '\\t' : '') +\n                '\\t' +\n                (null == a.b ? '' : 'b=' + a.b) +\n                '\\t' +\n                (null == a.n\n                    ? ''\n                    : 'n=' +\n                        entries(a.n).map(([k, v]) => k + ':' + v)) +\n                '\\t' +\n                (null == a.a ? '' : 'A') +\n                (null == a.c ? '' : 'C') +\n                (null == a.h ? '' : 'H') +\n                '\\t' +\n                (null == a.c?.n\n                    ? '\\t'\n                    : ' CN=' +\n                        entries(a.c.n).map(([k, v]) => k + ':' + v)) +\n                (null == a.c?.d ? '' : ' CD=' + a.c.d) +\n                (a.g ? '\\tg=' + a.g : ''))\n                .join('\\n') +\n            '\\n';\n}\nfunction ruleTree(jsonic, rn, rsm) {\n    const { values, omap } = jsonic.util;\n    return rn.reduce((a, n) => ((a +=\n        '  ' +\n            n +\n            ':\\n    ' +\n            values(omap({\n                op: ruleTreeStep(rsm, n, 'open', 'p'),\n                or: ruleTreeStep(rsm, n, 'open', 'r'),\n                cp: ruleTreeStep(rsm, n, 'close', 'p'),\n                cr: ruleTreeStep(rsm, n, 'close', 'r'),\n            }, ([n, d]) => [\n                1 < d.length ? n : undefined,\n                n + ': ' + d,\n            ])).join('\\n    ') +\n            '\\n'),\n        a), '');\n}\nfunction ruleTreeStep(rsm, name, state, step) {\n    return [\n        ...new Set(rsm[name].def[state]\n            .filter((alt) => alt[step])\n            .map((alt) => alt[step])\n            .map((step) => ('string' === typeof step ? step : '<F>'))),\n    ].join(' ');\n}\nfunction descTokenState(ctx) {\n    return ('[' +\n        (ctx.NOTOKEN === ctx.t0 ? '' : ctx.F(ctx.t0.src)) +\n        (ctx.NOTOKEN === ctx.t1 ? '' : ' ' + ctx.F(ctx.t1.src)) +\n        ']~[' +\n        (ctx.NOTOKEN === ctx.t0 ? '' : tokenize(ctx.t0.tin, ctx.cfg)) +\n        (ctx.NOTOKEN === ctx.t1 ? '' : ' ' + tokenize(ctx.t1.tin, ctx.cfg)) +\n        ']');\n}\nfunction descParseState(ctx, rule, lex) {\n    return (ctx.F(ctx.src().substring(lex.pnt.sI, lex.pnt.sI + 16)).padEnd(18, ' ') +\n        ' ' +\n        descTokenState(ctx).padEnd(34, ' ') +\n        ' ' +\n        ('' + rule.d).padStart(4, ' '));\n}\nfunction descRuleState(ctx, rule) {\n    let en = entries(rule.n);\n    let eu = entries(rule.u);\n    let ek = entries(rule.k);\n    return ('' +\n        (0 === en.length\n            ? ''\n            : ' N<' +\n                en\n                    .filter((n) => n[1])\n                    .map((n) => n[0] + '=' + n[1])\n                    .join(';') +\n                '>') +\n        (0 === eu.length\n            ? ''\n            : ' U<' + eu.map((u) => u[0] + '=' + ctx.F(u[1])).join(';') + '>') +\n        (0 === ek.length\n            ? ''\n            : ' K<' + ek.map((k) => k[0] + '=' + ctx.F(k[1])).join(';') + '>'));\n}\nfunction descAltSeq(alt, cfg) {\n    return ('[' +\n        (alt.s || [])\n            .map((tin) => 'number' === typeof tin\n            ? tokenize(tin, cfg)\n            : Array.isArray(tin)\n                ? '[' + tin.map((t) => tokenize(t, cfg)) + ']'\n                : '')\n            .join(' ') +\n        '] ');\n}\nconst LOG = {\n    RuleState: {\n        o: jsonic_1.S.open.toUpperCase(),\n        c: jsonic_1.S.close.toUpperCase(),\n    },\n};\nconst LOGKIND = {\n    step: (...rest) => rest,\n    stack: (ctx, rule, lex) => [\n        jsonic_1.S.logindent + jsonic_1.S.stack,\n        descParseState(ctx, rule, lex),\n        // S.indent.repeat(Math.max(rule.d + ('o' === rule.state ? -1 : 1), 0)) +\n        jsonic_1.S.indent.repeat(rule.d) +\n            '/' +\n            ctx.rs\n                // .slice(0, ctx.rsI)\n                .slice(0, rule.d)\n                .map((r) => r.name + '~' + r.i)\n                .join('/'),\n        '~',\n        '/' +\n            ctx.rs\n                // .slice(0, ctx.rsI)\n                .slice(0, rule.d)\n                .map((r) => ctx.F(r.node))\n                .join('/'),\n        // 'd=' + rule.d,\n        //'rsI=' + ctx.rsI,\n        ctx,\n        rule,\n        lex,\n    ],\n    rule: (ctx, rule, lex) => [\n        rule,\n        ctx,\n        lex,\n        jsonic_1.S.logindent + jsonic_1.S.rule + jsonic_1.S.space,\n        descParseState(ctx, rule, lex),\n        jsonic_1.S.indent.repeat(rule.d) +\n            (rule.name + '~' + rule.i + jsonic_1.S.colon + LOG.RuleState[rule.state]).padEnd(16),\n        ('prev=' +\n            rule.prev.i +\n            ' parent=' +\n            rule.parent.i +\n            ' child=' +\n            rule.child.i).padEnd(28),\n        descRuleState(ctx, rule),\n    ],\n    node: (ctx, rule, lex, next) => [\n        rule,\n        ctx,\n        lex,\n        next,\n        jsonic_1.S.logindent + jsonic_1.S.node + jsonic_1.S.space,\n        descParseState(ctx, rule, lex),\n        jsonic_1.S.indent.repeat(rule.d) +\n            ('why=' + next.why + jsonic_1.S.space + '<' + ctx.F(rule.node) + '>').padEnd(46),\n        descRuleState(ctx, rule),\n    ],\n    parse: (ctx, rule, lex, match, cond, altI, alt, out) => {\n        let ns = match && out.n ? entries(out.n) : null;\n        let us = match && out.u ? entries(out.u) : null;\n        let ks = match && out.k ? entries(out.k) : null;\n        return [\n            ctx,\n            rule,\n            lex,\n            jsonic_1.S.logindent + jsonic_1.S.parse,\n            descParseState(ctx, rule, lex),\n            jsonic_1.S.indent.repeat(rule.d) + (match ? 'alt=' + altI : 'no-alt'),\n            match && alt ? descAltSeq(alt, ctx.cfg) : '',\n            match && out.g ? 'g:' + out.g + ' ' : '',\n            (match && out.p ? 'p:' + out.p + ' ' : '') +\n                (match && out.r ? 'r:' + out.r + ' ' : '') +\n                (match && out.b ? 'b:' + out.b + ' ' : ''),\n            alt && alt.c ? 'c:' + cond : jsonic_1.EMPTY,\n            null == ns ? '' : 'n:' + ns.map((p) => p[0] + '=' + p[1]).join(';'),\n            null == us ? '' : 'u:' + us.map((p) => p[0] + '=' + p[1]).join(';'),\n            null == ks ? '' : 'k:' + ks.map((p) => p[0] + '=' + p[1]).join(';'),\n        ];\n    },\n    lex: (ctx, rule, lex, pnt, sI, match, tkn, alt, altI, tI) => [\n        jsonic_1.S.logindent + jsonic_1.S.lex + jsonic_1.S.space + jsonic_1.S.space,\n        descParseState(ctx, rule, lex),\n        jsonic_1.S.indent.repeat(rule.d) +\n            // S.indent.repeat(rule.d) + S.lex, // Log entry prefix.\n            // Name of token from tin (token identification numer).\n            tokenize(tkn.tin, ctx.cfg),\n        ctx.F(tkn.src), // Format token src for log.\n        pnt.sI, // Current source index.\n        pnt.rI + ':' + pnt.cI, // Row and column.\n        match?.name || '',\n        alt\n            ? 'on:alt=' +\n                altI +\n                ';' +\n                alt.g +\n                ';t=' +\n                tI +\n                ';' +\n                descAltSeq(alt, ctx.cfg)\n            : '',\n        ctx.F(lex.src.substring(sI, sI + 16)),\n        ctx,\n        rule,\n        lex,\n    ],\n};\nDebug.defaults = DEFAULTS;\n//# sourceMappingURL=debug.js.map"
  },
  {
    "path": "dist/defaults.d.ts",
    "content": "import { Options } from './jsonic';\ndeclare const defaults: Options;\nexport { defaults };\n"
  },
  {
    "path": "dist/defaults.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2023 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.defaults = void 0;\n// Functions that create token matching lexers.\n// The `make*Matcher` functions may optionally initialise\n// and validate Config properties specific to their lexing.\nconst lexer_1 = require(\"./lexer\");\nconst defaults = {\n    // Prevent prototype pollution\n    safe: {\n        key: true,\n    },\n    // Default tag - set your own!\n    tag: '-',\n    // Fixed token lexing.\n    fixed: {\n        // Recognize fixed tokens in the Lexer.\n        lex: true,\n        // Token names.\n        token: {\n            '#OB': '{',\n            '#CB': '}',\n            '#OS': '[',\n            '#CS': ']',\n            '#CL': ':',\n            '#CA': ',',\n        },\n    },\n    match: {\n        lex: true,\n        token: {},\n    },\n    // Token sets.\n    tokenSet: {\n        IGNORE: ['#SP', '#LN', '#CM'],\n        VAL: ['#TX', '#NR', '#ST', '#VL'],\n        KEY: ['#TX', '#NR', '#ST', '#VL'],\n    },\n    // Recognize space characters in the lexer.\n    space: {\n        // Recognize space in the Lexer.\n        lex: true,\n        // Space characters are kept to a minimal set.\n        // Add more from https://en.wikipedia.org/wiki/Whitespace_character as needed.\n        chars: ' \\t',\n    },\n    // Line lexing.\n    line: {\n        // Recognize lines in the Lexer.\n        lex: true,\n        // Line characters.\n        chars: '\\r\\n',\n        // Increments row (aka line) counter.\n        rowChars: '\\n',\n        // Generate separate lexer tokens for each newline.\n        // Note: '\\r\\n' counts as one newline.\n        single: false,\n    },\n    // Text formats.\n    text: {\n        // Recognize text (non-quoted strings) in the Lexer.\n        lex: true,\n    },\n    // Control number formats.\n    number: {\n        // Recognize numbers in the Lexer.\n        lex: true,\n        // Recognize hex numbers (eg. 10 === 0x0a).\n        hex: true,\n        // Recognize octal numbers (eg. 10 === 0o12).\n        oct: true,\n        // Recognize ninary numbers (eg. 10 === 0b1010).\n        bin: true,\n        // All possible number chars. |+-|0|xob|0-9a-fA-F|.e|+-|0-9a-fA-F|\n        // digital: '-1023456789._xoeEaAbBcCdDfF+',\n        // Allow embedded separator. `null` to disable.\n        sep: '_',\n        // Exclude number strings matching this RegExp\n        exclude: undefined,\n    },\n    // Comment markers.\n    // <mark-char>: true -> single line comments\n    // <mark-start>: <mark-end> -> multiline comments\n    comment: {\n        // Recognize comments in the Lexer.\n        lex: true,\n        // TODO: plugin\n        // Balance multiline comments.\n        // balance: true,\n        // Comment markers.\n        def: {\n            hash: { line: true, start: '#', lex: true, eatline: false },\n            slash: { line: true, start: '//', lex: true, eatline: false },\n            multi: {\n                line: false,\n                start: '/' + '*',\n                end: '*' + '/',\n                lex: true,\n                eatline: false,\n            },\n        },\n    },\n    // String formats.\n    string: {\n        // Recognize strings in the Lexer.\n        lex: true,\n        // Quote characters\n        chars: '\\'\"`',\n        // Multiline quote chars.\n        multiChars: '`',\n        // Escape character.\n        escapeChar: '\\\\',\n        // String escape chars.\n        // Denoting char (follows escape char) => actual char.\n        escape: {\n            b: '\\b',\n            f: '\\f',\n            n: '\\n',\n            r: '\\r',\n            t: '\\t',\n            v: '\\v',\n            // These preserve standard escapes when allowUnknown=false.\n            '\"': '\"',\n            \"'\": \"'\",\n            '`': '`',\n            '\\\\': '\\\\',\n            '/': '/',\n        },\n        // Allow unknown escape characters - they are copied to output: '\\w' -> 'w'.\n        allowUnknown: true,\n        // If string lexing fails, instead of error, allow other matchers to try.\n        abandon: false,\n    },\n    // Object formats.\n    map: {\n        // TODO: or trigger error?\n        // Later duplicates extend earlier ones, rather than replacing them.\n        extend: true,\n        // Custom merge function for duplicates (optional).\n        // TODO: needs function signature\n        merge: undefined,\n        // Allow bare colon `:value` in maps, stored as `child$` property.\n        child: false,\n    },\n    // Array formats.\n    list: {\n        // Allow arrays to have properties: `[a:9,0,1]`\n        property: true,\n        // Parse pairs as object elements: `[a:1]` -> `[{\"a\":1}]`\n        // Takes precedence over list.property when true.\n        pair: false,\n        // Parse bare colon as child$ property: `[:1]` -> [] with child$=1\n        // Multiple child values merge.\n        child: false,\n    },\n    // Metadata info markers. When enabled, a non-enumerable marker property\n    // is attached to parsed nodes with metadata (implicit flag, meta bag, etc.).\n    info: {\n        // Attach marker to map nodes.\n        map: false,\n        // Attach marker to list nodes.\n        list: false,\n        // Wrap string values as String objects with marker (quote info).\n        text: false,\n        // Property name for the marker.\n        marker: '__info__',\n    },\n    // Keyword values.\n    value: {\n        lex: true,\n        def: {\n            true: { val: true },\n            false: { val: false },\n            null: { val: null },\n        },\n    },\n    // Additional text ending characters\n    ender: [],\n    // Plugin custom options, (namespace by plugin name).\n    plugin: {},\n    // Debug settings\n    debug: {\n        // Default console for logging.\n        get_console: () => console,\n        // Max length of parse value to print.\n        maxlen: 99,\n        // Print internal structures\n        print: {\n            // Print config built from options.\n            config: false,\n            // Custom string formatter for src and node values.\n            src: undefined,\n        },\n    },\n    // Error messages.\n    error: {\n        unknown: 'unknown error: {code}',\n        unexpected: 'unexpected character(s): {src}',\n        invalid_unicode: 'invalid unicode escape: {src}',\n        invalid_ascii: 'invalid ascii escape: {src}',\n        unprintable: 'unprintable character: {src}',\n        unterminated_string: 'unterminated string: {src}',\n        unterminated_comment: 'unterminated comment: {src}',\n        unknown_rule: 'unknown rule: {rulename}',\n        end_of_source: 'unexpected end of source',\n    },\n    errmsg: {\n        name: 'jsonic',\n        suffix: true\n    },\n    // Error hints: {error-code: hint-text}.\n    hint: {\n        unknown: `\nSince the error is unknown, this is probably a bug inside jsonic\nitself, or a plugin. Please consider posting a github issue - thanks!\n\nCode: {code}, Details: \n{details}`,\n        unexpected: `\nThe character(s) {src} were not expected at this point as they do not\nmatch the expected syntax, even under the relaxed jsonic rules. If it\nis not obviously wrong, the actual syntax error may be elsewhere. Try\ncommenting out larger areas around this point until you get no errors,\nthen remove the comments in small sections until you find the\noffending syntax. NOTE: Also check if any plugins you are using\nexpect different syntax in this case.`,\n        invalid_unicode: `\nThe escape sequence {src} does not encode a valid unicode code point\nnumber. You may need to validate your string data manually using test\ncode to see how JavaScript will interpret it. Also consider that your\ndata may have become corrupted, or the escape sequence has not been\ngenerated correctly.`,\n        invalid_ascii: `\nThe escape sequence {src} does not encode a valid ASCII character. You\nmay need to validate your string data manually using test code to see\nhow JavaScript will interpret it. Also consider that your data may\nhave become corrupted, or the escape sequence has not been generated\ncorrectly.`,\n        unprintable: `\nString values cannot contain unprintable characters (character codes\nbelow 32). The character {src} is unprintable. You may need to remove\nthese characters from your source data. Also check that it has not\nbecome corrupted.`,\n        unterminated_string: `\nThis string has no end quote.`,\n        unterminated_comment: `\nThis comment is never closed.`,\n        unknown_rule: `\nNo rule named $rulename is defined. This is probably an error in the\ngrammar of a plugin.`,\n        end_of_source: `\nUnexpected end of source.`,\n    },\n    // Lexer\n    lex: {\n        match: {\n            match: { order: 1e6, make: lexer_1.makeMatchMatcher },\n            fixed: { order: 2e6, make: lexer_1.makeFixedMatcher },\n            space: { order: 3e6, make: lexer_1.makeSpaceMatcher },\n            line: { order: 4e6, make: lexer_1.makeLineMatcher },\n            string: { order: 5e6, make: lexer_1.makeStringMatcher },\n            comment: { order: 6e6, make: lexer_1.makeCommentMatcher },\n            number: { order: 7e6, make: lexer_1.makeNumberMatcher },\n            text: { order: 8e6, make: lexer_1.makeTextMatcher },\n        },\n        // Empty string is allowed and returns undefined\n        empty: true,\n        emptyResult: undefined,\n    },\n    // Parser\n    parse: {\n        // Plugin custom functions to prepare parser context.\n        prepare: {},\n    },\n    // Parser rule options.\n    rule: {\n        // Name of the starting rule.\n        start: 'val',\n        // Automatically close remaining structures at EOF.\n        finish: true,\n        // Multiplier to increase the maximum number of rule occurences.\n        maxmul: 3,\n        // Include only those alts with matching group tags (comma sep).\n        // NOTE: applies universally, thus also for subsequent rules.\n        include: '',\n        // Exclude alts with matching group tags (comma sep).\n        // NOTE: applies universally, thus also for subsequent rules.\n        exclude: '',\n    },\n    // Result value options.\n    result: {\n        // Fail if result matches any of these.\n        fail: [],\n    },\n    // Token-rewind options. `history` bounds how many consumed tokens\n    // are retained on ctx.v for ctx.rewind(). The default of 64 keeps\n    // parse-time memory bounded for large inputs; raise it if a\n    // grammar needs to rewind further, or set to Infinity to retain\n    // every consumed token. ctx.rewind(mark) throws if `mark` falls\n    // outside the retained window.\n    rewind: {\n        history: 64,\n    },\n    // Configuration options.\n    config: {\n        // Configuration modifiers.\n        modify: {},\n    },\n    // Provide a custom parser.\n    parser: {\n        start: undefined,\n    },\n};\nexports.defaults = defaults;\n//# sourceMappingURL=defaults.js.map"
  },
  {
    "path": "dist/error.d.ts",
    "content": "import type { Bag, Context, Rule, Token } from './types';\ndeclare class JsonicError extends SyntaxError {\n    constructor(code: string, details: Bag, token: Token, rule: Rule, ctx: Context);\n}\ndeclare function errinject<T extends string | string[] | {\n    [key: string]: string;\n}>(s: T, code: string, details: Bag, token: Token, rule: Rule, ctx: Context): T;\ndeclare function trimstk(err: Error): void;\ndeclare function errsite(spec: {\n    src: string;\n    sub?: string;\n    msg?: string;\n    row?: number;\n    col?: number;\n    pos?: number;\n    cline?: string;\n}): string;\ndeclare function errmsg(spec: {\n    code?: string;\n    name?: string;\n    txts?: {\n        msg?: string;\n        hint?: string;\n        site?: string;\n    };\n    smsg?: string;\n    src?: string;\n    file?: string;\n    row?: number;\n    col?: number;\n    pos?: number;\n    site?: string;\n    sub?: string;\n    prefix?: string | Function;\n    suffix?: string | Function;\n    color?: {\n        active?: boolean;\n        reset?: string;\n        hi?: string;\n        lo?: string;\n        line?: string;\n    };\n}): string;\ndeclare function errdesc(code: string, details: Bag, token: Token, rule: Rule, ctx: Context): Bag;\ndeclare function strinject<T extends string | string[] | {\n    [key: string]: string;\n}>(s: T, m: Bag, f?: {\n    indent?: string;\n}): T;\ndeclare function prop(obj: any, path: string, val?: any): any;\nexport { JsonicError, errdesc, errinject, errsite, errmsg, trimstk, strinject, prop, };\n"
  },
  {
    "path": "dist/error.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2024 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.JsonicError = void 0;\nexports.errdesc = errdesc;\nexports.errinject = errinject;\nexports.errsite = errsite;\nexports.errmsg = errmsg;\nexports.trimstk = trimstk;\nexports.strinject = strinject;\nexports.prop = prop;\nconst types_1 = require(\"./types\");\nconst utility_1 = require(\"./utility\");\nconst S = {\n    function: 'function',\n    object: 'object',\n    string: 'string',\n    unexpected: 'unexpected',\n    Object: 'Object',\n    Array: 'Array',\n    gap: '  ',\n    no_re_flags: types_1.EMPTY,\n};\n// Jsonic errors with nice formatting.\nclass JsonicError extends SyntaxError {\n    constructor(code, details, token, rule, ctx) {\n        details = (0, utility_1.deep)({}, details);\n        let desc = errdesc(code, details, token, rule, ctx);\n        super(desc.message);\n        (0, utility_1.assign)(this, desc);\n    }\n}\nexports.JsonicError = JsonicError;\n// Inject value text into an error message. The value is taken from\n// the `details` parameter to JsonicError. If not defined, the value is\n// determined heuristically from the Token and Context.\nfunction errinject(s, code, details, token, rule, ctx) {\n    let ref = {\n        ...(ctx || {}),\n        ...(ctx.cfg || {}),\n        ...(ctx.opts || {}),\n        ...(token || {}),\n        ...(rule || {}),\n        ...(ctx.meta || {}),\n        ...(details || {}),\n        ...{ code, details, token, rule, ctx },\n    };\n    return strinject(s, ref, { indent: '  ' });\n}\n// Remove Jsonic internal lines as spurious for caller.\nfunction trimstk(err) {\n    if (err.stack) {\n        err.stack = err.stack\n            .split('\\n')\n            .filter((s) => !s.includes('jsonic/jsonic'))\n            .map((s) => s.replace(/    at /, 'at '))\n            .join('\\n');\n    }\n}\n// Extract error site in source text and mark error point. */\nfunction errsite(spec) {\n    let { src, sub, msg, cline, row, col, pos } = spec;\n    row = null != row && 0 < row ? row : 1;\n    col = null != col && 0 < col ? col : 1;\n    pos =\n        null != pos && 0 < pos\n            ? pos\n            : null == src\n                ? 0\n                : src\n                    .split('\\n')\n                    .reduce((pos, line, i) => ((pos +=\n                    i < row - 1 ? line.length + 1 : i === row - 1 ? col : 0),\n                    pos), 0);\n    let tsrc = null == sub ? types_1.EMPTY : sub;\n    let behind = src.substring(Math.max(0, pos - 333), pos).split('\\n');\n    let ahead = src.substring(pos, pos + 333).split('\\n');\n    let pad = 2 + (types_1.EMPTY + (row + 2)).length;\n    let rc = row < 3 ? 1 : row - 2;\n    let ln = (s) => (null == cline ? '' : cline) +\n        (types_1.EMPTY + rc++).padStart(pad, ' ') +\n        ' | ' +\n        (null == cline ? '' : '\\x1b[0m') +\n        (null == s ? types_1.EMPTY : s);\n    let blen = behind.length;\n    let lines = [\n        2 < blen ? ln(behind[blen - 3]) : null,\n        1 < blen ? ln(behind[blen - 2]) : null,\n        ln(behind[blen - 1] + ahead[0]),\n        ' '.repeat(pad) +\n            '   ' +\n            ' '.repeat(col - 1) +\n            (null == cline ? '' : cline) +\n            '^'.repeat(tsrc.length || 1) +\n            ' ' +\n            msg +\n            (null == cline ? '' : '\\x1b[0m'),\n        ln(ahead[1]),\n        ln(ahead[2]),\n    ]\n        .filter((line) => null != line)\n        .join('\\n');\n    return lines;\n}\nfunction errmsg(spec) {\n    const color = {\n        active: false,\n        reset: '',\n        hi: '',\n        lo: '',\n        line: '',\n    };\n    if (spec.color && spec.color.active) {\n        Object.assign(color, spec.color);\n    }\n    const txts = {\n        msg: null,\n        hint: null,\n        site: null,\n        ...(spec.txts || {})\n    };\n    let message = [\n        null == spec.prefix\n            ? null\n            : 'function' === typeof spec.prefix\n                ? spec.prefix(color, spec)\n                : '' + spec.prefix,\n        (null == spec.code\n            ? ''\n            : color.hi +\n                '[' +\n                (null == spec.name ? '' : spec.name + '/') +\n                spec.code +\n                ']:') +\n            color.reset +\n            ' ' +\n            // (null == spec.msg ? '' : spec.msg),\n            (null == txts.msg ? '' : txts.msg),\n        (null != spec.row && null != spec.col) || null != spec.file\n            ? '  ' +\n                color.line +\n                '-->' +\n                color.reset +\n                ' ' +\n                (null == spec.file ? '<no-file>' : spec.file) +\n                (null == spec.row || null == spec.col\n                    ? ''\n                    : ':' + spec.row + ':' + spec.col)\n            : null,\n        null == spec.src\n            ? ''\n            : (null == txts.site ? '' : errsite({\n                src: spec.src,\n                sub: spec.sub,\n                msg: spec.smsg || spec.txts?.msg,\n                cline: color.line,\n                row: spec.row,\n                col: spec.col,\n                pos: spec.pos,\n            })),\n        '',\n        // null == spec.hint ? null : spec.hint,\n        // txts.hint,\n        (null == txts.hint ? '' : txts.hint),\n        null == spec.suffix\n            ? null\n            : 'function' === typeof spec.suffix\n                ? spec.suffix(color, spec)\n                : '' + spec.suffix,\n    ]\n        .filter((n) => null != n)\n        .join('\\n');\n    return message;\n}\nfunction errdesc(code, details, token, rule, ctx) {\n    try {\n        const src = ctx.src();\n        const cfg = ctx.cfg;\n        const meta = ctx.meta;\n        const txts = errinject({\n            msg: cfg.error[code] ||\n                (details?.use?.err &&\n                    (details.use.err.code || details.use.err.message)) ||\n                cfg.error.unknown,\n            hint: (cfg.hint[code] ||\n                details.use?.err?.message ||\n                cfg.hint.unknown ||\n                '')\n                .trim()\n                .split('\\n')\n                .map((s) => '  ' + s)\n                .join('\\n'),\n            site: '',\n        }, code, details, token, rule, ctx);\n        txts.site = errsite({\n            src,\n            msg: txts.msg,\n            cline: cfg.color.active ? cfg.color.line : '',\n            row: token.rI,\n            col: token.cI,\n            pos: token.sI,\n            sub: token.src,\n        });\n        const suffix = true === cfg.errmsg.suffix ? (color) => [\n            '',\n            '  ' + color.lo + 'https://jsonic.senecajs.org' + color.reset + '',\n            '  ' +\n                color.lo +\n                '--internal: tag=' +\n                (ctx.opts.tag || '') +\n                '; rule=' +\n                rule.name +\n                '~' +\n                rule.state +\n                '; token=' +\n                (0, utility_1.tokenize)(token.tin, ctx.cfg) +\n                (null == token.why ? '' : '~' + token.why) +\n                '; plugins=' +\n                ctx\n                    .plgn()\n                    .map((p) => p.name)\n                    .join(',') +\n                '--' +\n                color.reset,\n        ].join('\\n') :\n            ('string' === typeof cfg.errmsg.suffix || 'function' === typeof cfg.errmsg.suffix) ?\n                cfg.errmsg.suffix :\n                undefined;\n        let message = errmsg({\n            code,\n            // name: 'jsonic',\n            name: cfg.errmsg.name,\n            txts,\n            src,\n            file: meta ? meta.fileName : undefined,\n            row: token.rI,\n            col: token.cI,\n            pos: token.sI,\n            sub: token.src,\n            color: cfg.color,\n            suffix,\n        });\n        let desc = {\n            internal: {\n                token,\n                ctx,\n            },\n        };\n        desc = {\n            ...Object.create(desc),\n            message,\n            code,\n            details,\n            meta,\n            fileName: meta ? meta.fileName : undefined,\n            lineNumber: token.rI,\n            columnNumber: token.cI,\n            txts: () => txts\n        };\n        return desc;\n    }\n    catch (e) {\n        // TODO: fix\n        console.log(e);\n        return {};\n    }\n}\n// Inject value into text by key using \"{key}\" syntax.\nfunction strinject(s, m, f) {\n    let st = typeof s;\n    let t = Array.isArray(s)\n        ? 'array'\n        : null == s\n            ? 'string'\n            : 'object' === st\n                ? st\n                : 'string';\n    let so = 'object' === t\n        ? s\n        : 'array' === t\n            ? s.reduce((a, n, i) => ((a[i] = n), a), {})\n            : { _: s };\n    let mo = null == m ? {} : m;\n    Object.entries(so).map((n) => (so[n[0]] =\n        null == n[1]\n            ? ''\n            : ('' + n[1]).replace(/\\{([\\w_0-9.]+)}/g, (match, keypath) => {\n                let inject = prop(mo, keypath);\n                inject = undefined === inject ? match : inject;\n                if ('object' === typeof inject) {\n                    let cn = inject?.constructor?.name;\n                    if ('Object' === cn || 'Array' === cn) {\n                        inject = JSON.stringify(inject).replace(/([^\"])\"/g, '$1');\n                    }\n                    else {\n                        inject = inject.toString();\n                    }\n                }\n                else {\n                    inject = '' + inject;\n                }\n                if (f) {\n                    if ('string' === typeof f.indent) {\n                        inject = inject.replace(/\\n/g, '\\n' + f.indent);\n                    }\n                }\n                return inject;\n            })));\n    return ('string' === t ? so._ : 'array' === t ? Object.values(so) : so);\n}\nfunction prop(obj, path, val) {\n    let root = obj;\n    try {\n        let parts = path.split('.');\n        let pn;\n        for (let pI = 0; pI < parts.length; pI++) {\n            pn = parts[pI];\n            if ('__proto__' === pn) {\n                throw new Error(pn);\n            }\n            if (pI < parts.length - 1) {\n                obj = obj[pn] = obj[pn] || {};\n            }\n        }\n        if (undefined !== val) {\n            if ('__proto__' === pn) {\n                throw new Error(pn);\n            }\n            obj[pn] = val;\n        }\n        return obj[pn];\n    }\n    catch (e) {\n        throw new Error('Cannot ' +\n            (undefined === val ? 'get' : 'set') +\n            ' path ' +\n            path +\n            ' on object: ' +\n            str(root) +\n            (undefined === val ? '' : ' to value: ' + str(val, 22)));\n    }\n}\nfunction str(o, len = 44) {\n    let s;\n    try {\n        s = 'object' === typeof o ? JSON.stringify(o) : '' + o;\n    }\n    catch (e) {\n        s = '' + o;\n    }\n    return snip(len < s.length ? s.substring(0, len - 3) + '...' : s, len);\n}\nfunction snip(s, len = 5) {\n    return undefined === s\n        ? ''\n        : ('' + s).substring(0, len).replace(/[\\r\\n\\t]/g, '.');\n}\n//# sourceMappingURL=error.js.map"
  },
  {
    "path": "dist/grammar.d.ts",
    "content": "import { Jsonic } from './jsonic';\ndeclare function grammar(jsonic: Jsonic): void;\ndeclare function makeJSON(jsonic: any): any;\nexport { grammar, makeJSON };\n"
  },
  {
    "path": "dist/grammar.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2024 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.grammar = grammar;\nexports.makeJSON = makeJSON;\nconst defprop = Object.defineProperty;\nfunction mark(node, marker, data) {\n    if (node != null && typeof node === 'object') {\n        defprop(node, marker, { value: data, writable: true });\n    }\n}\nfunction grammar(jsonic) {\n    const { deep } = jsonic.util;\n    const { \n    // Fixed tokens\n    // OB, // Open Brace `{`\n    // CB, // Close Brace `}`\n    // OS, // Open Square `[`\n    // CS, // Close Square `]`\n    // CL, // Colon `:`\n    CA, // Comma `,`\n    // Complex tokens\n    TX, // Text (unquoted character sequence)\n    ST, // String (quoted character sequence)\n    // Control tokens\n    ZZ, // End-of-source\n     } = jsonic.token;\n    const { VAL, // All tokens that make up values\n    // KEY, // All tokens that make up keys\n     } = jsonic.tokenSet;\n    const fnm = {\n        '@finish': (_rule, ctx) => {\n            if (!ctx.cfg.rule.finish) {\n                // TODO: pass missing end char for replacement in error message\n                ctx.t0.err = 'end_of_source';\n                return ctx.t0;\n            }\n        },\n        // TODO: define a way to \"export\" rule actions or other functions so that\n        // other plugins can use them.\n        '@pairkey': (r) => {\n            // Get key string value from first matching token of `Open` state.\n            const key_token = r.o0;\n            const key = ST === key_token.tin || TX === key_token.tin\n                ? key_token.val // Was text\n                : key_token.src; // Was number, use original text\n            r.u.key = key;\n        },\n    };\n    // Plain JSON\n    // ----------\n    jsonic.grammar({\n        ref: {\n            '@finish': (_rule, ctx) => {\n                if (!ctx.cfg.rule.finish) {\n                    // TODO: pass missing end char for replacement in error message\n                    ctx.t0.err = 'end_of_source';\n                    return ctx.t0;\n                }\n            },\n            // TODO: define a way to \"export\" rule actions or other functions so that\n            // other plugins can use them.\n            '@pairkey': (r) => {\n                // Get key string value from first matching token of `Open` state.\n                const key_token = r.o0;\n                const key = ST === key_token.tin || TX === key_token.tin\n                    ? key_token.val // Was text\n                    : key_token.src; // Was number, use original text\n                r.u.key = key;\n            },\n            '@val-bo': (rule) => (rule.node = undefined),\n            '@val-bc': (r, ctx) => {\n                // NOTE: val can be undefined when there is no value at all\n                // (eg. empty string, thus no matched opening token)\n                r.node =\n                    // If there's no node,\n                    undefined === r.node\n                        ? // ... or no child node (child map or list),\n                            undefined === r.child.node\n                                ? // ... or no matched tokens,\n                                    0 === r.os\n                                        ? // ... then the node has no value\n                                            undefined\n                                        : // .. otherwise use the token value\n                                            (() => {\n                                                let val = r.o0.resolveVal(r, ctx);\n                                                if (ctx.cfg.info.text &&\n                                                    typeof val === 'string' &&\n                                                    (r.o0.tin === ctx.cfg.t.ST || r.o0.tin === ctx.cfg.t.TX)) {\n                                                    let quote = r.o0.tin === ctx.cfg.t.ST && r.o0.src.length > 0\n                                                        ? r.o0.src[0] : '';\n                                                    let sv = new String(val);\n                                                    mark(sv, ctx.cfg.info.marker, { quote });\n                                                    val = sv;\n                                                }\n                                                return val;\n                                            })()\n                                : r.child.node\n                        : r.node;\n            },\n            '@map-bo': (r, ctx) => {\n                // Create a new empty map.\n                r.node = Object.create(null);\n                if (ctx.cfg.info.map) {\n                    mark(r.node, ctx.cfg.info.marker, { implicit: false, meta: {} });\n                }\n            },\n            '@list-bo': (r, ctx) => {\n                // Create a new empty list.\n                r.node = [];\n                if (ctx.cfg.info.list) {\n                    mark(r.node, ctx.cfg.info.marker, { implicit: false, meta: {} });\n                }\n            },\n            '@pair-bc': (r, ctx) => {\n                if (r.u.pair) {\n                    // Drop keys that match the info marker to preserve metadata.\n                    if (ctx.cfg.info.map && r.u.key === ctx.cfg.info.marker) {\n                        return;\n                    }\n                    // Store previous value (if any, for extensions).\n                    r.u.prev = r.node[r.u.key];\n                    r.node[r.u.key] = r.child.node;\n                }\n            },\n            '@elem-bc': (r) => {\n                if (true !== r.u.done && undefined !== r.child.node) {\n                    r.node.push(r.child.node);\n                }\n            },\n        },\n        rule: {\n            val: {\n                // Opening token alternates.\n                open: [\n                    // A map: `{ ...`\n                    { s: '#OB', p: 'map', b: 1, g: 'map,json' },\n                    // A list: `[ ...`\n                    { s: '#OS', p: 'list', b: 1, g: 'list,json' },\n                    // A plain value: `x` `\"x\"` `1` `true` ....\n                    { s: '#VAL', g: 'val,json' },\n                ],\n                // Closing token alternates.\n                close: [\n                    // End of input.\n                    { s: '#ZZ', g: 'end,json' },\n                    // There's more JSON.\n                    { b: 1, g: 'more,json' },\n                ]\n            },\n            map: {\n                open: [\n                    // An empty map: {}.\n                    { s: '#OB #CB', b: 1, n: { pk: 0 }, g: 'map,json' },\n                    // Start matching map key-value pairs: a:1.\n                    // Reset counter n.pk as new map (for extensions).\n                    { s: '#OB', p: 'pair', n: { pk: 0 }, g: 'map,json,pair' },\n                ],\n                close: [\n                    // End of map.\n                    { s: '#CB', g: 'end,json' },\n                ],\n            },\n            list: {\n                open: [\n                    // An empty list: [].\n                    { s: '#OS #CS', b: 1, g: 'list,json' },\n                    // Start matching list elements: 1,2.\n                    { s: '#OS', p: 'elem', g: 'list,elem,json' },\n                ],\n                close: [\n                    // End of map.\n                    { s: '#CS', g: 'end,json' },\n                ]\n            },\n            // sets key:val on node\n            pair: {\n                open: [\n                    // Match key-colon start of pair. Marker `pair=true` allows flexibility.\n                    {\n                        s: '#KEY #CL',\n                        p: 'val',\n                        u: { pair: true },\n                        a: '@pairkey',\n                        g: 'map,pair,key,json',\n                    },\n                ],\n                close: [\n                    // Comma means a new pair at same pair-key level.\n                    { s: '#CA', r: 'pair', g: 'map,pair,json' },\n                    // End of map.\n                    { s: '#CB', b: 1, g: 'map,pair,json' },\n                ]\n            },\n            // push onto node\n            elem: {\n                open: [\n                    // List elements are values.\n                    { p: 'val', g: 'list,elem,val,json' },\n                ],\n                close: [\n                    // Next element.\n                    { s: '#CA', r: 'elem', g: 'list,elem,json' },\n                    // End of list.\n                    { s: '#CS', b: 1, g: 'list,elem,json' },\n                ],\n            },\n        },\n    });\n    /*\n    jsonic.rule('val', (rs: RuleSpec) => {\n    rs\n     \n    .fnref({\n      '@val-bo': (rule: Rule) => (rule.node = undefined),\n      '@val-bc': (r: Rule, ctx: Context) => {\n        // NOTE: val can be undefined when there is no value at all\n        // (eg. empty string, thus no matched opening token)\n        r.node =\n          // If there's no node,\n          undefined === r.node\n            ? // ... or no child node (child map or list),\n            undefined === r.child.node\n              ? // ... or no matched tokens,\n              0 === r.os\n                ? // ... then the node has no value\n                undefined\n                : // .. otherwise use the token value\n                r.o0.resolveVal(r, ctx)\n              : r.child.node\n            : r.node\n      }\n    })\n     \n    // Clear the current node as this a new value.\n    // .bo((rule: Rule) => (rule.node = undefined))\n    // .bo('@val-bo')\n     \n    // Opening token alternates.\n    .open([\n    // A map: `{ ...`\n    { s: '#OB', p: 'map', b: 1, g: 'map,json' },\n     \n    // A list: `[ ...`\n    { s: '#OS', p: 'list', b: 1, g: 'list,json' },\n     \n    // A plain value: `x` `\"x\"` `1` `true` ....\n    { s: '#VAL', g: 'val,json' },\n    ])\n     \n    // Closing token alternates.\n    .close([\n    // End of input.\n    { s: '#ZZ', g: 'end,json' },\n     \n    // There's more JSON.\n    { b: 1, g: 'more,json' },\n    ])\n     \n    // .bc('@val-bc')\n     \n    })\n     \n     \n     \n    jsonic.rule('map', (rs: RuleSpec) => {\n      rs\n        .fnref({\n          '@map-bo': (r: Rule) => {\n            // Create a new empty map.\n            r.node = Object.create(null)\n          }\n        })\n        // .bo('@bo')\n        .open([\n          // An empty map: {}.\n          { s: '#OB #CB', b: 1, n: { pk: 0 }, g: 'map,json' },\n     \n          // Start matching map key-value pairs: a:1.\n          // Reset counter n.pk as new map (for extensions).\n          { s: '#OB', p: 'pair', n: { pk: 0 }, g: 'map,json,pair' },\n        ])\n        .close([\n          // End of map.\n          { s: '#CB', g: 'end,json' },\n        ])\n    })\n     \n    jsonic.rule('list', (rs: RuleSpec) => {\n      rs\n        .fnref({\n          '@list-bo': (r: Rule) => {\n            // Create a new empty list.\n            r.node = []\n          }\n        })\n        // .bo('@bo')\n        .open([\n          // An empty list: [].\n          { s: '#OS #CS', b: 1, g: 'list,json' },\n    \n          // Start matching list elements: 1,2.\n          { s: '#OS', p: 'elem', g: 'list,elem,json' },\n        ])\n        .close([\n          // End of map.\n          { s: '#CS', g: 'end,json' },\n        ])\n    })\n  \n  \n  \n  \n    // sets key:val on node\n    jsonic.rule('pair', (rs: RuleSpec) => {\n      rs\n        .fnref({\n          ...fnm,\n          '@pair-bc': (r: Rule, _ctx: Context) => {\n            if (r.u.pair) {\n              // Store previous value (if any, for extensions).\n              r.u.prev = r.node[r.u.key]\n              r.node[r.u.key] = r.child.node\n            }\n          }\n        })\n  \n        .open([\n          // Match key-colon start of pair. Marker `pair=true` allows flexibility.\n          {\n            s: '#KEY #CL',\n            p: 'val',\n            u: { pair: true },\n            a: '@pairkey',\n            g: 'map,pair,key,json',\n          },\n        ])\n        // .bc('@bc')\n        .close([\n          // Comma means a new pair at same pair-key level.\n          { s: '#CA', r: 'pair', g: 'map,pair,json' },\n  \n          // End of map.\n          { s: '#CB', b: 1, g: 'map,pair,json' },\n        ])\n    })\n  \n    // push onto node\n    jsonic.rule('elem', (rs: RuleSpec) => {\n      rs\n        .fnref({\n          ...fnm,\n          '@elem-bc': (r: Rule) => {\n            if (true !== r.u.done && undefined !== r.child.node) {\n              r.node.push(r.child.node)\n            }\n          }\n        })\n        .open([\n          // List elements are values.\n          { p: 'val', g: 'list,elem,val,json' },\n        ])\n        // .bc('@bc')\n        .close([\n          // Next element.\n          { s: '#CA', r: 'elem', g: 'list,elem,json' },\n  \n          // End of list.\n          { s: '#CS', b: 1, g: 'list,elem,json' },\n        ])\n    })\n  \n    */\n    // Jsonic syntax extensions.\n    // NOTE: undefined values are still removed, as JSON does not have \"undefined\", only null.\n    // Counters.\n    // * pk: depth of the pair-key path\n    // * dmap: depth of maps\n    function pairval(r, ctx) {\n        let key = r.u.key;\n        let val = r.child.node;\n        const prev = r.u.prev;\n        // Convert undefined to null when there was no pair value\n        val = undefined === val ? null : val;\n        // Do not set unsafe keys on Arrays (Objects are created without a prototype)\n        if (r.u.list && ctx.cfg.safe.key) {\n            if ('__proto__' === key || 'constructor' === key) {\n                return;\n            }\n        }\n        // Drop keys that match the info marker to preserve metadata.\n        if (ctx.cfg.info.map && key === ctx.cfg.info.marker) {\n            return;\n        }\n        val = null == prev\n            ? val\n            : ctx.cfg.map.merge\n                ? ctx.cfg.map.merge(prev, val, r, ctx)\n                : ctx.cfg.map.extend\n                    ? deep(prev, val)\n                    : val;\n        r.node[key] = val;\n    }\n    jsonic.grammar({\n        ref: {\n            '@val-close-error': (r, c) => (0 === r.d ? c.t0 : undefined),\n        },\n        rule: {\n            val: {\n                open: {\n                    alts: [\n                        // A pair key: `a: ...`\n                        // Implicit map at top level.\n                        {\n                            s: '#KEY #CL',\n                            c: { d: 0 },\n                            p: 'map',\n                            b: 2,\n                            g: 'pair,jsonic,top',\n                        },\n                        // A pair dive: `a:b: ...`\n                        // Increment counter n.pk to indicate pair-key depth (for extensions).\n                        // a:9 -> pk=undef, a:b:9 -> pk=1, a:b:c:9 -> pk=2, etc\n                        {\n                            s: '#KEY #CL',\n                            p: 'map',\n                            b: 2,\n                            n: { pk: 1 },\n                            g: 'pair,jsonic',\n                        },\n                        // A plain value: `x` `\"x\"` `1` `true` ....\n                        { s: '#VAL', g: 'val,json' },\n                        // Implicit ends `{a:}` -> {\"a\":null}, `[a:]` -> [{\"a\":null}]\n                        {\n                            s: ['#CB #CS'],\n                            b: 1,\n                            c: { d: { $gt: 0 } },\n                            g: 'val,imp,null,jsonic',\n                        },\n                        // Implicit list at top level: a,b.\n                        {\n                            s: '#CA',\n                            c: { d: 0 },\n                            p: 'list',\n                            b: 1,\n                            g: 'list,imp,jsonic',\n                        },\n                        // Value is implicitly null when empty before commas.\n                        { s: '#CA', b: 1, g: 'list,val,imp,null,jsonic' },\n                        { s: '#ZZ', g: 'jsonic' },\n                    ],\n                    inject: { append: true, delete: [2] },\n                },\n                close: {\n                    alts: [\n                        // Explicitly close map or list: `}`, `]`\n                        {\n                            s: ['#CB #CS'],\n                            b: 1,\n                            g: 'val,json,close',\n                            e: '@val-close-error', // (r, c) => (0 === r.d ? c.t0 : undefined),\n                        },\n                        // Implicit list (comma sep) only allowed at top level: `1,2`.\n                        {\n                            s: '#CA',\n                            c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n                            r: 'list',\n                            u: { implist: true },\n                            g: 'list,val,imp,comma,jsonic',\n                        },\n                        // Implicit list (space sep) only allowed at top level: `1 2`.\n                        {\n                            c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n                            r: 'list',\n                            u: { implist: true },\n                            g: 'list,val,imp,space,jsonic',\n                            b: 1,\n                        },\n                        { s: '#ZZ', g: 'jsonic' },\n                    ],\n                    inject: {\n                        append: true,\n                        // Move \"There's more JSON\" to end.\n                        move: [1, -1],\n                    }\n                }\n            }\n        }\n    });\n    /*\n      jsonic.rule('val', (rs: RuleSpec) => {\n        rs\n          .open(\n            [\n              // A pair key: `a: ...`\n              // Implicit map at top level.\n              {\n                s: '#KEY #CL',\n                c: { d: 0 },\n                p: 'map',\n                b: 2,\n                g: 'pair,jsonic,top',\n              },\n     \n              // A pair dive: `a:b: ...`\n              // Increment counter n.pk to indicate pair-key depth (for extensions).\n              // a:9 -> pk=undef, a:b:9 -> pk=1, a:b:c:9 -> pk=2, etc\n              {\n                s: '#KEY #CL',\n                p: 'map',\n                b: 2,\n                n: { pk: 1 },\n                g: 'pair,jsonic',\n              },\n     \n              // A plain value: `x` `\"x\"` `1` `true` ....\n              { s: [VAL], g: 'val,json' },\n     \n              // Implicit ends `{a:}` -> {\"a\":null}, `[a:]` -> [{\"a\":null}]\n              {\n                s: ['#CB #CS'],\n                b: 1,\n                c: { d: { $gt: 0 } },\n                g: 'val,imp,null,jsonic',\n              },\n     \n              // Implicit list at top level: a,b.\n              {\n                s: '#CA',\n                c: { d: 0 },\n                p: 'list',\n                b: 1,\n                g: 'list,imp,jsonic',\n              },\n     \n              // Value is implicitly null when empty before commas.\n              { s: '#CA', b: 1, g: 'list,val,imp,null,jsonic' },\n     \n              { s: '#ZZ', g: 'jsonic' },\n            ],\n            { append: true, delete: [2] },\n          )\n          .close(\n            [\n              // Explicitly close map or list: `}`, `]`\n              {\n                s: ['#CB #CS'],\n                b: 1,\n                g: 'val,json,close',\n                e: (r, c) => (0 === r.d ? c.t0 : undefined),\n              },\n     \n              // Implicit list (comma sep) only allowed at top level: `1,2`.\n              {\n                s: '#CA',\n                c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n                r: 'list',\n                u: { implist: true },\n                g: 'list,val,imp,comma,jsonic',\n              },\n     \n              // Implicit list (space sep) only allowed at top level: `1 2`.\n              {\n                c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n                r: 'list',\n                u: { implist: true },\n                g: 'list,val,imp,space,jsonic',\n                b: 1,\n              },\n     \n              { s: '#ZZ', g: 'jsonic' },\n            ],\n            {\n              append: true,\n     \n              // Move \"There's more JSON\" to end.\n              move: [1, -1],\n            },\n          )\n      })\n    */\n    jsonic.rule('map', (rs) => {\n        rs\n            .fnref({\n            ...fnm\n        })\n            .bo((r) => {\n            // Increment depth of maps.\n            r.n.dmap = 1 + (r.n.dmap ? r.n.dmap : 0);\n        })\n            .open([\n            // Auto-close; fail if rule.finish option is false.\n            { s: '#OB #ZZ', b: 1, e: '@finish', g: 'end,jsonic' },\n        ])\n            .open([\n            // Pair from implicit map.\n            { s: '#KEY #CL', p: 'pair', b: 2, g: 'pair,list,val,imp,jsonic' },\n        ], { append: true })\n            .close([\n            // Normal end of map, no path dive.\n            {\n                s: '#CB',\n                c: { 'n.pk': { $lte: 0 } },\n                g: 'end,json',\n            },\n            // Not yet at end of path dive, keep ascending.\n            { s: '#CB', b: 1, g: 'path,jsonic' },\n            // End of implicit path\n            { s: ['#CA #CS #VAL'], b: 1, g: 'end,path,jsonic' },\n            // Auto-close; fail if rule.finish option is false.\n            { s: '#ZZ', e: '@finish', g: 'end,jsonic' },\n        ], { append: true, delete: [0] })\n            .bc((r, ctx) => {\n            let m = ctx.cfg.info.marker;\n            if (ctx.cfg.info.map && r.node?.[m]) {\n                r.node[m].implicit = !(r.o0 && r.o0.tin === ctx.cfg.t.OB);\n            }\n        });\n    });\n    jsonic.rule('list', (rs) => {\n        rs\n            .fnref({\n            ...fnm,\n            '@list-bo': (r) => {\n                // Increment depth of lists.\n                r.n.dlist = 1 + (r.n.dlist ? r.n.dlist : 0);\n                if (r.prev.u.implist) {\n                    r.node.push(r.prev.node);\n                    r.prev.node = r.node;\n                }\n            }\n        })\n            // .bo('@bo')\n            .open({\n            c: { 'prev.u.implist': { $eq: true } },\n            p: 'elem',\n        })\n            .open([\n            // Initial comma [, will insert null as [null,\n            { s: '#CA', p: 'elem', b: 1, g: 'list,elem,val,imp,jsonic' },\n            // Another element.\n            { p: 'elem', g: 'list,elem,jsonic' },\n        ], { append: true })\n            .close([\n            // Fail if rule.finish option is false.\n            { s: '#ZZ', e: '@finish', g: 'end,jsonic' },\n        ], { append: true })\n            .bc((r, ctx) => {\n            let m = ctx.cfg.info.marker;\n            if (ctx.cfg.info.list && r.node?.[m]) {\n                r.node[m].implicit = !(r.o0 && r.o0.tin === ctx.cfg.t.OS);\n            }\n        });\n    });\n    // sets key:val on node\n    jsonic.rule('pair', (rs, p) => {\n        rs\n            .fnref({\n            ...fnm,\n            '@pair-bc': (r, ctx) => {\n                if (r.u.pair) {\n                    pairval(r, ctx);\n                }\n                if (true === r.u.child) {\n                    let val = r.child.node;\n                    val = undefined === val ? null : val;\n                    let prev = r.node['child$'];\n                    if (undefined === prev) {\n                        r.node['child$'] = val;\n                    }\n                    else {\n                        r.node['child$'] =\n                            ctx.cfg.map.merge\n                                ? ctx.cfg.map.merge(prev, val, r, ctx)\n                                : ctx.cfg.map.extend\n                                    ? deep(prev, val)\n                                    : val;\n                    }\n                }\n            }\n        })\n            .open([\n            // Ignore initial comma: {,a:1.\n            { s: '#CA', g: 'map,pair,comma,jsonic' },\n            // map.child: bare colon `:value` stores value on child$ property.\n            p.cfg.map.child && {\n                s: '#CL',\n                p: 'val',\n                u: { done: true, child: true },\n                g: 'map,pair,child,jsonic',\n            },\n        ], { append: true })\n            // NOTE: JSON pair.bc runs first, then this bc may override value.\n            // .bc('@bc')\n            .close([\n            // End of map, reset implicit depth counter so that\n            // a:b:c:1,d:2 -> {a:{b:{c:1}},d:2}\n            {\n                s: '#CB',\n                c: { 'n.pk': { $lte: 0 } },\n                b: 1,\n                g: 'map,pair,json',\n            },\n            // Ignore trailing comma at end of map.\n            {\n                s: '#CA #CB',\n                c: { 'n.pk': { $lte: 0 } },\n                b: 1,\n                g: 'map,pair,comma,jsonic',\n            },\n            { s: [CA, ZZ], g: 'end,jsonic' },\n            // Comma means a new pair at same pair-key level.\n            {\n                s: '#CA',\n                c: { 'n.pk': { $lte: 0 } },\n                r: 'pair',\n                g: 'map,pair,json',\n            },\n            // TODO: try CA VAL ? works anywhere?\n            // Comma means a new pair if implicit top level map.\n            {\n                s: '#CA',\n                c: { 'n.dmap': { $lte: 1 } },\n                r: 'pair',\n                g: 'map,pair,jsonic',\n            },\n            // TODO: try VAL CL ? works anywhere?\n            // Value means a new pair if implicit top level map.\n            {\n                s: '#KEY',\n                c: { 'n.dmap': { $lte: 1 } },\n                r: 'pair',\n                b: 1,\n                g: 'map,pair,imp,jsonic',\n            },\n            // End of implicit path (eg. a:b:1), keep closing until pk=0.\n            {\n                s: ['#CB #CA #CS #KEY'],\n                c: { 'n.pk': { $gt: 0 } },\n                b: 1,\n                g: 'map,pair,imp,path,jsonic',\n            },\n            // Can't close a map with `]`\n            { s: '#CS', e: (r) => r.c0, g: 'end,jsonic' },\n            // Fail if auto-close option is false.\n            { s: '#ZZ', e: '@finish', g: 'map,pair,json' },\n            // Who needs commas anyway?\n            {\n                r: 'pair',\n                b: 1,\n                g: 'map,pair,imp,jsonic',\n            },\n        ], { append: true, delete: [0, 1] });\n    });\n    // push onto node\n    jsonic.rule('elem', (rs, p) => {\n        rs\n            .fnref({\n            ...fnm,\n            '@elem-bc': (r, ctx) => {\n                if (true === r.u.pair) {\n                    if (ctx.cfg.list.pair) {\n                        // list.pair: push pair as object element into the list\n                        let key = r.u.key;\n                        let val = r.child.node;\n                        val = undefined === val ? null : val;\n                        let pairObj = Object.create(null);\n                        pairObj[key] = val;\n                        r.node.push(pairObj);\n                    }\n                    else {\n                        r.u.prev = r.node[r.u.key];\n                        pairval(r, ctx);\n                    }\n                }\n                if (true === r.u.child) {\n                    let val = r.child.node;\n                    val = undefined === val ? null : val;\n                    let prev = r.node['child$'];\n                    if (undefined === prev) {\n                        r.node['child$'] = val;\n                    }\n                    else {\n                        r.node['child$'] =\n                            ctx.cfg.map.merge\n                                ? ctx.cfg.map.merge(prev, val, r, ctx)\n                                : ctx.cfg.map.extend\n                                    ? deep(prev, val)\n                                    : val;\n                    }\n                }\n            }\n        })\n            .open([\n            // Empty commas insert null elements.\n            // Note that close consumes a comma, so b:2 works.\n            {\n                s: '#CA #CA',\n                b: 2,\n                u: { done: true },\n                a: (r) => r.node.push(null),\n                g: 'list,elem,imp,null,jsonic',\n            },\n            {\n                s: '#CA',\n                u: { done: true },\n                a: (r) => r.node.push(null),\n                g: 'list,elem,imp,null,jsonic',\n            },\n            {\n                s: '#KEY #CL',\n                e: (p.cfg.list.property || p.cfg.list.pair) ? undefined :\n                    (_r, ctx) => ctx.t0,\n                p: 'val',\n                n: { pk: 1, dmap: 1 },\n                u: { done: true, pair: true, list: true },\n                a: '@pairkey',\n                g: 'elem,pair,jsonic',\n            },\n            // list.child: bare colon `:value` stores value on child$ property.\n            p.cfg.list.child && {\n                s: '#CL',\n                p: 'val',\n                u: { done: true, child: true, list: true },\n                g: 'elem,child,jsonic',\n            },\n        ])\n            // .bc('@bc')\n            .close([\n            // Ignore trailing comma.\n            { s: ['#CA', '#CS #ZZ'], b: 1, g: 'list,elem,comma,jsonic' },\n            // Next element.\n            { s: '#CA', r: 'elem', g: 'list,elem,json' },\n            // End of list.\n            { s: '#CS', b: 1, g: 'list,elem,json' },\n            // Fail if auto-close option is false.\n            { s: '#ZZ', e: '@finish', g: 'list,elem,json' },\n            // Can't close a list with `}`\n            { s: '#CB', e: (r) => r.c0, g: 'end,jsonic' },\n            // Who needs commas anyway?\n            { r: 'elem', b: 1, g: 'list,elem,imp,jsonic' },\n        ], { delete: [-1, -2] });\n    });\n}\nfunction makeJSON(jsonic) {\n    let justJSON = jsonic.make({\n        grammar$: false,\n        text: { lex: false },\n        number: {\n            hex: false,\n            oct: false,\n            bin: false,\n            sep: null,\n            exclude: /^00+/,\n        },\n        string: {\n            chars: '\"',\n            multiChars: '',\n            allowUnknown: false,\n            escape: { v: null },\n        },\n        comment: { lex: false },\n        map: { extend: false },\n        lex: { empty: false },\n        rule: { finish: false, include: 'json' },\n        result: { fail: [undefined, NaN] },\n        tokenSet: {\n            KEY: ['#ST', null, null, null],\n        },\n    });\n    grammar(justJSON);\n    return justJSON;\n}\n//# sourceMappingURL=grammar.js.map"
  },
  {
    "path": "dist/jsonic-bnf-cli.d.ts",
    "content": "export declare function run(argv: string[], console: Console): Promise<void>;\n"
  },
  {
    "path": "dist/jsonic-bnf-cli.js",
    "content": "\"use strict\";\n/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n    return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.run = run;\n/*  jsonic-bnf-cli.ts\n *  CLI wrapper for the BNF -> jsonic grammar spec converter.\n */\nconst node_fs_1 = __importDefault(require(\"node:fs\"));\nconst bnf_1 = require(\"./bnf\");\nconst jsonic_1 = require(\"./jsonic\");\nasync function run(argv, console) {\n    const args = {\n        help: false,\n        stdin: false,\n        files: [],\n        inline: [],\n        start: undefined,\n        tag: undefined,\n        space: 2,\n        // When set, convert and install the grammar, parse each sample,\n        // and report the tree (or an error) instead of the spec.\n        parse: [],\n        parseFiles: [],\n    };\n    for (let aI = 2; aI < argv.length; aI++) {\n        const arg = argv[aI];\n        if ('-' === arg) {\n            args.stdin = true;\n        }\n        else if ('--help' === arg || '-h' === arg) {\n            args.help = true;\n        }\n        else if ('--file' === arg || '-f' === arg) {\n            args.files.push(argv[++aI]);\n        }\n        else if ('--start' === arg || '-s' === arg) {\n            args.start = argv[++aI];\n        }\n        else if ('--tag' === arg || '-t' === arg) {\n            args.tag = argv[++aI];\n        }\n        else if ('--compact' === arg || '-c' === arg) {\n            args.space = 0;\n        }\n        else if ('--parse' === arg || '-P' === arg) {\n            args.parse.push(argv[++aI]);\n        }\n        else if ('--parse-file' === arg) {\n            args.parseFiles.push(argv[++aI]);\n        }\n        else if (arg && !arg.startsWith('-')) {\n            args.inline.push(arg);\n        }\n    }\n    if (args.help) {\n        return help(console);\n    }\n    let src = '';\n    for (const fp of args.files) {\n        if ('string' === typeof fp && '' !== fp) {\n            src += node_fs_1.default.readFileSync(fp).toString() + '\\n';\n        }\n    }\n    for (const inline of args.inline) {\n        src += inline + '\\n';\n    }\n    if ('' === src.trim() || args.stdin) {\n        src += await readStdin(console);\n    }\n    const spec = (0, bnf_1.bnf)(src, { start: args.start, tag: args.tag });\n    // Parse-mode: validate the grammar against one or more sample\n    // inputs and print their parse trees. Exits 1 if any sample fails.\n    if (args.parse.length > 0 || args.parseFiles.length > 0) {\n        const samples = [];\n        for (const fp of args.parseFiles) {\n            samples.push({\n                label: fp,\n                input: node_fs_1.default.readFileSync(fp).toString(),\n            });\n        }\n        for (const inp of args.parse) {\n            samples.push({ label: inp, input: inp });\n        }\n        const j = jsonic_1.Jsonic.make();\n        j.grammar(spec);\n        let failed = 0;\n        for (const { label, input } of samples) {\n            try {\n                const tree = j(input);\n                console.log(`ok: ${JSON.stringify(label)} -> ` +\n                    JSON.stringify(tree, null, args.space || undefined));\n            }\n            catch (e) {\n                failed++;\n                const msg = (e?.message || String(e)).split('\\n')[0];\n                console.error(`fail: ${JSON.stringify(label)}: ${msg}`);\n            }\n        }\n        if (failed > 0) {\n            process.exitCode = 1;\n        }\n        return;\n    }\n    console.log(JSON.stringify(spec, null, args.space || undefined));\n}\nasync function readStdin(console) {\n    if ('string' === typeof console.test$) {\n        return console.test$;\n    }\n    if (process.stdin.isTTY)\n        return '';\n    let s = '';\n    process.stdin.setEncoding('utf8');\n    for await (const p of process.stdin)\n        s += p;\n    return s;\n}\nfunction help(console) {\n    console.log(`\njsonic-bnf: convert a BNF grammar into a jsonic grammar spec.\n\nUsage: jsonic-bnf <args> [<bnf-source>]*\n\nArguments:\n  -                      Read BNF source from stdin.\n  --file <path>          Read BNF source from <path> (repeatable).\n  -f <path>\n\n  --start <name>         Set the start rule (defaults to the first\n  -s <name>                production).\n\n  --tag <name>           Group tag applied to every emitted alt.\n  -t <name>                Defaults to \\`bnf\\`.\n\n  --compact              Emit single-line JSON (default indent is 2).\n  -c\n\n  --parse <input>        Parse <input> against the generated grammar\n  -P <input>               and print its parse tree. Repeatable.\n                           Exits non-zero if any sample fails.\n\n  --parse-file <path>    Parse the contents of <path> against the\n                           generated grammar (repeatable).\n\n  --help                 Print this help message.\n  -h\n\nExamples:\n  > jsonic-bnf '<greet> ::= \"hi\" | \"hello\"'\n  > jsonic-bnf -f grammar.bnf\n  > echo '<g> ::= \"a\"' | jsonic-bnf -\n  > jsonic-bnf -f grammar.bnf --parse 'hi'\n`);\n}\n//# sourceMappingURL=jsonic-bnf-cli.js.map"
  },
  {
    "path": "dist/jsonic-cli.d.ts",
    "content": "export declare function run(argv: string[], console: Console): Promise<void>;\n"
  },
  {
    "path": "dist/jsonic-cli.js",
    "content": "\"use strict\";\n/* Copyright (c) 2020-2024 Richard Rodger, Oliver Sturm, and other contributors, MIT License */\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n    return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.run = run;\nconst node_fs_1 = __importDefault(require(\"node:fs\"));\nconst jsonic_1 = require(\"./jsonic\");\nconst debug_1 = require(\"./debug\");\nasync function run(argv, console) {\n    const args = {\n        help: false,\n        stdin: false,\n        sources: [],\n        files: [],\n        options: [],\n        meta: [],\n        plugins: [],\n    };\n    let plugins = {};\n    let accept_args = true;\n    for (let aI = 2; aI < argv.length; aI++) {\n        let arg = argv[aI];\n        if (accept_args && arg.startsWith('-')) {\n            if ('-' === arg) {\n                args.stdin = true;\n            } //\n            else if ('--' === arg) {\n                accept_args = false;\n            } //\n            else if ('--file' === arg || '-f' === arg) {\n                args.files.push(argv[++aI]);\n            } //\n            else if ('--option' === arg || '-o' === arg) {\n                args.options.push(argv[++aI]);\n            } //\n            else if ('--meta' === arg || '-m' === arg) {\n                args.meta.push(argv[++aI]);\n            } //\n            else if ('--debug' === arg || '-d' === arg) {\n                plugins.debug = debug_1.Debug;\n                args.meta.push('log=-1');\n            } //\n            else if ('--help' === arg || '-h' === arg) {\n                args.help = true;\n            } //\n            else if ('--plugin' === arg || '-p' === arg) {\n                args.plugins.push(argv[++aI]);\n            } //\n            else if ('--nice' === arg || '-n' === arg) {\n                args.options.push('JSON.space=2');\n            } //\n            else {\n                args.sources.push(arg);\n            }\n        } //\n        else {\n            args.sources.push(arg);\n        }\n    }\n    if (args.help) {\n        return help(console);\n    }\n    let options = handle_props(args.options);\n    let meta = handle_props(args.meta);\n    plugins = { ...plugins, ...handle_plugins(args.plugins) };\n    options.debug = options.debug || {};\n    options.debug.get_console = () => console;\n    let jsonic = jsonic_1.Jsonic.make(options);\n    for (let pn in plugins) {\n        jsonic.use(plugins[pn], options.plugin?.[pn] || {});\n    }\n    if (null != plugins.debug) {\n        console.log(jsonic.debug.describe() + '\\n=== PARSE ===');\n    }\n    let data = { val: null };\n    for (let fp of args.files) {\n        if ('string' === typeof fp && '' !== fp) {\n            jsonic_1.util.deep(data, { val: jsonic(node_fs_1.default.readFileSync(fp).toString(), meta) });\n        }\n    }\n    if (0 === args.sources.length || args.stdin) {\n        let stdin = await read_stdin(console);\n        jsonic_1.util.deep(data, { val: jsonic(stdin, meta) });\n    }\n    for (let src of args.sources) {\n        jsonic_1.util.deep(data, { val: jsonic(src, meta) });\n    }\n    options.JSON =\n        null == options.JSON || 'object' !== typeof options.JSON ? {} : options.JSON;\n    let replacer = (0, jsonic_1.Jsonic)(options.JSON.replacer);\n    let space = (0, jsonic_1.Jsonic)(options.JSON.space);\n    replacer = Array.isArray(replacer)\n        ? replacer\n        : null == replacer\n            ? null\n            : [replacer];\n    let json = JSON.stringify(data.val, replacer, space);\n    console.log(json);\n}\nasync function read_stdin(console) {\n    if ('string' === typeof console.test$) {\n        return console.test$;\n    }\n    if (process.stdin.isTTY)\n        return '';\n    let s = '';\n    process.stdin.setEncoding('utf8');\n    for await (const p of process.stdin)\n        s += p;\n    return s;\n}\n// NOTE: uses vanilla Jsonic to parse arg vals, so you can set complex\n// properties.  This will break if core Jsonic is broken.\nfunction handle_props(propvals) {\n    let out = {};\n    for (let propval of propvals) {\n        let pv = propval.split(/=/);\n        if ('' !== pv[0] && '' !== pv[1]) {\n            let val = (0, jsonic_1.Jsonic)(pv[1]);\n            jsonic_1.util.prop(out, pv[0], val);\n        }\n    }\n    return out;\n}\nfunction handle_plugins(plugins) {\n    let out = {};\n    for (let name of plugins) {\n        try {\n            out[name] = require(name);\n        }\n        catch (e) {\n            let err = e;\n            // Might be @jsonic plugin\n            if (!name.startsWith('@')) {\n                try {\n                    out[name] = require('@jsonic/' + name);\n                }\n                catch (e) {\n                    throw err; // NOTE: throws original error\n                }\n            }\n            else {\n                throw err;\n            }\n        }\n        // Handle some variations in the way the plugin function is exported.\n        if ('function' !== typeof out[name]) {\n            let refname = (name.match(/([^.\\\\\\/]+)($|\\.[^.]+$)/) || [])[1];\n            refname = null != refname ? refname.toLowerCase() : refname;\n            // See test plugin test/p1.js\n            if ('function' == typeof out[name].default) {\n                out[name] = out[name].default;\n            } //\n            else if (null != refname &&\n                'function' == typeof out[name][camel(refname)]) {\n                out[name] = out[name][camel(refname)];\n            }\n            // See test plugin test/p2.js\n            else if (null != refname &&\n                'function' == typeof out[name][refname]) {\n                out[refname] = out[name][refname];\n                delete out[name];\n            } //\n            else {\n                throw new Error('Plugin is not a function: ' + name);\n            }\n        }\n    }\n    return out;\n}\nfunction camel(s) {\n    return (s[0].toUpperCase() +\n        s\n            .substring(1)\n            .replace(/-(\\w)/g, (m) => m[1][0].toUpperCase() + m[1].substring(1)));\n}\nfunction help(console) {\n    let s = `\nA JSON parser that isn't strict.\n\nUsage: jsonic <args> [<source-text>]*\n\nwhere \n  <source-text> is the source text to be parsed into JSON.\n    If omitted, the source text is read from STDIN. If multiple source texts\n    are provided, they will be merged in precedence (from highest) \n    right to left, STDIN, <file>.\n\n  <args> are the command arguments:\n\n    -                      Alias for STDIN.\n\n    --file <file>          Load and parse <file>.\n    -f <file>\n\n    --option <name=value>  Set option <name> to <value>, where <name> \n    -o <name=value>          can be a dotted path (see example below).\n\n    --nice                 Print JSON indented over multiple lines.\n    -n\n\n    --meta <meta=value>    Set parse meta data <name> to <value>, where <name> \n    -m <meta=value>          can be a dotted path (see option example).\n\n    --plugin <require>     Load a plugin, where <require> is the plugin module\n    -p <require>             reference (name or path).\n\n    --debug                Print abbreviated lex and parse logs for debugging,\n    -d                       alias of \\`--meta log = -1\\`.\n\n    --help                 Print this help message.\n    -h\n\nOutput:\n  Output is generated by the built-in JSON.stringify method. The \\`replacer\\`\n    and \\`space\\` arguments can be specified using \\`-o JSON.replacer=...\\` and\n    \\`-o JSON.space=...\\` respectively.\n\n\nPlugins \n  The built-in plugins (found in the ./plugin folder of the distribution) can be \n  specified using the abbreviated references:\n    directive, multisource, csv, toml, ...\n\n  Plugin options can be specified using: \\`-o plugin.<name>.<option>=<value>\\`.\n  See the example below.\n\n\nExamples:\n\n# Basic usage\n> jsonic a:1\n{\"a\":1} \n\n> jsonic -n a:1\n{\n  \"a\": 1\n}\n\n\n# Merging arguments\n> jsonic a:b:1 a:c:2\n{\"a\":{\"b\":1,\"c\":2}}\n\n\n# Output options\n> jsonic a:b:1 a:c:2 --option JSON.space=2\n{\n  \"a\": {\n    \"b\": 1,\n    \"c\": 2\n  }\n}\n\n\n# Piping\n> echo a:1 | jsonic\n{\"a\":1} \n\n\n# Using plugins (e.g. npm install @jsonic/csv)\n> jsonic -p csv  -o plugin.csv.record.separators=^ \"a,b^1,2\"\n[{\"a\":\"1\",\"b\":\"2\"}]\n\n\n# Full debug tracing\n> jsonic -d -o plugin.debug.trace=true a:1\n... lots of debug info, including token-by-token trace ...\n{\"a\":1}\n\n\nSee also: http://jsonic.senecajs.org\n`;\n    console.log(s);\n}\n//# sourceMappingURL=jsonic-cli.js.map"
  },
  {
    "path": "dist/jsonic.d.ts",
    "content": "import type { AltAction, AltCond, AltError, AltMatch, AltModifier, AltSpec, Bag, Config, Context, Counters, FuncRef, JsonicAPI, JsonicParse, Lex, LexCheck, LexMatcher, MakeLexMatcher, NormAltSpec, Options, Parser, Plugin, Point, Rule, RuleDefiner, RuleSpec, RuleSpecMap, RuleState, StateAction, Tin, Token } from './types';\nimport { OPEN, CLOSE, BEFORE, AFTER, EMPTY, SKIP } from './types';\nimport { S, badlex, deep, makelog, mesc, regexp, tokenize, srcfmt, clone, charset, configure, escre, parserwrap, str, clean } from './utility';\nimport { JsonicError, errdesc, errinject, errsite, errmsg, trimstk, strinject, prop } from './error';\nimport { makePoint, makeToken, makeLex, makeFixedMatcher, makeSpaceMatcher, makeLineMatcher, makeStringMatcher, makeCommentMatcher, makeNumberMatcher, makeTextMatcher } from './lexer';\nimport { makeRule, makeRuleSpec, makeParser } from './parser';\ndeclare const util: {\n    tokenize: typeof tokenize;\n    srcfmt: typeof srcfmt;\n    clone: typeof clone;\n    charset: typeof charset;\n    trimstk: typeof trimstk;\n    makelog: typeof makelog;\n    badlex: typeof badlex;\n    errsite: typeof errsite;\n    errinject: typeof errinject;\n    errdesc: typeof errdesc;\n    configure: typeof configure;\n    parserwrap: typeof parserwrap;\n    mesc: typeof mesc;\n    escre: typeof escre;\n    regexp: typeof regexp;\n    prop: typeof prop;\n    str: typeof str;\n    clean: typeof clean;\n    errmsg: typeof errmsg;\n    strinject: typeof strinject;\n    deep: typeof deep;\n    omap: (o: any, f?: (e: any) => any) => any;\n    keys: (x: any) => string[];\n    values: <T>(x: {\n        [key: string]: T;\n    } | undefined | null) => T[];\n    entries: <T>(x: {\n        [key: string]: T;\n    } | undefined | null) => [string, T][];\n};\ntype Jsonic = JsonicParse & // A function that parses.\nJsonicAPI & {\n    [prop: string]: any;\n};\ndeclare function make(param_options?: Bag | string, parent?: Jsonic): Jsonic;\ndeclare let root: any;\ndeclare let Jsonic: Jsonic;\nexport type { AltAction, AltCond, AltError, AltMatch, AltModifier, AltSpec, Bag, Config, Context, Counters, FuncRef, Lex, LexCheck, LexMatcher, MakeLexMatcher, NormAltSpec, Options, Plugin, Point, Rule, RuleDefiner, RuleSpec, RuleSpecMap, RuleState, StateAction, Tin, Token, };\nexport { Jsonic as Jsonic, JsonicError, Parser, util, make, makeToken, makePoint, makeRule, makeRuleSpec, makeLex, makeParser, makeFixedMatcher, makeSpaceMatcher, makeLineMatcher, makeStringMatcher, makeCommentMatcher, makeNumberMatcher, makeTextMatcher, OPEN, CLOSE, BEFORE, AFTER, EMPTY, SKIP, S, root, };\nexport default Jsonic;\n"
  },
  {
    "path": "dist/jsonic.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2023 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.root = exports.S = exports.SKIP = exports.EMPTY = exports.AFTER = exports.BEFORE = exports.CLOSE = exports.OPEN = exports.makeTextMatcher = exports.makeNumberMatcher = exports.makeCommentMatcher = exports.makeStringMatcher = exports.makeLineMatcher = exports.makeSpaceMatcher = exports.makeFixedMatcher = exports.makeParser = exports.makeLex = exports.makeRuleSpec = exports.makeRule = exports.makePoint = exports.makeToken = exports.util = exports.JsonicError = exports.Jsonic = void 0;\nexports.make = make;\nconst types_1 = require(\"./types\");\nObject.defineProperty(exports, \"OPEN\", { enumerable: true, get: function () { return types_1.OPEN; } });\nObject.defineProperty(exports, \"CLOSE\", { enumerable: true, get: function () { return types_1.CLOSE; } });\nObject.defineProperty(exports, \"BEFORE\", { enumerable: true, get: function () { return types_1.BEFORE; } });\nObject.defineProperty(exports, \"AFTER\", { enumerable: true, get: function () { return types_1.AFTER; } });\nObject.defineProperty(exports, \"EMPTY\", { enumerable: true, get: function () { return types_1.EMPTY; } });\nObject.defineProperty(exports, \"SKIP\", { enumerable: true, get: function () { return types_1.SKIP; } });\nconst utility_1 = require(\"./utility\");\nObject.defineProperty(exports, \"S\", { enumerable: true, get: function () { return utility_1.S; } });\nconst error_1 = require(\"./error\");\nObject.defineProperty(exports, \"JsonicError\", { enumerable: true, get: function () { return error_1.JsonicError; } });\nconst defaults_1 = require(\"./defaults\");\nconst lexer_1 = require(\"./lexer\");\nObject.defineProperty(exports, \"makePoint\", { enumerable: true, get: function () { return lexer_1.makePoint; } });\nObject.defineProperty(exports, \"makeToken\", { enumerable: true, get: function () { return lexer_1.makeToken; } });\nObject.defineProperty(exports, \"makeLex\", { enumerable: true, get: function () { return lexer_1.makeLex; } });\nObject.defineProperty(exports, \"makeFixedMatcher\", { enumerable: true, get: function () { return lexer_1.makeFixedMatcher; } });\nObject.defineProperty(exports, \"makeSpaceMatcher\", { enumerable: true, get: function () { return lexer_1.makeSpaceMatcher; } });\nObject.defineProperty(exports, \"makeLineMatcher\", { enumerable: true, get: function () { return lexer_1.makeLineMatcher; } });\nObject.defineProperty(exports, \"makeStringMatcher\", { enumerable: true, get: function () { return lexer_1.makeStringMatcher; } });\nObject.defineProperty(exports, \"makeCommentMatcher\", { enumerable: true, get: function () { return lexer_1.makeCommentMatcher; } });\nObject.defineProperty(exports, \"makeNumberMatcher\", { enumerable: true, get: function () { return lexer_1.makeNumberMatcher; } });\nObject.defineProperty(exports, \"makeTextMatcher\", { enumerable: true, get: function () { return lexer_1.makeTextMatcher; } });\nconst parser_1 = require(\"./parser\");\nObject.defineProperty(exports, \"makeRule\", { enumerable: true, get: function () { return parser_1.makeRule; } });\nObject.defineProperty(exports, \"makeRuleSpec\", { enumerable: true, get: function () { return parser_1.makeRuleSpec; } });\nObject.defineProperty(exports, \"makeParser\", { enumerable: true, get: function () { return parser_1.makeParser; } });\nconst grammar_1 = require(\"./grammar\");\nconst bnf_1 = require(\"./bnf\");\n// TODO: remove - too much for an API!\nconst util = {\n    tokenize: utility_1.tokenize,\n    srcfmt: utility_1.srcfmt,\n    clone: utility_1.clone,\n    charset: utility_1.charset,\n    trimstk: error_1.trimstk,\n    makelog: utility_1.makelog,\n    badlex: utility_1.badlex,\n    errsite: error_1.errsite,\n    errinject: error_1.errinject,\n    errdesc: error_1.errdesc,\n    configure: utility_1.configure,\n    parserwrap: utility_1.parserwrap,\n    mesc: utility_1.mesc,\n    escre: utility_1.escre,\n    regexp: utility_1.regexp,\n    prop: error_1.prop,\n    str: utility_1.str,\n    clean: utility_1.clean,\n    errmsg: error_1.errmsg,\n    strinject: error_1.strinject,\n    // TODO: validated to include in util API:\n    deep: utility_1.deep,\n    omap: utility_1.omap,\n    keys: utility_1.keys,\n    values: utility_1.values,\n    entries: utility_1.entries,\n};\nexports.util = util;\nfunction make(param_options, parent) {\n    let injectFullAPI = true;\n    if ('jsonic' === param_options) {\n        injectFullAPI = false;\n    }\n    else if ('json' === param_options) {\n        return (0, grammar_1.makeJSON)(root);\n    }\n    param_options = 'string' === typeof param_options ? {} : param_options;\n    let internal = {\n        parser: null,\n        config: null,\n        plugins: [],\n        sub: {\n            lex: undefined,\n            rule: undefined,\n        },\n        mark: Math.random(),\n    };\n    // Merge options.\n    let merged_options = (0, utility_1.deep)({}, parent\n        ? { ...parent.options }\n        : false === param_options?.defaults$\n            ? {}\n            : defaults_1.defaults, param_options ? param_options : {});\n    // Create primary parsing function\n    let jsonic = function Jsonic(src, meta, parent_ctx) {\n        if (utility_1.S.string === typeof src) {\n            let internal = jsonic.internal();\n            let parser = optionsMethod.parser?.start\n                ? (0, utility_1.parserwrap)(optionsMethod.parser)\n                : internal.parser;\n            return parser.start(src, jsonic, meta, parent_ctx);\n        }\n        return src;\n    };\n    // This lets you access options as direct properties,\n    // and set them as a function call.\n    // `change_options` can be a Bag object or a jsonic-format string that\n    // is parsed into a Bag before applying.\n    let optionsMethod = (change_options) => {\n        if (null != change_options) {\n            if (utility_1.S.string === typeof change_options) {\n                const parsed = make()(change_options);\n                change_options =\n                    null != parsed && utility_1.S.object === typeof parsed\n                        ? parsed\n                        : undefined;\n            }\n            if (null != change_options && utility_1.S.object === typeof change_options) {\n                (0, utility_1.deep)(merged_options, change_options);\n                (0, utility_1.configure)(jsonic, internal.config, merged_options);\n                let parser = jsonic.internal().parser;\n                internal.parser = parser.clone(merged_options, internal.config, jsonic);\n            }\n        }\n        return { ...jsonic.options };\n    };\n    // Define the API\n    let api = {\n        token: ((ref) => internal.config.fixed.token[ref] ??\n            (0, utility_1.tokenize)(ref, internal.config, jsonic)),\n        tokenSet: ((ref) => (0, utility_1.findTokenSet)(ref, internal.config)),\n        fixed: ((ref) => internal.config.fixed.ref[ref]),\n        options: (0, utility_1.deep)(optionsMethod, merged_options),\n        config: () => (0, utility_1.deep)(internal.config),\n        parse: jsonic,\n        // TODO: how to handle null plugin?\n        use: function use(plugin, plugin_options) {\n            if (utility_1.S.function !== typeof plugin) {\n                throw new Error('Jsonic.use: the first argument must be a function ' +\n                    'defining a plugin. See https://jsonic.senecajs.org/plugin');\n            }\n            // Plugin name keys in options.plugin are the lower-cased plugin function name.\n            const plugin_name = plugin.name.toLowerCase();\n            const full_plugin_options = (0, utility_1.deep)({}, plugin.defaults || {}, plugin_options || {});\n            jsonic.options({\n                plugin: {\n                    [plugin_name]: full_plugin_options,\n                },\n            });\n            let merged_plugin_options = jsonic.options.plugin[plugin_name];\n            jsonic.internal().plugins.push(plugin);\n            plugin.options = merged_plugin_options;\n            return plugin(jsonic, merged_plugin_options) || jsonic;\n        },\n        rule: (name, define) => {\n            return jsonic.internal().parser.rule(name, define) || jsonic;\n        },\n        make: (options) => {\n            return make(options, jsonic);\n        },\n        empty: (options) => make({\n            defaults$: false,\n            standard$: false,\n            grammar$: false,\n            ...(options || {}),\n        }),\n        id: 'Jsonic/' +\n            Date.now() +\n            '/' +\n            ('' + Math.random()).substring(2, 8).padEnd(6, '0') +\n            (null == optionsMethod.tag ? '' : '/' + optionsMethod.tag),\n        toString: () => {\n            return api.id;\n        },\n        sub: (spec) => {\n            if (spec.lex) {\n                internal.sub.lex = internal.sub.lex || [];\n                internal.sub.lex.push(spec.lex);\n            }\n            if (spec.rule) {\n                internal.sub.rule = internal.sub.rule || [];\n                internal.sub.rule.push(spec.rule);\n            }\n            return jsonic;\n        },\n        util,\n        grammar: (gs, setting) => {\n            if ('string' === typeof gs) {\n                const parsed = make()(gs);\n                if (null == parsed || 'object' !== typeof parsed) {\n                    return;\n                }\n                gs = parsed;\n            }\n            // Normalize the optional setting's rule.alt.g value to a string[] once.\n            const altG = setting?.rule?.alt?.g;\n            const altGArr = null == altG\n                ? null\n                : Array.isArray(altG)\n                    ? [...altG]\n                    : String(altG).split(/\\s*,\\s*/).filter((s) => s.length > 0);\n            // Append altGArr tags to each alt's g field without mutating the input alt.\n            const applyG = (alts) => {\n                if (null == altGArr || 0 === altGArr.length || !Array.isArray(alts)) {\n                    return alts;\n                }\n                return alts.map((a) => {\n                    if (null == a || 'object' !== typeof a)\n                        return a;\n                    const existing = null == a.g\n                        ? []\n                        : Array.isArray(a.g)\n                            ? [...a.g]\n                            : String(a.g).split(/\\s*,\\s*/).filter((s) => s.length > 0);\n                    return { ...a, g: [...existing, ...altGArr] };\n                });\n            };\n            if (gs.options) {\n                const resolved = (0, utility_1.resolveFuncRefs)(gs.options, gs.ref);\n                ji.options(resolved);\n            }\n            if (gs.rule) {\n                for (const rulename of Object.keys(gs.rule)) {\n                    const rulespec = gs.rule[rulename];\n                    ji.rule(rulename, (rs) => {\n                        if (gs.ref) {\n                            rs.fnref(gs.ref);\n                        }\n                        if (rulespec.open) {\n                            const isarr = Array.isArray(rulespec.open);\n                            const alts = isarr ? rulespec.open : rulespec.open.alts;\n                            const inject = isarr ? {} : rulespec.open.inject;\n                            rs.open(applyG(alts), inject);\n                        }\n                        if (rulespec.close) {\n                            const isarr = Array.isArray(rulespec.close);\n                            const alts = isarr ? rulespec.close : rulespec.close.alts;\n                            const inject = isarr ? {} : rulespec.close.inject;\n                            rs.close(applyG(alts), inject);\n                        }\n                    });\n                }\n            }\n        },\n        // Convert a BNF grammar string into a jsonic GrammarSpec and install\n        // it on this instance. Returns the generated spec so callers can\n        // inspect, serialise or diff it. Use `bnf.toSpec(src, opts)` to\n        // build the spec without installing it.\n        bnf: (() => {\n            const impl = (src, opts) => {\n                const spec = (0, bnf_1.bnf)(src, opts);\n                ji.grammar(spec);\n                return spec;\n            };\n            impl.toSpec = (src, opts) => (0, bnf_1.bnf)(src, opts);\n            return impl;\n        })(),\n    };\n    // Has to be done indirectly as we are in a fuction named `make`.\n    (0, utility_1.defprop)(api.make, utility_1.S.name, { value: utility_1.S.make });\n    let ji = jsonic;\n    if (injectFullAPI) {\n        // Add API methods to the core utility function.\n        (0, utility_1.assign)(jsonic, api);\n    }\n    else {\n        (0, utility_1.assign)(jsonic, {\n            empty: api.empty,\n            parse: api.parse,\n            sub: api.sub,\n            id: api.id,\n            toString: api.toString,\n        });\n        ji = (0, utility_1.assign)(Object.create(jsonic), api);\n    }\n    // Hide internals where you can still find them.\n    (0, utility_1.defprop)(jsonic, 'internal', { value: () => internal });\n    if (parent) {\n        // Transfer extra parent properties (preserves plugin decorations, etc).\n        for (let k in parent) {\n            if (undefined === jsonic[k]) {\n                jsonic[k] = parent[k];\n            }\n        }\n        jsonic.parent = parent;\n        let parent_internal = parent.internal();\n        internal.config = (0, utility_1.deep)({}, parent_internal.config);\n        (0, utility_1.configure)(jsonic, internal.config, merged_options);\n        (0, utility_1.assign)(jsonic.token, internal.config.t);\n        internal.plugins = [...parent_internal.plugins];\n        internal.parser = parent_internal.parser.clone(merged_options, internal.config, ji);\n    }\n    else {\n        let rootWithAPI = { ...jsonic, ...api };\n        internal.config = (0, utility_1.configure)(rootWithAPI, undefined, merged_options);\n        internal.plugins = [];\n        internal.parser = (0, parser_1.makeParser)(merged_options, internal.config, ji);\n        if (false !== merged_options.grammar$) {\n            (0, grammar_1.grammar)(rootWithAPI);\n        }\n    }\n    return jsonic;\n}\nlet root = undefined;\nexports.root = root;\n// The global root Jsonic instance parsing rules cannot be modified.\n// use Jsonic.make() to create a modifiable instance.\nlet Jsonic = (exports.root = root = make('jsonic'));\nexports.Jsonic = Jsonic;\n// Provide deconstruction export names\nroot.Jsonic = root;\nroot.JsonicError = error_1.JsonicError;\nroot.makeLex = lexer_1.makeLex;\nroot.makeParser = parser_1.makeParser;\nroot.makeToken = lexer_1.makeToken;\nroot.makePoint = lexer_1.makePoint;\nroot.makeRule = parser_1.makeRule;\nroot.makeRuleSpec = parser_1.makeRuleSpec;\nroot.makeFixedMatcher = lexer_1.makeFixedMatcher;\nroot.makeSpaceMatcher = lexer_1.makeSpaceMatcher;\nroot.makeLineMatcher = lexer_1.makeLineMatcher;\nroot.makeStringMatcher = lexer_1.makeStringMatcher;\nroot.makeCommentMatcher = lexer_1.makeCommentMatcher;\nroot.makeNumberMatcher = lexer_1.makeNumberMatcher;\nroot.makeTextMatcher = lexer_1.makeTextMatcher;\nroot.OPEN = types_1.OPEN;\nroot.CLOSE = types_1.CLOSE;\nroot.BEFORE = types_1.BEFORE;\nroot.AFTER = types_1.AFTER;\nroot.EMPTY = types_1.EMPTY;\nroot.SKIP = types_1.SKIP;\nroot.util = util;\nroot.make = make;\nroot.S = utility_1.S;\nexports.default = Jsonic;\nif ('undefined' !== typeof module) {\n    module.exports = Jsonic;\n}\n//# sourceMappingURL=jsonic.js.map"
  },
  {
    "path": "dist/lexer.d.ts",
    "content": "import type { Tin, Token, Point, Lex, Rule, Config, Context, MakeLexMatcher, Bag, NormAltSpec } from './types';\nimport { INSPECT } from './types';\ndeclare class PointImpl implements Point {\n    len: number;\n    sI: number;\n    rI: number;\n    cI: number;\n    token: Token[];\n    end?: Token;\n    constructor(len: number, sI?: number, rI?: number, cI?: number);\n    toString(): string;\n    [INSPECT](): string;\n}\ndeclare const makePoint: (...params: ConstructorParameters<typeof PointImpl>) => PointImpl;\ndeclare class TokenImpl implements Token {\n    isToken: boolean;\n    name: string;\n    tin: number;\n    val: undefined;\n    src: string;\n    sI: number;\n    rI: number;\n    cI: number;\n    len: number;\n    use?: Bag;\n    err?: string;\n    why?: string;\n    constructor(name: string, tin: Tin, val: any, src: string, pnt: Point, use?: any, why?: string);\n    resolveVal(rule: Rule, ctx: Context): any;\n    bad(err: string, details?: any): Token;\n    toString(): string;\n    [INSPECT](): string;\n}\ndeclare const makeToken: (...params: ConstructorParameters<typeof TokenImpl>) => TokenImpl;\ndeclare const makeNoToken: () => TokenImpl;\ndeclare let makeFixedMatcher: MakeLexMatcher;\ndeclare let makeMatchMatcher: MakeLexMatcher;\ndeclare let makeCommentMatcher: MakeLexMatcher;\ndeclare let makeTextMatcher: MakeLexMatcher;\ndeclare let makeNumberMatcher: MakeLexMatcher;\ndeclare let makeStringMatcher: MakeLexMatcher;\ndeclare let makeLineMatcher: MakeLexMatcher;\ndeclare let makeSpaceMatcher: MakeLexMatcher;\ndeclare class LexImpl implements Lex {\n    src: string;\n    ctx: Context;\n    cfg: Config;\n    pnt: PointImpl;\n    fwd: string;\n    refwd(): string;\n    constructor(ctx: Context);\n    token(ref: Tin | string, val: any, src: string, pnt?: Point, use?: any, why?: string): Token;\n    next(rule: Rule, alt?: NormAltSpec, altI?: number, tI?: number): Token;\n    tokenize<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R): T;\n    bad(why: string, pstart: number, pend: number): Token;\n}\ndeclare const makeLex: (...params: ConstructorParameters<typeof LexImpl>) => LexImpl;\nexport { makeNoToken, makeLex, makePoint, makeToken, makeMatchMatcher, makeFixedMatcher, makeSpaceMatcher, makeLineMatcher, makeStringMatcher, makeCommentMatcher, makeNumberMatcher, makeTextMatcher, };\n"
  },
  {
    "path": "dist/lexer.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2022 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.makeTextMatcher = exports.makeNumberMatcher = exports.makeCommentMatcher = exports.makeStringMatcher = exports.makeLineMatcher = exports.makeSpaceMatcher = exports.makeFixedMatcher = exports.makeMatchMatcher = exports.makeToken = exports.makePoint = exports.makeLex = exports.makeNoToken = void 0;\nconst types_1 = require(\"./types\");\nconst utility_1 = require(\"./utility\");\nclass PointImpl {\n    constructor(len, sI, rI, cI) {\n        this.len = -1;\n        this.sI = 0;\n        this.rI = 1;\n        this.cI = 1;\n        this.token = [];\n        this.len = len;\n        if (null != sI) {\n            this.sI = sI;\n        }\n        if (null != rI) {\n            this.rI = rI;\n        }\n        if (null != cI) {\n            this.cI = cI;\n        }\n    }\n    toString() {\n        return ('Point[' +\n            [this.sI + '/' + this.len, this.rI, this.cI] +\n            (0 < this.token.length ? ' ' + this.token : '') +\n            ']');\n    }\n    [types_1.INSPECT]() {\n        return this.toString();\n    }\n}\nconst makePoint = (...params) => new PointImpl(...params);\nexports.makePoint = makePoint;\n// Tokens from the lexer.\nclass TokenImpl {\n    constructor(name, tin, val, src, pnt, use, why) {\n        this.isToken = true;\n        this.name = types_1.EMPTY;\n        this.tin = -1;\n        this.val = undefined;\n        this.src = types_1.EMPTY;\n        this.sI = -1;\n        this.rI = -1;\n        this.cI = -1;\n        this.len = -1;\n        this.name = name;\n        this.tin = tin;\n        this.src = src;\n        this.val = val;\n        this.sI = pnt.sI;\n        this.rI = pnt.rI;\n        this.cI = pnt.cI;\n        this.use = use;\n        this.why = why;\n        this.len = null == src ? 0 : src.length;\n    }\n    resolveVal(rule, ctx) {\n        let out = 'function' === typeof this.val ? this.val(rule, ctx) : this.val;\n        return out;\n    }\n    bad(err, details) {\n        this.err = err;\n        if (null != details) {\n            this.use = (0, utility_1.deep)(this.use || {}, details);\n        }\n        return this;\n    }\n    toString() {\n        return ('Token[' +\n            this.name +\n            '=' +\n            this.tin +\n            ' ' +\n            (0, utility_1.snip)(this.src) +\n            (undefined === this.val || '#ST' === this.name || '#TX' === this.name\n                ? ''\n                : '=' + (0, utility_1.snip)(this.val)) +\n            ' ' +\n            [this.sI, this.rI, this.cI] +\n            (null == this.use\n                ? ''\n                : ' ' + (0, utility_1.snip)('' + JSON.stringify(this.use).replace(/\"/g, ''), 22)) +\n            (null == this.err ? '' : ' ' + this.err) +\n            (null == this.why ? '' : ' ' + (0, utility_1.snip)('' + this.why, 22)) +\n            ']');\n    }\n    [types_1.INSPECT]() {\n        return this.toString();\n    }\n}\nconst makeToken = (...params) => new TokenImpl(...params);\nexports.makeToken = makeToken;\nconst makeNoToken = () => makeToken('', -1, undefined, types_1.EMPTY, makePoint(-1));\nexports.makeNoToken = makeNoToken;\nlet makeFixedMatcher = (cfg, _opts) => {\n    let fixed = (0, utility_1.regexp)(null, '^(', cfg.rePart.fixed, ')');\n    return function fixedMatcher(lex) {\n        let mcfg = cfg.fixed;\n        if (!mcfg.lex)\n            return undefined;\n        if (cfg.fixed.check) {\n            let check = cfg.fixed.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let pnt = lex.pnt;\n        let fwd = lex.fwd;\n        let m = fwd.match(fixed);\n        if (m) {\n            let msrc = m[1];\n            let mlen = msrc.length;\n            if (0 < mlen) {\n                let tkn = undefined;\n                let tin = mcfg.token[msrc];\n                if (null != tin) {\n                    tkn = lex.token(tin, undefined, msrc, pnt);\n                    pnt.sI += mlen;\n                    pnt.cI += mlen;\n                }\n                return tkn;\n            }\n        }\n    };\n};\nexports.makeFixedMatcher = makeFixedMatcher;\nlet makeMatchMatcher = (cfg, _opts) => {\n    // Pre-sort both matcher lists at configure time so lexing iterates in\n    // a deterministic order regardless of how the config object was built.\n    // Value matchers: sort by user-supplied name (ascending).\n    // Token matchers: sort by attached tin$ (ascending), set in utility.ts.\n    let valueMatchers = (0, utility_1.entries)(cfg.match.value)\n        .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n        .map(([, spec]) => spec);\n    let tokenMatchers = (0, utility_1.values)(cfg.match.token).sort((a, b) => (a.tin$ || 0) - (b.tin$ || 0));\n    // Don't add a matcher if there's nothing to do.\n    if (0 === valueMatchers.length && 0 === tokenMatchers.length) {\n        return null;\n    }\n    return function matchMatcher(lex, rule, tI = 0) {\n        let mcfg = cfg.match;\n        if (!mcfg.lex)\n            return undefined;\n        if (cfg.match.check) {\n            let check = cfg.match.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let pnt = lex.pnt;\n        let fwd = lex.fwd;\n        let oc = 'o' === rule.state ? 0 : 1;\n        for (let valueMatcher of valueMatchers) {\n            if (valueMatcher.match instanceof RegExp) {\n                // TODO: only match VL if present in rule\n                let m = fwd.match(valueMatcher.match);\n                if (m) {\n                    let msrc = m[0];\n                    let mlen = msrc.length;\n                    if (0 < mlen) {\n                        let tkn = undefined;\n                        let val = valueMatcher.val ? valueMatcher.val(m) : msrc;\n                        tkn = lex.token('#VL', val, msrc, pnt);\n                        pnt.sI += mlen;\n                        pnt.cI += mlen;\n                        return tkn;\n                    }\n                }\n            }\n            else {\n                let tkn = valueMatcher.match(lex, rule);\n                if (null != tkn) {\n                    return tkn;\n                }\n            }\n        }\n        for (let tokenMatcher of tokenMatchers) {\n            // Only match Token if present in Rule sequence.\n            // Exception: an `eager$` flag on the matcher opts out of\n            // tcol gating — the matcher fires whenever its regex matches\n            // and the downstream parser rejects tokens it doesn't expect\n            // at the current position. This is what ABNF's\n            // case-insensitive literals need: the lexer has to emit the\n            // literal's own tin even when the current rule's tcol is\n            // narrower, so the next rule up the stack can see the token\n            // as its proper type rather than falling through to #TX.\n            if (tokenMatcher.tin$ &&\n                !tokenMatcher.eager$ &&\n                !rule.spec.def.tcol[oc][tI].includes(tokenMatcher.tin$)) {\n                continue;\n            }\n            if (tokenMatcher instanceof RegExp) {\n                let m = fwd.match(tokenMatcher);\n                if (m) {\n                    let msrc = m[0];\n                    let mlen = msrc.length;\n                    if (0 < mlen) {\n                        let tkn = undefined;\n                        let tin = tokenMatcher.tin$;\n                        tkn = lex.token(tin, msrc, msrc, pnt);\n                        pnt.sI += mlen;\n                        pnt.cI += mlen;\n                        return tkn;\n                    }\n                }\n            }\n            else {\n                let tkn = tokenMatcher(lex, rule);\n                if (null != tkn) {\n                    return tkn;\n                }\n            }\n        }\n    };\n};\nexports.makeMatchMatcher = makeMatchMatcher;\nlet makeCommentMatcher = (cfg, opts) => {\n    let oc = opts.comment;\n    cfg.comment = {\n        lex: oc ? !!oc.lex : false,\n        def: (oc?.def ? (0, utility_1.entries)(oc.def) : []).reduce((def, [name, om]) => {\n            // Set comment marker to null to remove\n            if (null == om || false === om) {\n                return def;\n            }\n            let { suffixes, suffixFn } = normalizeCommentSuffix(om.suffix);\n            let cm = {\n                name,\n                start: om.start,\n                end: om.end,\n                line: !!om.line,\n                lex: !!om.lex,\n                eatline: !!om.eatline,\n                suffixes,\n                suffixFn,\n            };\n            def[name] = cm;\n            return def;\n        }, {}),\n    };\n    // Pre-sort by start length (longest first) so that a longer marker\n    // shadows any shorter marker it contains (e.g. '##' wins over '#'),\n    // regardless of the insertion order of cfg.comment.def. Ties break by\n    // name for deterministic iteration across runtimes.\n    let byStartLenDesc = (a, b) => b.start.length - a.start.length ||\n        (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);\n    let lineComments = cfg.comment.lex\n        ? (0, utility_1.values)(cfg.comment.def).filter((c) => c.lex && c.line).sort(byStartLenDesc)\n        : [];\n    let blockComments = cfg.comment.lex\n        ? (0, utility_1.values)(cfg.comment.def).filter((c) => c.lex && !c.line).sort(byStartLenDesc)\n        : [];\n    return function matchComment(lex, _rule) {\n        let mcfg = cfg.comment;\n        if (!mcfg.lex)\n            return undefined;\n        if (cfg.comment.check) {\n            let check = cfg.comment.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let pnt = lex.pnt;\n        let fwd = lex.fwd;\n        let rI = pnt.rI;\n        let cI = pnt.cI;\n        // Single line comment.\n        for (let mc of lineComments) {\n            if (fwd.startsWith(mc.start)) {\n                let fwdlen = fwd.length;\n                let fI = mc.start.length;\n                cI += mc.start.length;\n                let suffixLen = 0;\n                while (fI < fwdlen && !cfg.line.chars[fwd[fI]]) {\n                    let n = commentSuffixMatch(fwd, fI, mc.suffixes);\n                    if (n > 0) {\n                        suffixLen = n;\n                        break;\n                    }\n                    n = commentSuffixFnMatch(lex, fI, mc.suffixFn);\n                    if (n > 0) {\n                        suffixLen = n;\n                        break;\n                    }\n                    cI++;\n                    fI++;\n                }\n                if (suffixLen > 0) {\n                    // Consume the suffix as the tail of the comment body.\n                    fI += suffixLen;\n                    cI += suffixLen;\n                }\n                else if (mc.eatline) {\n                    // Only absorb trailing line chars when termination came from\n                    // a line char (not from a suffix match).\n                    while (fI < fwdlen && cfg.line.chars[fwd[fI]]) {\n                        if (cfg.line.rowChars[fwd[fI]]) {\n                            rI++;\n                        }\n                        fI++;\n                    }\n                }\n                let csrc = fwd.substring(0, fI);\n                let tkn = lex.token('#CM', undefined, csrc, pnt);\n                pnt.sI += csrc.length;\n                pnt.cI = cI;\n                pnt.rI = rI;\n                return tkn;\n            }\n        }\n        // Multiline comment.\n        for (let mc of blockComments) {\n            if (fwd.startsWith(mc.start)) {\n                let fwdlen = fwd.length;\n                let fI = mc.start.length;\n                let end = mc.end;\n                cI += mc.start.length;\n                let suffixLen = 0;\n                while (fI < fwdlen && !fwd.startsWith(end, fI)) {\n                    let n = commentSuffixMatch(fwd, fI, mc.suffixes);\n                    if (n > 0) {\n                        suffixLen = n;\n                        break;\n                    }\n                    n = commentSuffixFnMatch(lex, fI, mc.suffixFn);\n                    if (n > 0) {\n                        suffixLen = n;\n                        break;\n                    }\n                    if (cfg.line.rowChars[fwd[fI]]) {\n                        rI++;\n                        cI = 0;\n                    }\n                    cI++;\n                    fI++;\n                }\n                if (suffixLen > 0) {\n                    // Advance through the consumed suffix, tracking newlines.\n                    for (let k = 0; k < suffixLen; k++) {\n                        if (cfg.line.rowChars[fwd[fI + k]]) {\n                            rI++;\n                            cI = 0;\n                        }\n                        cI++;\n                    }\n                    let csrc = fwd.substring(0, fI + suffixLen);\n                    let tkn = lex.token('#CM', undefined, csrc, pnt);\n                    pnt.sI += csrc.length;\n                    pnt.rI = rI;\n                    pnt.cI = cI;\n                    return tkn;\n                }\n                if (fwd.startsWith(end, fI)) {\n                    cI += end.length;\n                    if (mc.eatline) {\n                        while (fI < fwdlen && cfg.line.chars[fwd[fI]]) {\n                            if (cfg.line.rowChars[fwd[fI]]) {\n                                rI++;\n                            }\n                            fI++;\n                        }\n                    }\n                    let csrc = fwd.substring(0, fI + end.length);\n                    let tkn = lex.token('#CM', undefined, csrc, pnt);\n                    pnt.sI += csrc.length;\n                    pnt.rI = rI;\n                    pnt.cI = cI;\n                    return tkn;\n                }\n                else {\n                    return lex.bad(utility_1.S.unterminated_comment, pnt.sI, pnt.sI + 9 * mc.start.length);\n                }\n            }\n        }\n    };\n};\nexports.makeCommentMatcher = makeCommentMatcher;\n// normalizeCommentSuffix splits the polymorphic suffix option value\n// (string | string[] | LexMatcher) into a length-sorted string list and\n// an optional LexMatcher probe. Empty/absent input yields empty outputs.\nfunction normalizeCommentSuffix(raw) {\n    if (null == raw) {\n        return { suffixes: undefined, suffixFn: undefined };\n    }\n    if ('function' === typeof raw) {\n        return { suffixes: undefined, suffixFn: raw };\n    }\n    let list = [];\n    if ('string' === typeof raw) {\n        if ('' !== raw)\n            list.push(raw);\n    }\n    else if (Array.isArray(raw)) {\n        for (let s of raw) {\n            if ('string' === typeof s && '' !== s)\n                list.push(s);\n        }\n    }\n    if (list.length > 1) {\n        list.sort((a, b) => b.length - a.length || (a < b ? -1 : a > b ? 1 : 0));\n    }\n    return {\n        suffixes: 0 === list.length ? undefined : list,\n        suffixFn: undefined,\n    };\n}\n// commentSuffixMatch returns the length of the best suffix match at\n// fwd[fI:] or 0 if none matches. Suffixes are pre-sorted longest-first,\n// so the first match is the best match.\nfunction commentSuffixMatch(fwd, fI, suffixes) {\n    if (!suffixes || 0 === suffixes.length)\n        return 0;\n    for (let s of suffixes) {\n        if (fwd.substring(fI, fI + s.length) === s)\n            return s.length;\n    }\n    return 0;\n}\n// commentSuffixFnMatch probes the LexMatcher-form suffix terminator at\n// offset fI. Returns the length of the returned token's src (to be\n// consumed) or 0 if no termination. The lex point is snapshotted and\n// restored so a misbehaving matcher can't advance the stream itself.\nfunction commentSuffixFnMatch(lex, fI, fn) {\n    if (!fn)\n        return 0;\n    let pnt = lex.pnt;\n    let savedSI = pnt.sI;\n    let savedRI = pnt.rI;\n    let savedCI = pnt.cI;\n    pnt.sI = savedSI + fI;\n    let tkn;\n    try {\n        tkn = fn(lex, undefined);\n    }\n    finally {\n        pnt.sI = savedSI;\n        pnt.rI = savedRI;\n        pnt.cI = savedCI;\n    }\n    if (null == tkn)\n        return 0;\n    return ('string' === typeof tkn.src) ? tkn.src.length : 0;\n}\n// Match text, checking for literal values, optionally followed by a fixed token.\n// Text strings are terminated by end markers.\nlet makeTextMatcher = (cfg, opts) => {\n    let ender = (0, utility_1.regexp)(cfg.line.lex ? null : 's', '^(.*?)', ...cfg.rePart.ender);\n    return function textMatcher(lex) {\n        if (cfg.text.check) {\n            let check = cfg.text.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let mcfg = cfg.text;\n        let pnt = lex.pnt;\n        let fwd = lex.fwd;\n        let def = cfg.value.def;\n        let defre = cfg.value.defre;\n        let m = fwd.match(ender);\n        if (m) {\n            let msrc = m[1];\n            let tsrc = m[2];\n            let out = undefined;\n            if (null != msrc) {\n                let mlen = msrc.length;\n                if (0 < mlen) {\n                    // Check for values first.\n                    let vs = undefined;\n                    if (cfg.value.lex) {\n                        // Fixed values (e.g true, false, null).\n                        if (undefined !== (vs = def[msrc])) {\n                            out = lex.token('#VL', vs.val, msrc, pnt);\n                            pnt.sI += mlen;\n                            pnt.cI += mlen;\n                        }\n                        // Regexp processed values.\n                        else {\n                            // defre is a name-sorted array (see cfg.value.defre build in\n                            // utility.ts) — iteration order is deterministic.\n                            for (let vspec of defre) {\n                                if (vspec.match) {\n                                    // If consume, assume regexp starts with ^.\n                                    let res = vspec.match.exec(vspec.consume ? fwd : msrc);\n                                    // Must match entire text.\n                                    if (res && (vspec.consume || res[0].length === msrc.length)) {\n                                        let remsrc = res[0];\n                                        if (null == vspec.val) {\n                                            out = lex.token('#VL', remsrc, remsrc, pnt);\n                                        }\n                                        else {\n                                            let val = vspec.val(res);\n                                            out = lex.token('#VL', val, remsrc, pnt);\n                                        }\n                                        pnt.sI += remsrc.length;\n                                        pnt.cI += remsrc.length;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    // Not a value, so plain text.\n                    // NOTEL if !text.lex then only values are matched.\n                    if (null == out && mcfg.lex) {\n                        out = lex.token('#TX', msrc, msrc, pnt);\n                        pnt.sI += mlen;\n                        pnt.cI += mlen;\n                    }\n                }\n            }\n            // A following fixed token can only match if there was already a\n            // valid text or value match.\n            if (out) {\n                out = subMatchFixed(lex, out, tsrc);\n            }\n            if (out && 0 < cfg.text.modify.length) {\n                const modify = cfg.text.modify;\n                for (let mI = 0; mI < modify.length; mI++) {\n                    out.val = modify[mI](out.val, lex, cfg, opts);\n                }\n            }\n            return out;\n        }\n    };\n};\nexports.makeTextMatcher = makeTextMatcher;\nlet makeNumberMatcher = (cfg, _opts) => {\n    let mcfg = cfg.number;\n    let ender = (0, utility_1.regexp)(null, [\n        '^([-+]?(0(',\n        [\n            mcfg.hex ? 'x[0-9a-fA-F_]+' : null,\n            mcfg.oct ? 'o[0-7_]+' : null,\n            mcfg.bin ? 'b[01_]+' : null,\n        ]\n            .filter((s) => null != s)\n            .join('|'),\n        // ')|[.0-9]+([0-9_]*[0-9])?)',\n        ')|\\\\.?[0-9]+([0-9_]*[0-9])?)',\n        '(\\\\.[0-9]?([0-9_]*[0-9])?)?',\n        '([eE][-+]?[0-9]+([0-9_]*[0-9])?)?',\n    ]\n        .join('')\n        .replace(/_/g, mcfg.sep ? (0, utility_1.escre)(mcfg.sepChar) : ''), ')', ...cfg.rePart.ender);\n    let numberSep = mcfg.sep\n        ? (0, utility_1.regexp)('g', (0, utility_1.escre)(mcfg.sepChar))\n        : undefined;\n    return function matchNumber(lex) {\n        mcfg = cfg.number;\n        if (!mcfg.lex)\n            return undefined;\n        if (cfg.number.check) {\n            let check = cfg.number.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let pnt = lex.pnt;\n        let fwd = lex.fwd;\n        let valdef = cfg.value.def;\n        let m = fwd.match(ender);\n        if (m) {\n            let msrc = m[1];\n            let tsrc = m[9]; // NOTE: count parens in numberEnder!\n            let out = undefined;\n            let included = true;\n            if (null != msrc &&\n                (included = !cfg.number.exclude || !msrc.match(cfg.number.exclude))) {\n                let mlen = msrc.length;\n                if (0 < mlen) {\n                    let vs = undefined;\n                    if (cfg.value.lex && undefined !== (vs = valdef[msrc])) {\n                        out = lex.token('#VL', vs.val, msrc, pnt);\n                    }\n                    else {\n                        let nstr = numberSep ? msrc.replace(numberSep, '') : msrc;\n                        let num = +nstr;\n                        // Special case: +- prefix of 0x... format\n                        if (isNaN(num)) {\n                            let first = nstr[0];\n                            if ('-' === first || '+' === first) {\n                                num = ('-' === first ? -1 : 1) * +nstr.substring(1);\n                            }\n                        }\n                        if (!isNaN(num)) {\n                            out = lex.token('#NR', num, msrc, pnt);\n                            pnt.sI += mlen;\n                            pnt.cI += mlen;\n                        }\n                        // Else let later matchers try.\n                    }\n                }\n            }\n            if (included) {\n                out = subMatchFixed(lex, out, tsrc);\n            }\n            return out;\n        }\n    };\n};\nexports.makeNumberMatcher = makeNumberMatcher;\nlet makeStringMatcher = (cfg, opts) => {\n    // TODO: does `clean` make sense here?\n    let os = opts.string || {};\n    cfg.string = cfg.string || {};\n    // TODO: compose with earlier config - do this in other makeFooMatchers?\n    cfg.string = (0, utility_1.deep)(cfg.string, {\n        lex: !!os?.lex,\n        quoteMap: (0, utility_1.charset)(os.chars),\n        multiChars: (0, utility_1.charset)(os.multiChars),\n        escMap: { ...os.escape },\n        escChar: os.escapeChar,\n        escCharCode: null == os.escapeChar ? undefined : os.escapeChar.charCodeAt(0),\n        allowUnknown: !!os.allowUnknown,\n        replaceCodeMap: (0, utility_1.omap)((0, utility_1.clean)({ ...os.replace }), ([c, r]) => [\n            c.charCodeAt(0),\n            r,\n        ]),\n        hasReplace: false,\n        abandon: !!os.abandon,\n    });\n    cfg.string.escMap = (0, utility_1.clean)(cfg.string.escMap);\n    cfg.string.hasReplace = 0 < (0, utility_1.keys)(cfg.string.replaceCodeMap).length;\n    return function stringMatcher(lex) {\n        let mcfg = cfg.string;\n        if (!mcfg.lex)\n            return undefined;\n        if (cfg.string.check) {\n            let check = cfg.string.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let { quoteMap, escMap, escChar, escCharCode, multiChars, allowUnknown, replaceCodeMap, hasReplace, } = mcfg;\n        let { pnt, src } = lex;\n        let { sI, rI, cI } = pnt;\n        let srclen = src.length;\n        if (quoteMap[src[sI]]) {\n            const q = src[sI]; // Quote character\n            const qI = sI;\n            const qrI = rI;\n            const isMultiLine = multiChars[q];\n            ++sI;\n            ++cI;\n            let s = [];\n            let rs;\n            for (sI; sI < srclen; sI++) {\n                cI++;\n                let c = src[sI];\n                rs = undefined;\n                // Quote char.\n                if (q === c) {\n                    sI++;\n                    break; // String finished.\n                }\n                // Escape char.\n                else if (escChar === c) {\n                    sI++;\n                    cI++;\n                    let es = escMap[src[sI]];\n                    if (null != es) {\n                        s.push(es);\n                    }\n                    // ASCII escape \\x**\n                    else if ('x' === src[sI]) {\n                        sI++;\n                        let cc = parseInt(src.substring(sI, sI + 2), 16);\n                        if (isNaN(cc)) {\n                            if (mcfg.abandon) {\n                                return undefined;\n                            }\n                            sI = sI - 2;\n                            cI -= 2;\n                            pnt.sI = sI;\n                            pnt.cI = cI;\n                            return lex.bad(utility_1.S.invalid_ascii, sI, sI + 4);\n                        }\n                        let us = String.fromCharCode(cc);\n                        s.push(us);\n                        sI += 1; // Loop increments sI.\n                        cI += 2;\n                    }\n                    // Unicode escape \\u**** and \\u{*****}.\n                    else if ('u' === src[sI]) {\n                        sI++;\n                        let ux = '{' === src[sI] ? (sI++, 1) : 0;\n                        let ulen = ux ? 6 : 4;\n                        let cc = parseInt(src.substring(sI, sI + ulen), 16);\n                        if (isNaN(cc)) {\n                            if (mcfg.abandon) {\n                                return undefined;\n                            }\n                            sI = sI - 2 - ux;\n                            cI -= 2;\n                            pnt.sI = sI;\n                            pnt.cI = cI;\n                            return lex.bad(utility_1.S.invalid_unicode, sI, sI + ulen + 2 + 2 * ux);\n                        }\n                        let us = String.fromCodePoint(cc);\n                        s.push(us);\n                        sI += ulen - 1 + ux; // Loop increments sI.\n                        cI += ulen + ux;\n                    }\n                    else if (allowUnknown) {\n                        s.push(src[sI]);\n                    }\n                    else {\n                        if (mcfg.abandon) {\n                            return undefined;\n                        }\n                        pnt.sI = sI;\n                        pnt.cI = cI - 1;\n                        return lex.bad(utility_1.S.unexpected, sI, sI + 1);\n                    }\n                }\n                else if (hasReplace &&\n                    undefined !== (rs = replaceCodeMap[src.charCodeAt(sI)])) {\n                    s.push(rs);\n                    cI++;\n                }\n                // Body part of string.\n                else {\n                    let bI = sI;\n                    // TODO: move to cfgx\n                    let qc = q.charCodeAt(0);\n                    let cc = src.charCodeAt(sI);\n                    while ((!hasReplace || undefined === (rs = replaceCodeMap[cc])) &&\n                        sI < srclen &&\n                        32 <= cc &&\n                        qc !== cc &&\n                        escCharCode !== cc) {\n                        cc = src.charCodeAt(++sI);\n                        cI++;\n                    }\n                    cI--;\n                    if (undefined === rs && cc < 32) {\n                        // TODO: move up - allow c < 32 to be a line char\n                        if (isMultiLine && cfg.line.chars[src[sI]]) {\n                            if (cfg.line.rowChars[src[sI]]) {\n                                pnt.rI = ++rI;\n                            }\n                            cI = 1;\n                            s.push(src.substring(bI, sI + 1));\n                        }\n                        else {\n                            if (mcfg.abandon) {\n                                return undefined;\n                            }\n                            pnt.sI = sI;\n                            pnt.cI = cI;\n                            return lex.bad(utility_1.S.unprintable, sI, sI + 1);\n                        }\n                    }\n                    else {\n                        s.push(src.substring(bI, sI));\n                        sI--;\n                    }\n                }\n            }\n            if (src[sI - 1] !== q || pnt.sI === sI - 1) {\n                if (mcfg.abandon) {\n                    return undefined;\n                }\n                pnt.rI = qrI;\n                return lex.bad(utility_1.S.unterminated_string, qI, sI);\n            }\n            const tkn = lex.token('#ST', s.join(types_1.EMPTY), src.substring(pnt.sI, sI), pnt);\n            pnt.sI = sI;\n            pnt.rI = rI;\n            pnt.cI = cI;\n            return tkn;\n        }\n    };\n};\nexports.makeStringMatcher = makeStringMatcher;\n// Line ending matcher.\nlet makeLineMatcher = (cfg, _opts) => {\n    return function matchLine(lex) {\n        if (!cfg.line.lex)\n            return undefined;\n        if (cfg.line.check) {\n            let check = cfg.line.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let { chars, rowChars } = cfg.line;\n        let { pnt, src } = lex;\n        let { sI, rI } = pnt;\n        let single = cfg.line.single;\n        let counts = undefined;\n        if (single) {\n            counts = {};\n        }\n        while (chars[src[sI]]) {\n            if (counts) {\n                counts[src[sI]] = (counts[src[sI]] || 0) + 1;\n                if (single) {\n                    if (1 < counts[src[sI]]) {\n                        break;\n                    }\n                }\n            }\n            rI += rowChars[src[sI]] ? 1 : 0;\n            sI++;\n        }\n        if (pnt.sI < sI) {\n            let msrc = src.substring(pnt.sI, sI);\n            const tkn = lex.token('#LN', undefined, msrc, pnt);\n            pnt.sI += msrc.length;\n            pnt.rI = rI;\n            pnt.cI = 1;\n            return tkn;\n        }\n    };\n};\nexports.makeLineMatcher = makeLineMatcher;\n// Space matcher.\nlet makeSpaceMatcher = (cfg, _opts) => {\n    return function spaceMatcher(lex) {\n        if (!cfg.space.lex)\n            return undefined;\n        if (cfg.space.check) {\n            let check = cfg.space.check(lex);\n            if (check && check.done) {\n                return check.token;\n            }\n        }\n        let { chars } = cfg.space;\n        let { pnt, src } = lex;\n        let { sI, cI } = pnt;\n        while (chars[src[sI]]) {\n            sI++;\n            cI++;\n        }\n        if (pnt.sI < sI) {\n            let msrc = src.substring(pnt.sI, sI);\n            const tkn = lex.token('#SP', undefined, msrc, pnt);\n            pnt.sI += msrc.length;\n            pnt.cI = cI;\n            return tkn;\n        }\n    };\n};\nexports.makeSpaceMatcher = makeSpaceMatcher;\nfunction subMatchFixed(lex, first, tsrc) {\n    let pnt = lex.pnt;\n    let out = first;\n    if (lex.cfg.fixed.lex && null != tsrc) {\n        let tknlen = tsrc.length;\n        if (0 < tknlen) {\n            let tkn = undefined;\n            let tin = lex.cfg.fixed.token[tsrc];\n            if (null != tin) {\n                tkn = lex.token(tin, undefined, tsrc, pnt);\n            }\n            if (null != tkn) {\n                pnt.sI += tkn.src.length;\n                pnt.cI += tkn.src.length;\n                if (null == first) {\n                    out = tkn;\n                }\n                else {\n                    pnt.token.push(tkn);\n                }\n            }\n        }\n    }\n    return out;\n}\nclass LexImpl {\n    refwd() {\n        this.fwd = this.src.substring(this.pnt.sI);\n        return this.fwd;\n    }\n    constructor(ctx) {\n        this.src = types_1.EMPTY;\n        this.ctx = {};\n        this.cfg = {};\n        this.pnt = makePoint(-1);\n        this.fwd = types_1.EMPTY;\n        this.ctx = ctx;\n        this.src = ctx.src();\n        this.cfg = ctx.cfg;\n        this.pnt = makePoint(this.src.length);\n    }\n    token(ref, val, src, pnt, use, why) {\n        let tin;\n        let name;\n        if ('string' === typeof ref) {\n            name = ref;\n            tin = (0, utility_1.tokenize)(name, this.cfg);\n        }\n        else {\n            tin = ref;\n            name = (0, utility_1.tokenize)(ref, this.cfg);\n        }\n        let tkn = makeToken(name, tin, val, src, pnt || this.pnt, use, why);\n        return tkn;\n    }\n    next(rule, alt, altI, tI) {\n        let tkn;\n        let pnt = this.pnt;\n        let sI = pnt.sI;\n        let match = undefined;\n        if (pnt.end) {\n            tkn = pnt.end;\n        }\n        else if (0 < pnt.token.length) {\n            tkn = pnt.token.shift();\n        }\n        else if (pnt.len <= pnt.sI) {\n            pnt.end = this.token('#ZZ', undefined, '', pnt);\n            tkn = pnt.end;\n        }\n        else {\n            this.fwd = this.src.substring(pnt.sI);\n            try {\n                for (let mat of this.cfg.lex.match) {\n                    if ((tkn = mat(this, rule, tI))) {\n                        match = mat;\n                        break;\n                    }\n                }\n            }\n            catch (err) {\n                tkn =\n                    tkn ||\n                        this.token('#BD', undefined, this.src[pnt.sI], pnt, { err }, err.code || utility_1.S.unexpected);\n            }\n            tkn =\n                tkn ||\n                    this.token('#BD', undefined, this.src[pnt.sI], pnt, undefined, utility_1.S.unexpected);\n        }\n        this.ctx.log &&\n            this.ctx.log(utility_1.S.lex, this.ctx, rule, this, pnt, sI, match, tkn, alt, altI, tI);\n        // this.ctx.log &&\n        //   log_lex(this.ctx, rule, this, pnt, sI, match, tkn, alt, altI, tI)\n        if (this.ctx.sub.lex) {\n            this.ctx.sub.lex.map((sub) => sub(tkn, rule, this.ctx));\n        }\n        return tkn;\n    }\n    tokenize(ref) {\n        return (0, utility_1.tokenize)(ref, this.cfg);\n    }\n    bad(why, pstart, pend) {\n        return this.token('#BD', undefined, 0 <= pstart && pstart <= pend\n            ? this.src.substring(pstart, pend)\n            : this.src[this.pnt.sI], undefined, undefined, why);\n    }\n}\nconst makeLex = (...params) => new LexImpl(...params);\nexports.makeLex = makeLex;\n//# sourceMappingURL=lexer.js.map"
  },
  {
    "path": "dist/parser.d.ts",
    "content": "import type { Config, Options, Parser, RuleDefiner, RuleSpec, RuleSpecMap, Jsonic } from './types';\nimport { makeRule, makeRuleSpec } from './rules';\ndeclare class ParserImpl implements Parser {\n    options: Options;\n    cfg: Config;\n    rsm: RuleSpecMap;\n    ji: Jsonic;\n    constructor(options: Options, cfg: Config, j: Jsonic);\n    rule(name?: string, define?: RuleDefiner | null): RuleSpec | RuleSpecMap | undefined;\n    start(src: string, jsonic: any, meta?: any, parent_ctx?: any): any;\n    clone(options: Options, config: Config, j: Jsonic): ParserImpl;\n    norm(): void;\n}\ndeclare const makeParser: (...params: ConstructorParameters<typeof ParserImpl>) => ParserImpl;\nexport { makeRule, makeRuleSpec, makeParser };\n"
  },
  {
    "path": "dist/parser.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2026 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.makeParser = exports.makeRuleSpec = exports.makeRule = void 0;\nconst types_1 = require(\"./types\");\nconst utility_1 = require(\"./utility\");\nconst error_1 = require(\"./error\");\nconst lexer_1 = require(\"./lexer\");\nconst rules_1 = require(\"./rules\");\nObject.defineProperty(exports, \"makeRule\", { enumerable: true, get: function () { return rules_1.makeRule; } });\nObject.defineProperty(exports, \"makeRuleSpec\", { enumerable: true, get: function () { return rules_1.makeRuleSpec; } });\n// Install ES getter/setter accessors on a Context so the legacy\n// `ctx.t0` / `ctx.t1` names proxy to `ctx.t[0]` / `ctx.t[1]`. Reading\n// an unfetched slot yields NOTOKEN; writing seeds the array slot.\nfunction defineLookaheadAliases(ctx, notoken) {\n    // Skip if already installed (a plain own-data property would be\n    // overwritten; an accessor matching ours is left alone).\n    const existing = Object.getOwnPropertyDescriptor(ctx, 't0');\n    if (existing && existing.get)\n        return;\n    Object.defineProperties(ctx, {\n        t0: {\n            configurable: true,\n            enumerable: true,\n            get() { return this.t[0] ?? notoken; },\n            set(v) { this.t[0] = v; },\n        },\n        t1: {\n            configurable: true,\n            enumerable: true,\n            get() { return this.t[1] ?? notoken; },\n            set(v) { this.t[1] = v; },\n        },\n        // v1 / v2 used to be plain data slots; keep them as accessors on\n        // the growing `v` stack so existing grammar code reads the same\n        // \"most-recently consumed\" tokens.\n        v1: {\n            configurable: true,\n            enumerable: true,\n            get() { return this.v[this.v.length - 1] ?? notoken; },\n            set(t) {\n                if (0 < this.v.length)\n                    this.v[this.v.length - 1] = t;\n                else\n                    this.v.push(t);\n            },\n        },\n        v2: {\n            configurable: true,\n            enumerable: true,\n            get() { return this.v[this.v.length - 2] ?? notoken; },\n            set(t) {\n                const L = this.v.length;\n                if (1 < L)\n                    this.v[L - 2] = t;\n                else if (1 === L)\n                    this.v.unshift(t);\n                else\n                    this.v.push(t);\n            },\n        },\n    });\n}\n// Rewind primitives. Attached to `ctx` at parse start so rule\n// actions can reach them via their `ctx` argument. `mark()` returns\n// an opaque absolute counter (ctx.vAbs); `rewind(mark)` replays the\n// tokens consumed since that mark by unshifting them back onto the\n// active lexer's pending-token queue, so subsequent `lex.next()`\n// calls re-serve them in forward order.\n//\n// Marks are absolute rather than array-relative so the ring-buffer\n// cap (options.rewind.history) can evict old tokens from the front\n// of ctx.v without invalidating mark values held by in-flight rule\n// actions. A rewind whose target has been evicted throws — the\n// caller's retained-history budget was too small for the grammar.\nfunction attachRewind(ctx) {\n    ctx.mark = function () {\n        return this.vAbs;\n    };\n    ctx.rewind = function (mark) {\n        const k = this.vAbs - mark;\n        if (k <= 0)\n            return;\n        if (k > this.v.length) {\n            throw new Error(`jsonic: ctx.rewind target ${mark} is outside the retained ` +\n                `history window (oldest mark available is ${this.vAbs - this.v.length}, ` +\n                `current is ${this.vAbs}); increase options.rewind.history.`);\n        }\n        const queue = this.lex.pnt.token;\n        const NOTOKEN = this.NOTOKEN;\n        // The lookahead buffer (ctx.t) holds tokens the lexer has already\n        // produced past the current consumed position but that haven't\n        // been committed to ctx.v yet. They advanced the lexer's sI — so\n        // if we just invalidated the buffer, those source chars would be\n        // lost. Preserve them by splicing into the front of the pending\n        // queue in the order the lexer produced them, BEHIND the rewound\n        // consumed tokens that come next.\n        const pendingLookahead = [];\n        for (let i = 0; i < this.t.length; i++) {\n            const tkn = this.t[i];\n            if (tkn && tkn !== NOTOKEN)\n                pendingLookahead.push(tkn);\n            this.t[i] = NOTOKEN;\n        }\n        // Un-shift pre-lexed lookahead (oldest-first order at the queue\n        // head), so the next lex.next() serves them in the same order they\n        // were originally produced.\n        for (let i = pendingLookahead.length - 1; i >= 0; i--) {\n            queue.unshift(pendingLookahead[i]);\n        }\n        // Then unshift the rewound consumed tokens — they go in FRONT of\n        // the lookahead, so the next lex.next() serves the oldest rewound\n        // consumed token first, then the rest in order.\n        for (let i = 0; i < k; i++) {\n            // Pop newest-first, unshift in that order — the first unshift\n            // lands the newest at the queue's head; the next unshift slides\n            // older tokens in front of it, so the queue reads oldest-first.\n            queue.unshift(this.v.pop());\n        }\n        this.vAbs -= k;\n        // Clear the lexer's cached end-of-source token so lex.next serves\n        // from the newly-replenished queue rather than short-circuiting\n        // to #ZZ. (Once the lexer has produced the end token it pins it\n        // to pnt.end; the rewound tokens would otherwise be unreachable.)\n        this.lex.pnt.end = undefined;\n    };\n}\nclass ParserImpl {\n    constructor(options, cfg, j) {\n        this.rsm = {};\n        this.options = options;\n        this.cfg = cfg;\n        this.ji = j;\n    }\n    // TODO: ensure chains properly, both for create and extend rule\n    // Multi-functional get/set for rules.\n    rule(name, define) {\n        // If no name, get all the rules.\n        if (null == name) {\n            return this.rsm;\n        }\n        // Else get a rule by name.\n        let rs = this.rsm[name];\n        // Else delete a specific rule by name.\n        if (null === define) {\n            delete this.rsm[name];\n        }\n        // Else add or redefine a rule by name.\n        else if (undefined !== define) {\n            rs = this.rsm[name] = this.rsm[name] || (0, rules_1.makeRuleSpec)(this.ji, this.cfg, {});\n            rs.name = name;\n            rs = this.rsm[name] = define(this.rsm[name], this) || this.rsm[name];\n            // Ensures jsonic.rule can chain\n            return undefined;\n        }\n        return rs;\n    }\n    start(src, jsonic, meta, parent_ctx) {\n        let root;\n        let endtkn = (0, lexer_1.makeToken)('#ZZ', (0, utility_1.tokenize)('#ZZ', this.cfg), undefined, types_1.EMPTY, (0, lexer_1.makePoint)(-1));\n        let notoken = (0, lexer_1.makeNoToken)();\n        let ctx = {\n            uI: 0,\n            opts: this.options,\n            cfg: this.cfg,\n            meta: meta || {},\n            src: () => src, // Avoid printing src\n            root: () => root,\n            plgn: () => jsonic.internal().plugins,\n            inst: () => jsonic,\n            rule: {},\n            sub: jsonic.internal().sub,\n            xs: -1,\n            // Consumed-token history. Legacy v1 / v2 accessors (installed by\n            // defineLookaheadAliases) read the top of this stack. ctx.vAbs\n            // is the absolute count of pushed-and-not-rewound tokens since\n            // parse start; ctx.mark() returns it so ring-buffer eviction of\n            // old entries doesn't invalidate outstanding marks.\n            v: [],\n            vAbs: 0,\n            // Lookahead buffer. Seeded with two NOTOKEN slots; grows as alts\n            // request deeper positions via ctx.t[i].\n            t: [notoken, notoken],\n            tC: -2, // Prepare count for lookahead (two slots preseeded above).\n            kI: -1,\n            rs: [],\n            rsI: 0,\n            rsm: this.rsm,\n            log: undefined,\n            F: (0, utility_1.srcfmt)(this.cfg),\n            u: {},\n            NOTOKEN: notoken,\n            NORULE: {},\n        };\n        // Legacy accessors: ctx.t0 / ctx.t1 proxy to ctx.t[0] / ctx.t[1].\n        defineLookaheadAliases(ctx, notoken);\n        ctx = (0, utility_1.deep)(ctx, parent_ctx);\n        // `deep` may return a fresh object (when parent_ctx is a plain\n        // object); re-install the accessors on the result so they survive.\n        defineLookaheadAliases(ctx, notoken);\n        let norule = (0, rules_1.makeNoRule)(this.ji, ctx);\n        ctx.NORULE = norule;\n        ctx.rule = norule;\n        // makelog(ctx, meta)\n        if (meta && utility_1.S.function === typeof meta.log) {\n            ctx.log = meta.log;\n        }\n        this.cfg.parse.prepare.forEach((prep) => prep(jsonic, ctx, meta));\n        // Special case - avoids extra per-token tests in main parser rules.\n        if ('' === src) {\n            if (this.cfg.lex.empty) {\n                return this.cfg.lex.emptyResult;\n            }\n            else {\n                throw new error_1.JsonicError(utility_1.S.unexpected, { src }, ctx.t0, norule, ctx);\n            }\n        }\n        let lex = (0, utility_1.badlex)((0, lexer_1.makeLex)(ctx), (0, utility_1.tokenize)('#BD', this.cfg), ctx);\n        ctx.lex = lex;\n        attachRewind(ctx);\n        let startspec = this.rsm[this.cfg.rule.start];\n        if (null == startspec) {\n            return undefined;\n        }\n        let rule = (0, rules_1.makeRule)(startspec, ctx);\n        root = rule;\n        // Maximum rule iterations (prevents infinite loops). Allow for\n        // rule open and close, and for each rule on each char to be\n        // virtual (like map, list), and double for safety margin (allows\n        // lots of backtracking), and apply a multipler option as a get-out-of-jail.\n        let maxr = 2 * (0, utility_1.keys)(this.rsm).length * lex.src.length * 2 * ctx.cfg.rule.maxmul;\n        // Process rules on tokens\n        let kI = 0;\n        // This loop is the heart of the engine. Keep processing rule\n        // occurrences until there's none left.\n        while (norule !== rule && kI < maxr) {\n            ctx.kI = kI;\n            ctx.rule = rule;\n            ctx.log && ctx.log(utility_1.S.step, ctx.kI + ':');\n            if (ctx.sub.rule) {\n                ctx.sub.rule.map((sub) => sub(rule, ctx));\n            }\n            rule = rule.process(ctx, lex);\n            ctx.log && ctx.log(utility_1.S.stack, ctx, rule, lex);\n            kI++;\n        }\n        // TODO: option to allow trailing content\n        if (endtkn.tin !== lex.next(rule).tin) {\n            throw new error_1.JsonicError(utility_1.S.unexpected, {}, ctx.t0, norule, ctx);\n        }\n        // NOTE: by returning root, we get implicit closing of maps and lists.\n        const result = ctx.root().node;\n        if (this.cfg.result.fail.includes(result)) {\n            throw new error_1.JsonicError(utility_1.S.unexpected, {}, ctx.t0, norule, ctx);\n        }\n        return result;\n    }\n    clone(options, config, j) {\n        let parser = new ParserImpl(options, config, j);\n        // Inherit rules from parent, filtered by config.rule\n        parser.rsm = Object.keys(this.rsm).reduce((a, rn) => ((a[rn] = (0, utility_1.filterRules)(this.rsm[rn], this.cfg)), a), {});\n        parser.norm();\n        return parser;\n    }\n    norm() {\n        (0, utility_1.values)(this.rsm).map((rs) => rs.norm());\n    }\n}\nconst makeParser = (...params) => new ParserImpl(...params);\nexports.makeParser = makeParser;\n//# sourceMappingURL=parser.js.map"
  },
  {
    "path": "dist/rules.d.ts",
    "content": "import type { AltSpec, Config, Context, FuncRef, FuncRefMap, Jsonic, Lex, ListMods, Rule, RuleSpec, RuleState, RuleStep, StateAction, Tin, Token } from './types';\ndeclare class RuleImpl implements Rule {\n    i: number;\n    name: string;\n    node: null;\n    state: RuleState;\n    n: any;\n    d: number;\n    u: any;\n    k: any;\n    bo: boolean;\n    ao: boolean;\n    bc: boolean;\n    ac: boolean;\n    oN: number;\n    cN: number;\n    spec: RuleSpec;\n    child: Rule;\n    parent: Rule;\n    prev: Rule;\n    next: Rule;\n    o: Token[];\n    c: Token[];\n    _NOTOKEN?: Token;\n    need: number;\n    constructor(spec: RuleSpec, ctx: Context, node?: any);\n    get o0(): Token;\n    set o0(v: Token);\n    get o1(): Token;\n    set o1(v: Token);\n    get c0(): Token;\n    set c0(v: Token);\n    get c1(): Token;\n    set c1(v: Token);\n    get os(): number;\n    set os(v: number);\n    get cs(): number;\n    set cs(v: number);\n    process(ctx: Context, lex: Lex): Rule;\n    eq(counter: string, limit?: number): boolean;\n    lt(counter: string, limit?: number): boolean;\n    gt(counter: string, limit?: number): boolean;\n    lte(counter: string, limit?: number): boolean;\n    gte(counter: string, limit?: number): boolean;\n    toString(): string;\n}\ndeclare const makeRule: (...params: ConstructorParameters<typeof RuleImpl>) => RuleImpl;\ndeclare const makeNoRule: (j: Jsonic, ctx: Context) => RuleImpl;\ndeclare class RuleSpecImpl implements RuleSpec {\n    name: string;\n    def: {\n        open: AltSpec[];\n        close: AltSpec[];\n        bo: StateAction[];\n        bc: StateAction[];\n        ao: StateAction[];\n        ac: StateAction[];\n        tcol: Tin[][][];\n        fnref: FuncRefMap<Function>;\n    };\n    cfg: Config;\n    ji: Jsonic;\n    constructor(j: Jsonic, cfg: Config, def: any);\n    tin<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R): T;\n    fnref(frm: Record<FuncRef, Function>): RuleSpec;\n    add(rs: RuleState, a: AltSpec | AltSpec[], mods?: ListMods): RuleSpec;\n    open(a: AltSpec | AltSpec[], mods?: ListMods): RuleSpec;\n    close(a: AltSpec | AltSpec[], mods?: ListMods): RuleSpec;\n    action(append: boolean, step: RuleStep, state: RuleState, action: StateAction): RuleSpec;\n    bo(append: StateAction | boolean | FuncRef, action?: StateAction): RuleSpec;\n    ao(append: StateAction | boolean, action?: StateAction): RuleSpec;\n    bc(append: StateAction | boolean, action?: StateAction): RuleSpec;\n    ac(append: StateAction | boolean, action?: StateAction): RuleSpec;\n    clear(): this;\n    norm(): this;\n    process(rule: Rule, ctx: Context, lex: Lex, state: RuleState): Rule;\n    bad(tkn: Token, rule: Rule, ctx: Context, parse: {\n        is_open: boolean;\n    }): Rule;\n    unknownRule(tkn: Token, name: string): Token;\n}\ndeclare const makeRuleSpec: (...params: ConstructorParameters<typeof RuleSpecImpl>) => RuleSpecImpl;\nexport { makeRule, makeNoRule, makeRuleSpec };\n"
  },
  {
    "path": "dist/rules.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2023 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.makeRuleSpec = exports.makeNoRule = exports.makeRule = void 0;\nconst types_1 = require(\"./types\");\nconst utility_1 = require(\"./utility\");\nconst error_1 = require(\"./error\");\nclass RuleImpl {\n    constructor(spec, ctx, node) {\n        this.i = -1;\n        this.name = types_1.EMPTY;\n        this.node = null;\n        this.state = types_1.OPEN;\n        this.n = Object.create(null);\n        this.d = -1;\n        this.u = Object.create(null);\n        this.k = Object.create(null);\n        this.bo = false;\n        this.ao = false;\n        this.bc = false;\n        this.ac = false;\n        this.oN = 0;\n        this.cN = 0;\n        this.need = 0;\n        this.i = ctx.uI++; // Rule ids are unique only to the parse run.\n        this.name = spec.name;\n        this.spec = spec;\n        this.child = ctx.NORULE;\n        this.parent = ctx.NORULE;\n        this.prev = ctx.NORULE;\n        this.next = ctx.NORULE;\n        this._NOTOKEN = ctx.NOTOKEN;\n        this.o = [];\n        this.c = [];\n        this.node = node;\n        this.d = ctx.rsI;\n        this.bo = null != spec.def.bo;\n        this.ao = null != spec.def.ao;\n        this.bc = null != spec.def.bc;\n        this.ac = null != spec.def.ac;\n    }\n    // Legacy aliases for o[0], o[1], c[0], c[1] and the count fields.\n    // Maintained so existing grammar/plugin code that reads r.o0/r.o1/r.os\n    // (and r.c0/r.c1/r.cs) continues to work unchanged.\n    get o0() { return this.o[0] ?? this._NOTOKEN; }\n    set o0(v) { this.o[0] = v; }\n    get o1() { return this.o[1] ?? this._NOTOKEN; }\n    set o1(v) { this.o[1] = v; }\n    get c0() { return this.c[0] ?? this._NOTOKEN; }\n    set c0(v) { this.c[0] = v; }\n    get c1() { return this.c[1] ?? this._NOTOKEN; }\n    set c1(v) { this.c[1] = v; }\n    get os() { return this.oN; }\n    set os(v) { this.oN = v; }\n    get cs() { return this.cN; }\n    set cs(v) { this.cN = v; }\n    process(ctx, lex) {\n        let rule = this.spec.process(this, ctx, lex, this.state);\n        return rule;\n    }\n    eq(counter, limit = 0) {\n        let value = this.n[counter];\n        return null == value || value === limit;\n    }\n    lt(counter, limit = 0) {\n        let value = this.n[counter];\n        return null == value || value < limit;\n    }\n    gt(counter, limit = 0) {\n        let value = this.n[counter];\n        return null == value || value > limit;\n    }\n    lte(counter, limit = 0) {\n        let value = this.n[counter];\n        return null == value || value <= limit;\n    }\n    gte(counter, limit = 0) {\n        let value = this.n[counter];\n        return null == value || value >= limit;\n    }\n    toString() {\n        return '[Rule ' + this.name + '~' + this.i + ']';\n    }\n}\nconst makeRule = (...params) => new RuleImpl(...params);\nexports.makeRule = makeRule;\nconst makeNoRule = (j, ctx) => makeRule(makeRuleSpec(j, ctx.cfg, {}), ctx);\nexports.makeNoRule = makeNoRule;\n// Parse-alternate match (built from current tokens and AltSpec).\nclass AltMatchImpl {\n    constructor() {\n        this.p = types_1.EMPTY; // Push rule (by name).\n        this.r = types_1.EMPTY; // Replace rule (by name).\n        this.b = 0; // Move token position backward.\n    }\n}\nconst makeAltMatch = (...params) => new AltMatchImpl(...params);\nconst PALT = makeAltMatch(); // Only one alt object is created.\nconst EMPTY_ALT = makeAltMatch();\nclass RuleSpecImpl {\n    constructor(j, cfg, def) {\n        this.name = types_1.EMPTY; // Set by Parser.rule\n        this.def = {\n            open: [],\n            close: [],\n            bo: [],\n            bc: [],\n            ao: [],\n            ac: [],\n            tcol: [],\n            fnref: {},\n        };\n        this.ji = j;\n        this.cfg = cfg;\n        this.def = Object.assign(this.def, def);\n        // Null Alt entries are allowed and ignored as a convenience.\n        this.def.open = (this.def.open || []).filter((alt) => null != alt);\n        this.def.close = (this.def.close || []).filter((alt) => null != alt);\n        for (let alt of this.def.open) {\n            normalt(alt, types_1.OPEN, this);\n        }\n        for (let alt of this.def.close) {\n            normalt(alt, types_1.CLOSE, this);\n        }\n        const anames = ['bo', 'ao', 'bc', 'ac'];\n        for (let an of anames) {\n            for (let sa of (this.def[an] ?? [])) {\n                if ('object' === typeof sa) {\n                    let sadef = sa;\n                    this[an](sadef.append, sadef.action);\n                }\n            }\n        }\n    }\n    // Convenience access to token Tins\n    tin(ref) {\n        return (0, utility_1.tokenize)(ref, this.cfg);\n    }\n    fnref(frm) {\n        Object.assign(this.def.fnref, frm);\n        // Auto-install reserved `@<rulename>-<phase>` handlers as state\n        // actions. Dedupe by function identity per phase: registering the\n        // same function twice (directly or via a later fnref() call that\n        // also passes it) installs only one action, but distinct functions\n        // for the same phase all install. This accommodates grammars that\n        // layer handlers (e.g. core sets a basic @list-bo, then a richer\n        // one replaces/augments it) while preventing the old bug where\n        // iterating the accumulated fnref map re-installed every\n        // previously-registered handler on every call.\n        const rn = this.name;\n        const fr = this.def.fnref;\n        const installed = (this.def.fnrefInstalled =\n            this.def.fnrefInstalled || new Map());\n        const reserved = [`@${rn}-bo`, `@${rn}-ao`, `@${rn}-bc`, `@${rn}-ac`];\n        for (let base of reserved) {\n            let phaseSet = installed.get(base);\n            if (!phaseSet)\n                installed.set(base, phaseSet = new WeakSet());\n            const aname = base.replace(/^[^-]+-/, '');\n            const prependFn = fr[base + '/prepend'];\n            const appendFn = fr[base + '/append'] ?? fr[base];\n            if (prependFn && !phaseSet.has(prependFn)) {\n                phaseSet.add(prependFn);\n                this[aname](false, prependFn);\n            }\n            if (appendFn && !phaseSet.has(appendFn)) {\n                phaseSet.add(appendFn);\n                this[aname](true, appendFn);\n            }\n        }\n        return this;\n    }\n    add(rs, a, mods) {\n        let inject = mods?.append ? 'push' : 'unshift';\n        let aa = ((0, utility_1.isarr)(a) ? a : [a])\n            .filter((alt) => null != alt && 'object' === typeof alt)\n            .map((a) => normalt(a, rs, this));\n        let altState = 'o' === rs ? 'open' : 'close';\n        let alts = this.def[altState];\n        alts[inject](...aa);\n        alts = this.def[altState] = (0, utility_1.modlist)(alts, mods);\n        (0, utility_1.filterRules)(this, this.cfg);\n        this.norm();\n        return this;\n    }\n    open(a, mods) {\n        return this.add('o', a, mods);\n    }\n    close(a, mods) {\n        return this.add('c', a, mods);\n    }\n    action(append, step, state, action) {\n        let actions = this.def[step + state];\n        if (append) {\n            actions.push(action);\n        }\n        else {\n            actions.unshift(action);\n        }\n        return this;\n    }\n    bo(append, action) {\n        return this.action(action ? !!append : true, types_1.BEFORE, types_1.OPEN, 'string' === typeof append ? this.def.fnref[append] :\n            (action ?? append));\n    }\n    ao(append, action) {\n        return this.action(action ? !!append : true, types_1.AFTER, types_1.OPEN, 'string' === typeof append ? this.def.fnref[append] :\n            (action ?? append));\n    }\n    bc(append, action) {\n        return this.action(action ? !!append : true, types_1.BEFORE, types_1.CLOSE, 'string' === typeof append ? this.def.fnref[append] :\n            (action ?? append));\n    }\n    ac(append, action) {\n        return this.action(action ? !!append : true, types_1.AFTER, types_1.CLOSE, 'string' === typeof append ? this.def.fnref[append] :\n            (action ?? append));\n    }\n    clear() {\n        this.def.open.length = 0;\n        this.def.close.length = 0;\n        this.def.bo.length = 0;\n        this.def.ao.length = 0;\n        this.def.bc.length = 0;\n        this.def.ac.length = 0;\n        return this;\n    }\n    norm() {\n        this.def.open.map((alt) => normalt(alt, types_1.OPEN, this));\n        this.def.close.map((alt) => normalt(alt, types_1.CLOSE, this));\n        // [stateI is o=0,c=1][tokenI is 0..maxS-1][tins]\n        const columns = [];\n        // Compute max lookahead depth declared across this rule's alts,\n        // per state. Generalizes the previous hard-coded 2-slot collation.\n        const maxS = (alts) => alts.reduce((m, a) => Math.max(m, a.sN || 0), 0);\n        const maxOpen = maxS(this.def.open);\n        const maxClose = maxS(this.def.close);\n        for (let tI = 0; tI < maxOpen; tI++) {\n            this.def.open.reduce(...collate(0, tI, columns));\n        }\n        for (let tI = 0; tI < maxClose; tI++) {\n            this.def.close.reduce(...collate(1, tI, columns));\n        }\n        // Ensure tcol[stateI] exists with enough slots so lexer.ts:264-268\n        // can always index `tcol[oc][tI]` safely for any tI the parser\n        // passes (bounded by this rule's own maxS).\n        columns[0] = columns[0] || [];\n        columns[1] = columns[1] || [];\n        for (let tI = 0; tI < maxOpen; tI++)\n            columns[0][tI] = columns[0][tI] || [];\n        for (let tI = 0; tI < maxClose; tI++)\n            columns[1][tI] = columns[1][tI] || [];\n        this.def.tcol = columns;\n        function collate(stateI, tokenI, columns) {\n            columns[stateI] = columns[stateI] || [];\n            let tins = (columns[stateI][tokenI] = columns[stateI][tokenI] || []);\n            return [\n                function (tins, alt) {\n                    let resolved = alt.t && alt.t[tokenI];\n                    if (resolved && 0 < resolved.length) {\n                        let newtins = [...new Set(tins.concat(resolved))];\n                        tins.length = 0;\n                        tins.push(...newtins);\n                    }\n                    return tins;\n                },\n                tins,\n            ];\n        }\n        return this;\n    }\n    process(rule, ctx, lex, state) {\n        ctx.log && ctx.log(utility_1.S.rule, ctx, rule, lex);\n        let is_open = state === 'o';\n        let next = is_open ? rule : ctx.NORULE;\n        let why = is_open ? 'O' : 'C';\n        let def = this.def;\n        // Match alternates for current state.\n        let alts = (is_open ? def.open : def.close);\n        // Handle \"before\" call.\n        let befores = is_open ? (rule.bo ? def.bo : null) : rule.bc ? def.bc : null;\n        if (befores) {\n            let bout = undefined;\n            for (let bI = 0; bI < befores.length; bI++) {\n                bout = befores[bI].call(this, rule, ctx, next, bout);\n                if (bout?.isToken && bout?.err) {\n                    return this.bad(bout, rule, ctx, { is_open });\n                }\n            }\n        }\n        // Attempt to match one of the alts.\n        let alt = 0 < alts.length ? parse_alts(is_open, alts, lex, rule, ctx) : EMPTY_ALT;\n        // Custom alt handler.\n        if (alt.h) {\n            alt = alt.h(rule, ctx, alt, next) || alt;\n            why += 'H';\n        }\n        // Unconditional error.\n        if (alt.e) {\n            return this.bad(alt.e, rule, ctx, { is_open });\n        }\n        // Update counters.\n        if (alt.n) {\n            for (let cn in alt.n) {\n                rule.n[cn] =\n                    // 0 reverts counter to 0.\n                    0 === alt.n[cn]\n                        ? 0\n                        : // First seen, set to 0.\n                            (null == rule.n[cn]\n                                ? 0\n                                : // Increment counter.\n                                    rule.n[cn]) + alt.n[cn];\n            }\n        }\n        // Set custom properties\n        if (alt.u) {\n            rule.u = Object.assign(rule.u, alt.u);\n        }\n        if (alt.k) {\n            rule.k = Object.assign(rule.k, alt.k);\n        }\n        // Record consumed tokens (matched minus backtrack) on the v\n        // history BEFORE running alt actions, so an action that calls\n        // ctx.rewind sees the just-matched tokens on top of the stack.\n        // The lookahead-buffer shift itself still happens at the end of\n        // process() so non-action paths behave identically.\n        //\n        // ctx.vAbs is an absolute monotonic counter used as the mark\n        // value — it's decoupled from ctx.v.length so the ring-buffer\n        // cap can evict old tokens from the front without invalidating\n        // outstanding marks (marks older than the retained window will\n        // simply fail at rewind time with a clear error).\n        const _cons = rule[is_open ? 'oN' : 'cN'] - (alt.b || 0);\n        if (0 < _cons) {\n            // Move consumed tokens from ctx.t → ctx.v. Clear the tbuf slots\n            // so a ctx.rewind call inside the subsequent alt action can\n            // distinguish \"token already in v\" (NOTOKEN here; will be\n            // replayed from v) from \"pre-lexed lookahead past consumed\"\n            // (real token in tbuf; needs re-queuing to preserve state).\n            const NOTOKEN = ctx.NOTOKEN;\n            for (let i = 0; i < _cons; i++) {\n                ctx.v.push(ctx.t[i]);\n                ctx.t[i] = NOTOKEN;\n            }\n            ;\n            ctx.vAbs += _cons;\n            // Amortised-O(1) ring-buffer cap: let v grow to twice the\n            // capacity, then splice its front back down. Batch-eviction\n            // makes each push O(1) on average even at the cap.\n            const cap = ctx.cfg.rewind.history;\n            if (cap !== Infinity && ctx.v.length > 2 * cap) {\n                ctx.v.splice(0, ctx.v.length - cap);\n            }\n        }\n        // TODO: move after rule.next resolution\n        // (breaks Expr! - fix first)\n        // Action call.\n        if (alt.a) {\n            why += 'A';\n            let tout = alt.a(rule, ctx, alt);\n            if (tout && tout.isToken && tout.err) {\n                return this.bad(tout, rule, ctx, { is_open });\n            }\n        }\n        // Push a new rule onto the stack...\n        if (alt.p) {\n            ctx.rs[ctx.rsI++] = rule;\n            let rulespec = ctx.rsm[alt.p];\n            if (rulespec) {\n                next = rule.child = makeRule(rulespec, ctx, rule.node);\n                next.parent = rule;\n                next.n = { ...rule.n };\n                if (0 < Object.keys(rule.k).length) {\n                    next.k = { ...rule.k };\n                }\n                why += 'P`' + alt.p + '`';\n            }\n            else {\n                return this.bad(this.unknownRule(ctx.t0, alt.p), rule, ctx, { is_open });\n            }\n        }\n        // ...or replace with a new rule.\n        else if (alt.r) {\n            let rulespec = ctx.rsm[alt.r];\n            if (rulespec) {\n                next = makeRule(rulespec, ctx, rule.node);\n                next.parent = rule.parent;\n                next.prev = rule;\n                next.n = { ...rule.n };\n                if (0 < Object.keys(rule.k).length) {\n                    next.k = { ...rule.k };\n                }\n                why += 'R`' + alt.r + '`';\n            }\n            else {\n                return this.bad(this.unknownRule(ctx.t0, alt.r), rule, ctx, { is_open });\n            }\n        }\n        // Pop closed rule off stack.\n        else if (!is_open) {\n            next = ctx.rs[--ctx.rsI] || ctx.NORULE;\n        }\n        // TODO: move action call here (alt.a)\n        // and set r.next = next, so that action has access to next\n        rule.next = next;\n        // Handle \"after\" call.\n        let afters = is_open ? (rule.ao ? def.ao : null) : rule.ac ? def.ac : null;\n        if (afters) {\n            let aout = undefined;\n            for (let aI = 0; aI < afters.length; aI++) {\n                aout = afters[aI](rule, ctx, next, aout);\n                if (aout?.isToken && aout?.err) {\n                    return this.bad(aout, rule, ctx, { is_open });\n                }\n            }\n        }\n        next.why = why;\n        ctx.log && ctx.log(utility_1.S.node, ctx, rule, lex, next);\n        // Must be last as state change is for next process call.\n        if (types_1.OPEN === rule.state) {\n            rule.state = types_1.CLOSE;\n        }\n        // Backtrack reduces consumed token count.\n        let consumed = rule[is_open ? 'oN' : 'cN'] - (alt.b || 0);\n        if (consumed < 0)\n            consumed = 0;\n        if (0 < consumed) {\n            // Shift the lookahead buffer left by `consumed` slots, filling\n            // vacated tail positions with NOTOKEN so later alts re-fetch.\n            // (The corresponding v-history push ran before alt actions.)\n            const L = ctx.t.length;\n            for (let i = 0; i < L - consumed; i++)\n                ctx.t[i] = ctx.t[i + consumed];\n            for (let i = Math.max(0, L - consumed); i < L; i++)\n                ctx.t[i] = ctx.NOTOKEN;\n        }\n        return next;\n    }\n    bad(tkn, rule, ctx, parse) {\n        throw new error_1.JsonicError(tkn.err || utility_1.S.unexpected, {\n            ...tkn.use,\n            state: parse.is_open ? utility_1.S.open : utility_1.S.close,\n        }, tkn, rule, ctx);\n    }\n    unknownRule(tkn, name) {\n        tkn.err = 'unknown_rule';\n        tkn.use = tkn.use || {};\n        tkn.use.rulename = name;\n        return tkn;\n    }\n}\nconst makeRuleSpec = (...params) => new RuleSpecImpl(...params);\nexports.makeRuleSpec = makeRuleSpec;\n// First match wins.\n// NOTE: input AltSpecs are used to build the Alt output.\nfunction parse_alts(is_open, alts, lex, rule, ctx) {\n    let out = PALT;\n    out.b = 0; // Backtrack n tokens.\n    out.p = types_1.EMPTY; // Push named rule onto stack.\n    out.r = types_1.EMPTY; // Replace current rule with named rule.\n    out.n = undefined; // Increment named counters.\n    out.h = undefined; // Custom handler function.\n    out.a = undefined; // Rule action.\n    out.u = undefined; // Custom rule properties.\n    out.k = undefined; // Custom rule properties (propagated).\n    out.e = undefined; // Error token.\n    let alt = null;\n    let altI = 0;\n    let t = ctx.cfg.t;\n    let cond = true;\n    let bitAA = 1 << (t.AA - 1);\n    let IGNORE = ctx.cfg.tokenSetTins.IGNORE;\n    function next(r, alt, altI, tI) {\n        let tkn;\n        do {\n            tkn = lex.next(r, alt, altI, tI);\n            ctx.tC++;\n        } while (IGNORE[tkn.tin]);\n        return tkn;\n    }\n    // TODO: replace with lookup map\n    let len = alts.length;\n    const NOTOKEN = ctx.NOTOKEN;\n    const tbuf = ctx.t;\n    for (altI = 0; altI < len; altI++) {\n        alt = alts[altI];\n        // Number of positions that matched in this alt. Tracked so the\n        // rule can record exactly which tokens it consumed.\n        let matched = 0;\n        cond = true;\n        const S = alt.S;\n        const sN = alt.sN | 0;\n        // Iterate alt's lookahead positions. Each position is fetched\n        // lazily and only when the previous position matched, preserving\n        // the original 2-slot lazy behaviour for any N.\n        //\n        // A null entry in S[i] means \"no Tin constraint at this position\"\n        // (wildcard) - the token is still fetched and consumed, but the\n        // bit-field check is skipped. This matches the `s` docstring\n        // (\"null if position matches any token\") and prevents silently\n        // dropping the check at a later required position.\n        for (let i = 0; i < sN; i++) {\n            let tkn = tbuf[i];\n            if (null == tkn || NOTOKEN === tkn) {\n                tkn = tbuf[i] = next(rule, alt, altI, i);\n            }\n            const Si = S ? S[i] : null;\n            if (null != Si) {\n                const tin = tkn.tin;\n                const part = (tin / 31) | 0;\n                // bitAA lives in partition 0 (tin=AA=4). ORing it into the\n                // match mask for any partition other than 0 lets unrelated\n                // tokens in higher partitions collide with alts that merely\n                // set bit 3 of their own partition — a false positive. Apply\n                // bitAA only when testing a partition-0 token.\n                const aaBit = part === 0 ? bitAA : 0;\n                if (!(Si[part] & ((1 << ((tin % 31) - 1)) | aaBit))) {\n                    cond = false;\n                    break;\n                }\n            }\n            matched = i + 1;\n        }\n        if (is_open) {\n            rule.oN = matched;\n            for (let i = 0; i < matched; i++)\n                rule.o[i] = tbuf[i];\n            // Clear trailing slots so stale matches from earlier alts are\n            // not observed via rule.o[i] / rule.o0 / rule.o1 accessors.\n            for (let i = matched; i < rule.o.length; i++)\n                rule.o[i] = NOTOKEN;\n        }\n        else {\n            rule.cN = matched;\n            for (let i = 0; i < matched; i++)\n                rule.c[i] = tbuf[i];\n            for (let i = matched; i < rule.c.length; i++)\n                rule.c[i] = NOTOKEN;\n        }\n        // Optional custom condition\n        if (cond && alt.c) {\n            cond = cond && alt.c(rule, ctx, out);\n        }\n        if (cond) {\n            break;\n        }\n        else {\n            alt = null;\n        }\n    }\n    if (!cond) {\n        out.e = tbuf[0] ?? NOTOKEN;\n    }\n    if (alt) {\n        out.n = null != alt.n ? alt.n : out.n;\n        out.h = null != alt.h ? alt.h : out.h;\n        out.a = null != alt.a ? alt.a : out.a;\n        out.u = null != alt.u ? alt.u : out.u;\n        out.k = null != alt.k ? alt.k : out.k;\n        out.g = null != alt.g ? alt.g : out.g;\n        out.e = (alt.e && alt.e(rule, ctx, out)) || undefined;\n        out.p =\n            null != alt.p && false !== alt.p\n                ? 'string' === typeof alt.p\n                    ? alt.p\n                    : alt.p(rule, ctx, out)\n                : out.p;\n        out.r =\n            null != alt.r && false !== alt.r\n                ? 'string' === typeof alt.r\n                    ? alt.r\n                    : alt.r(rule, ctx, out)\n                : out.r;\n        out.b =\n            null != alt.b && false !== alt.b\n                ? 'number' === typeof alt.b\n                    ? alt.b\n                    : alt.b(rule, ctx, out)\n                : out.b;\n    }\n    let match = altI < alts.length;\n    ctx.log && ctx.log(utility_1.S.parse, ctx, rule, lex, match, cond, altI, alt, out);\n    return out;\n}\nconst partify = (tins, part) => tins.filter((tin) => 31 * part <= tin && tin < 31 * (part + 1));\nconst bitify = (s, part) => s.reduce((bits, tin) => (1 << (tin - (31 * part + 1))) | bits, 0);\n// Valid group-tag pattern: lowercase letter followed by one or more\n// lowercase letters, digits, or hyphens. Enforced by normalt().\nconst GROUP_TAG_RE = /^[a-z][a-z0-9-]+$/;\n// Normalize AltSpec (mutates).\nfunction normalt(a, rs, r) {\n    // Ensure groups are a string[]\n    if (types_1.STRING === typeof a.g) {\n        a.g = a.g.split(/\\s*,\\s*/);\n    }\n    else if (null == a.g) {\n        a.g = [];\n    }\n    // Validate every group tag (reject empty and non-matching tags).\n    for (let tag of a.g) {\n        if (!GROUP_TAG_RE.test(tag)) {\n            throw new Error(`Grammar: invalid group tag \"${tag}\" ` +\n                `in rule ${r.name} (${rs}) — must match ${GROUP_TAG_RE}`);\n        }\n    }\n    a.g = a.g.sort();\n    const aa = a;\n    if (!a.s || 0 === a.s.length) {\n        a.s = null;\n        aa.t = [];\n        aa.S = null;\n        aa.sN = 0;\n    }\n    else {\n        const tinsify = (s) => {\n            const tins = s\n                .flat()\n                .map((n) => 'string' === typeof n ? n.split(/\\s* +\\s*/) : n)\n                .flat()\n                .map((n) => 'string' === typeof n ? (r.ji.tokenSet(n) ?? r.ji.token(n)) : n)\n                .flat()\n                .filter((tin) => 'number' === typeof tin);\n            return tins;\n        };\n        if ('string' === typeof a.s) {\n            a.s = a.s.split(/\\s* +\\s*/);\n        }\n        // Per-position resolved tins and bit-field match tables.\n        // alt.t[i] holds the Tin[] for position i (used by tcol collation);\n        // alt.S[i] holds the bit-packed lookup (null if position is empty,\n        // which should not normally occur - tinsify filters nulls).\n        const sN = a.s.length;\n        const t = new Array(sN);\n        const S = new Array(sN);\n        for (let i = 0; i < sN; i++) {\n            const tins = tinsify([a.s[i]]);\n            t[i] = tins;\n            // `#AA` is the ANY wildcard — a position whose tin list\n            // includes it must match every lexed token regardless of\n            // partition. Represent that by dropping to the existing\n            // `S[i] = null` sentinel (\"no constraint\"), bypassing the\n            // per-partition bitset check in parse_alts. The t[i] entry\n            // keeps the raw tin list so tcol collation still reflects\n            // what the user wrote.\n            const aaTin = r.ji.token('#AA');\n            if (aaTin != null && tins.includes(aaTin)) {\n                S[i] = null;\n                continue;\n            }\n            S[i] =\n                0 < tins.length\n                    ? new Array(Math.max(...tins.map((tin) => (1 + tin / 31) | 0)))\n                        .fill(null)\n                        .map((_, j) => j)\n                        .map((part) => bitify(partify(tins, part), part))\n                    : null;\n        }\n        aa.t = t;\n        aa.S = S;\n        aa.sN = sN;\n    }\n    if (!a.p) {\n        a.p = null;\n    }\n    else {\n        resolveFunctionRef('push', rs, r, a, 'p');\n    }\n    if (!a.r) {\n        a.r = null;\n    }\n    else {\n        resolveFunctionRef('replace', rs, r, a, 'r');\n    }\n    if (!a.b) {\n        a.b = null;\n    }\n    else {\n        resolveFunctionRef('back', rs, r, a, 'b');\n    }\n    if (!a.a) {\n        a.a = null;\n    }\n    else {\n        resolveFunctionRef('action', rs, r, a, 'a');\n    }\n    if (!a.h) {\n        a.h = null;\n    }\n    else {\n        resolveFunctionRef('modify', rs, r, a, 'h');\n    }\n    if (!a.e) {\n        a.e = null;\n    }\n    else {\n        resolveFunctionRef('error', rs, r, a, 'e');\n    }\n    if (!a.c) {\n        a.c = null;\n    }\n    else {\n        const ct = typeof a.c;\n        if ('string' === ct) {\n            resolveFunctionRef('condition', rs, r, a, 'c');\n        }\n        else if ('function' === ct) {\n            if ('c' === a.c.name) {\n                (0, utility_1.defprop)(a.c, 'name', { value: 'ruleCond' });\n            }\n        }\n        else if ('object' === ct) {\n            const ac = a.c;\n            const conds = [];\n            const ruleprops = Object.keys(a.c);\n            for (let prop of ruleprops) {\n                const pspec = ac[prop];\n                if (null != pspec) {\n                    if ('object' === typeof pspec) {\n                        for (let co of Object.keys(pspec)) {\n                            if (1 === COND_OPS[co]) {\n                                conds.push(makeRuleCond(co, prop, pspec[co]));\n                            }\n                        }\n                    }\n                    else {\n                        conds.push(makeRuleCond('$eq', prop, pspec));\n                    }\n                }\n            }\n            if (0 === conds.length) {\n                delete a.c;\n            }\n            else if (1 === conds.length) {\n                a.c = conds[0];\n            }\n            else {\n                a.c = function conjunctCond(r, c, a) {\n                    for (let cond of conds) {\n                        let pass = cond(r, c, a);\n                        if (false == pass) {\n                            return false;\n                        }\n                    }\n                    return true;\n                };\n            }\n        }\n        else {\n            throw new Error('Grammar: invalid condition: ' + a.c);\n        }\n    }\n    return a;\n}\nfunction isfnref(v) {\n    return 'string' === typeof v && v.startsWith('@');\n}\nfunction resolveFunctionRef(fkind, rs, r, a, k) {\n    const val = a[k];\n    if (isfnref(val)) {\n        const func = r.def.fnref[val];\n        if (null == func) {\n            throw new Error(`Grammar: unknown ${fkind} function reference: ` + val +\n                ` for rule ${r.name} (${rs}) and alt ${a.s} (${a.g})`);\n        }\n        a[k] = func;\n    }\n}\nconst COND_OPS = {\n    $eq: 1,\n    $ne: 1,\n    $lt: 1,\n    $lte: 1,\n    $gt: 1,\n    $gte: 1,\n};\nfunction makeRuleCond(co, prop, val) {\n    const path = prop.split('.');\n    if ('$eq' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return rval === val;\n        };\n    }\n    else if ('$ne' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return rval != val;\n        };\n    }\n    else if ('$lt' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return null == rval || rval < val;\n        };\n    }\n    else if ('$lte' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return null == rval || rval <= val;\n        };\n    }\n    else if ('$gt' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return null == rval || rval > val;\n        };\n    }\n    else if ('$gte' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return null == rval || rval >= val;\n        };\n    }\n    else if ('$exist' === co) {\n        return function ruleCond(r, _c, _a) {\n            const rval = (0, utility_1.getpath)(r, path);\n            return true === val ? null != rval : null == rval;\n        };\n    }\n    else {\n        throw new Error('Grammer: unknown comparison operator: ' + co);\n    }\n}\n//# sourceMappingURL=rules.js.map"
  },
  {
    "path": "dist/self.d.ts",
    "content": "import { Plugin } from './jsonic';\ndeclare let Self: Plugin;\nexport { Self };\n"
  },
  {
    "path": "dist/self.js",
    "content": "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.Self = void 0;\n//  See aontu lang.ts\n//\n//  plugin: aontu: {\n//\n//    rule: val: open: '& :': {\n//      push: map\n//      back: 2\n//      num: pk: 1\n//      g: aontu_spread \n//    } \n//    \n//    rule: val: close: '& :' {\n//      back: 2\n//      g: aontu_spread,json,more \n//    } \n//    \n//    rule: map: open: '#CJ #CL' {\n//      push: pair\n//      back: 2\n//    }\n//    \n//    rule: map: close: '#CJ #CL' {\n//      push: pair\n//      back: 2\n//    }\n//    \n//    rule: map: pair: '#CJ #CL' {\n//      push: val\n//      user: spread: true\n//    } \n//    \n//    rule: map: pair: '#KEY ?' {\n//      replace: pair\n//      user: aontu_optional: true\n//    } \n//    \n//    rule: map: pair: '? :' {\n//      push: val\n//      user: pair: true\n//      cond: { prev.user.aontu_optional: true }\n//      act: pair_optional\n//    } \n//\n//  }\n//\n//\n// cond:\n// [] - or\n// {} - and, keys select, vals match\nlet Self = function self(jsonic) {\n};\nexports.Self = Self;\n//# sourceMappingURL=self.js.map"
  },
  {
    "path": "dist/tsconfig.tsbuildinfo",
    "content": "{\"root\":[\"../src/bnf.ts\",\"../src/debug.ts\",\"../src/defaults.ts\",\"../src/error.ts\",\"../src/grammar.ts\",\"../src/jsonic-bnf-cli.ts\",\"../src/jsonic-cli.ts\",\"../src/jsonic.ts\",\"../src/lexer.ts\",\"../src/parser.ts\",\"../src/rules.ts\",\"../src/self.ts\",\"../src/types.ts\",\"../src/utility.ts\"],\"version\":\"6.0.3\"}"
  },
  {
    "path": "dist/types.d.ts",
    "content": "export declare const OPEN: RuleState;\nexport declare const CLOSE: RuleState;\nexport declare const BEFORE: RuleStep;\nexport declare const AFTER: RuleStep;\nexport declare const EMPTY = \"\";\nexport declare const INSPECT: unique symbol;\nexport declare const SKIP: unique symbol;\nexport declare const STRING = \"string\";\nexport type JsonicParse = (src: any, meta?: any, parent_ctx?: any) => any;\nexport interface JsonicAPI {\n    parse: JsonicParse;\n    options: Options & ((change_options?: Bag | string) => Bag);\n    config: () => Config;\n    make: (options?: Options | string) => Jsonic;\n    use: (plugin: Plugin, plugin_options?: Bag) => Jsonic;\n    rule: (name?: string, define?: RuleDefiner | null) => Jsonic | RuleSpec | RuleSpecMap;\n    empty: (options?: Options) => Jsonic;\n    token: TokenMap & TinMap & (<A extends string | Tin>(ref: A) => A extends string ? Tin : string);\n    tokenSet: TokenSetMap & TinSetMap & (<A extends string | Tin>(ref: A) => A extends string ? Tin[] : string);\n    fixed: TokenMap & TinMap & (<A extends string | Tin>(ref: A) => undefined | (A extends string ? Tin : string));\n    id: string;\n    toString: () => string;\n    sub: (spec: {\n        lex?: LexSub;\n        rule?: RuleSub;\n    }) => Jsonic;\n    util: Bag;\n    grammar: (gs: GrammarSpec | string, setting?: GrammarSetting) => void;\n    bnf: ((src: string, opts?: BnfConvertOptions) => GrammarSpec) & {\n        toSpec: (src: string, opts?: BnfConvertOptions) => GrammarSpec;\n    };\n}\nexport type BnfConvertOptions = {\n    start?: string;\n    tag?: string;\n};\nexport type GrammarSetting = {\n    rule?: {\n        alt?: {\n            g?: string | string[];\n        };\n    };\n};\nexport type Jsonic = JsonicParse & // A function that parses.\nJsonicAPI & {\n    [prop: string]: any;\n};\nexport type Plugin = ((jsonic: Jsonic, plugin_options?: any) => void | Jsonic) & {\n    defaults?: Bag;\n    options?: Bag;\n};\nexport type Options = {\n    safe?: {\n        key: boolean;\n    };\n    tag?: string;\n    fixed?: {\n        lex?: boolean;\n        token?: StrMap;\n        check?: LexCheck;\n    };\n    match?: {\n        lex?: boolean;\n        token?: {\n            [name: string]: RegExp | LexMatcher;\n        };\n        value?: {\n            [name: string]: {\n                match: RegExp | LexMatcher;\n                val?: any;\n            };\n        };\n        check?: LexCheck;\n    };\n    tokenSet?: {\n        [name: string]: string[];\n    };\n    space?: {\n        lex?: boolean;\n        chars?: string;\n        check?: LexCheck;\n    };\n    line?: {\n        lex?: boolean;\n        chars?: string;\n        rowChars?: string;\n        single?: boolean;\n        check?: LexCheck;\n    };\n    text?: {\n        lex?: boolean;\n        modify?: ValModifier | ValModifier[];\n        check?: LexCheck;\n    };\n    number?: {\n        lex?: boolean;\n        hex?: boolean;\n        oct?: boolean;\n        bin?: boolean;\n        sep?: string | null;\n        exclude?: RegExp;\n        check?: LexCheck;\n    };\n    comment?: {\n        lex?: boolean;\n        def?: {\n            [name: string]: {\n                line?: boolean;\n                start?: string;\n                end?: string;\n                lex?: boolean;\n                suffix?: string | string[] | LexMatcher;\n                eatline: boolean;\n            } | null | undefined | false;\n        };\n        check?: LexCheck;\n    };\n    string?: {\n        lex?: boolean;\n        chars?: string;\n        multiChars?: string;\n        escapeChar?: string;\n        escape?: {\n            [char: string]: string | null;\n        };\n        allowUnknown?: boolean;\n        replace?: {\n            [char: string]: string | null;\n        };\n        abandon?: boolean;\n        check?: LexCheck;\n    };\n    map?: {\n        extend?: boolean;\n        merge?: (prev: any, curr: any) => any;\n        child?: boolean;\n    };\n    list?: {\n        property: boolean;\n        pair: boolean;\n        child: boolean;\n    };\n    info?: {\n        map?: boolean;\n        list?: boolean;\n        text?: boolean;\n        marker?: string;\n    };\n    value?: {\n        lex?: boolean;\n        def?: {\n            [src: string]: undefined | null | false | {\n                val: any;\n                match?: RegExp;\n                consume?: boolean;\n            };\n        };\n    };\n    ender?: string | string[];\n    plugin?: Bag;\n    debug?: {\n        get_console?: () => any;\n        maxlen?: number;\n        print?: {\n            config?: boolean;\n            src?: (x: any) => string;\n        };\n    };\n    error?: {\n        [code: string]: string;\n    };\n    errmsg?: {\n        name?: string;\n        suffix?: boolean | string | ((color?: any, spec?: any) => string);\n    };\n    hint?: any;\n    lex?: {\n        empty?: boolean;\n        emptyResult?: any;\n        match: {\n            [name: string]: {\n                order: number;\n                make: MakeLexMatcher;\n            };\n        };\n    };\n    parse?: {\n        prepare?: {\n            [name: string]: ParsePrepare;\n        };\n    };\n    rule?: {\n        start?: string;\n        finish?: boolean;\n        maxmul?: number;\n        include?: string;\n        exclude?: string;\n    };\n    result?: {\n        fail: any[];\n    };\n    rewind?: {\n        history?: number;\n    };\n    config?: {\n        modify?: {\n            [plugin_name: string]: (config: Config, options: Options) => void;\n        };\n    };\n    parser?: {\n        start?: (lexer: any, src: string, jsonic: any, meta?: any, parent_ctx?: any) => any;\n    };\n    standard$?: boolean;\n    defaults$?: boolean;\n    grammar$?: boolean;\n    color?: {\n        active?: boolean;\n        reset?: string;\n        hi?: string;\n        lo?: string;\n        line?: string;\n    };\n};\nexport interface RuleSpec {\n    name: string;\n    def: {\n        open: AltSpec[];\n        close: AltSpec[];\n        bo: StateAction[];\n        bc: StateAction[];\n        ao: StateAction[];\n        ac: StateAction[];\n        tcol: Tin[][][];\n        fnref: FuncRefMap<AltNext | AltBack | AltCond | AltModifier | AltError>;\n    };\n    ji: Jsonic;\n    tin<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R): T;\n    fnref(frm: Record<FuncRef, Function>): RuleSpec;\n    add(state: RuleState, a: AltSpec | AltSpec[], flags: any): RuleSpec;\n    open(a: AltSpec | AltSpecish[], flags?: any): RuleSpec;\n    close(a: AltSpec | AltSpecish[], flags?: any): RuleSpec;\n    action(prepend: boolean, step: RuleStep, state: RuleState, action: StateAction): RuleSpec;\n    bo(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec;\n    ao(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec;\n    bc(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec;\n    ac(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec;\n    clear(): RuleSpec;\n    norm(): RuleSpec;\n    process(rule: Rule, ctx: Context, lex: Lex, state: RuleState): Rule;\n    bad(tkn: Token, rule: Rule, ctx: Context, parse: {\n        is_open: boolean;\n    }): Rule;\n}\nexport interface Rule {\n    i: number;\n    name: string;\n    spec: RuleSpec;\n    node: any;\n    state: RuleState;\n    child: Rule;\n    parent: Rule;\n    prev: Rule;\n    next: Rule;\n    o: Token[];\n    oN: number;\n    c: Token[];\n    cN: number;\n    os: number;\n    o0: Token;\n    o1: Token;\n    cs: number;\n    c0: Token;\n    c1: Token;\n    n: Counters;\n    d: number;\n    u: Bag;\n    k: Bag;\n    bo: boolean;\n    ao: boolean;\n    bc: boolean;\n    ac: boolean;\n    why?: string;\n    need: number;\n    process(ctx: Context, lex: Lex): Rule;\n    eq(counter: string, limit?: number): boolean;\n    lt(counter: string, limit?: number): boolean;\n    gt(counter: string, limit?: number): boolean;\n    lte(counter: string, limit?: number): boolean;\n    gte(counter: string, limit?: number): boolean;\n}\nexport type Context = {\n    uI: number;\n    opts: Options;\n    cfg: Config;\n    meta: Bag;\n    src: () => string;\n    root: () => any;\n    plgn: () => Plugin[];\n    inst: () => Jsonic;\n    rule: Rule;\n    sub: {\n        lex?: LexSub[];\n        rule?: RuleSub[];\n    };\n    xs: Tin;\n    v: Token[];\n    vAbs: number;\n    v2: Token;\n    v1: Token;\n    t: Token[];\n    mark: () => number;\n    rewind: (mark: number) => void;\n    t0: Token;\n    t1: Token;\n    tC: number;\n    kI: number;\n    rs: Rule[];\n    rsI: number;\n    rsm: {\n        [name: string]: RuleSpec;\n    };\n    log?: (...rest: any) => void;\n    F: (s: any) => string;\n    u: Bag;\n    NOTOKEN: Token;\n    NORULE: Rule;\n};\nexport interface Lex {\n    src: String;\n    ctx: Context;\n    cfg: Config;\n    pnt: Point;\n    fwd: string;\n    refwd(): string;\n    token(ref: Tin | string, val: any, src: string, pnt?: Point, use?: any, why?: string): Token;\n    next(rule: Rule, alt?: NormAltSpec, altI?: number, tI?: number): Token;\n    tokenize<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R): T;\n    bad(why: string, pstart: number, pend: number): Token;\n}\nexport type NextToken = (rule: Rule) => Token;\nexport type Config = {\n    safe: {\n        key: boolean;\n    };\n    lex: {\n        match: LexMatcher[];\n        empty: boolean;\n        emptyResult: any;\n    };\n    parse: {\n        prepare: ParsePrepare[];\n    };\n    rule: {\n        start: string;\n        maxmul: number;\n        finish: boolean;\n        include: string[];\n        exclude: string[];\n    };\n    fixed: {\n        lex: boolean;\n        token: TokenMap;\n        ref: Record<string | Tin, Tin | string>;\n        check?: LexCheck;\n    };\n    match: {\n        lex: boolean;\n        value: {\n            [name: string]: {\n                match: RegExp | LexMatcher;\n                val?: any;\n            };\n        };\n        token: MatchMap;\n        check?: LexCheck;\n    };\n    tokenSet: TokenSetMap;\n    tokenSetTins: {\n        [name: string]: {\n            [tin: number]: boolean;\n        };\n    };\n    space: {\n        lex: boolean;\n        chars: Chars;\n        check?: LexCheck;\n    };\n    line: {\n        lex: boolean;\n        chars: Chars;\n        rowChars: Chars;\n        single: boolean;\n        check?: LexCheck;\n    };\n    text: {\n        lex: boolean;\n        modify: ValModifier[];\n        check?: LexCheck;\n    };\n    number: {\n        lex: boolean;\n        hex: boolean;\n        oct: boolean;\n        bin: boolean;\n        sep: boolean;\n        exclude?: RegExp;\n        sepChar?: string | null;\n        check?: LexCheck;\n    };\n    string: {\n        lex: boolean;\n        quoteMap: Chars;\n        escMap: Bag;\n        escChar?: string;\n        escCharCode?: number;\n        multiChars: Chars;\n        allowUnknown: boolean;\n        replaceCodeMap: {\n            [charCode: number]: string;\n        };\n        hasReplace: boolean;\n        abandon: boolean;\n        check?: LexCheck;\n    };\n    value: {\n        lex: boolean;\n        def: {\n            [src: string]: {\n                val: any;\n            };\n        };\n        defre: {\n            name: string;\n            val: (res: any) => any;\n            match: RegExp;\n            consume: boolean;\n        }[];\n    };\n    comment: {\n        lex: boolean;\n        def: {\n            [name: string]: {\n                name: string;\n                line: boolean;\n                start: string;\n                end?: string;\n                lex: boolean;\n                eatline: boolean;\n                suffixes?: string[];\n                suffixFn?: LexMatcher;\n            };\n        };\n        check?: LexCheck;\n    };\n    map: {\n        extend: boolean;\n        merge?: (prev: any, curr: any, rule: Rule, ctx: Context) => any;\n        child: boolean;\n    };\n    list: {\n        property: boolean;\n        pair: boolean;\n        child: boolean;\n    };\n    info: {\n        map: boolean;\n        list: boolean;\n        text: boolean;\n        marker: string;\n    };\n    debug: {\n        get_console: () => any;\n        maxlen: number;\n        print: {\n            config: boolean;\n            src?: (x: any) => string;\n        };\n    };\n    result: {\n        fail: any[];\n    };\n    rewind: {\n        history: number;\n    };\n    error: {\n        [code: string]: string;\n    };\n    errmsg: {\n        name: string;\n        suffix: boolean | string | ((color?: any, spec?: any) => string);\n    };\n    hint: any;\n    rePart: any;\n    re: any;\n    tI: number;\n    t: Record<string, Tin>;\n    color: {\n        active: boolean;\n        reset: string;\n        hi: string;\n        lo: string;\n        line: string;\n    };\n};\nexport interface Point {\n    len: number;\n    sI: number;\n    rI: number;\n    cI: number;\n    token: Token[];\n    end?: Token;\n}\nexport interface Token {\n    isToken: boolean;\n    name: string;\n    tin: Tin;\n    val: any;\n    src: string;\n    sI: number;\n    rI: number;\n    cI: number;\n    len: number;\n    use?: Bag;\n    err?: string;\n    why?: string;\n    ignored?: Token;\n    bad(err: string, details?: any): Token;\n    resolveVal(rule: Rule, ctx: Context): any;\n}\nexport interface AltSpec {\n    s?: (Tin | Tin[] | null | undefined | string)[] | null | string;\n    p?: string | AltNext | null | false | FuncRef;\n    r?: string | AltNext | null | false | FuncRef;\n    b?: number | AltBack | null | false | FuncRef;\n    c?: AltCond | null;\n    n?: Counters;\n    a?: AltAction | FuncRef | null;\n    h?: AltModifier | null;\n    u?: Bag;\n    k?: Bag;\n    g?: string | string[];\n    e?: AltError | FuncRef | null;\n}\ntype AltSpecish = AltSpec | undefined | null | false | 0 | typeof NaN;\nexport type ListMods = {\n    append?: boolean;\n    move?: number[];\n    delete?: number[];\n    custom?: (alts: AltSpec[]) => null | AltSpec[];\n};\nexport interface AltMatch {\n    p?: string | null | false | 0;\n    r?: string | null | false | 0;\n    b?: number | null | false;\n    c?: AltCond;\n    n?: Counters;\n    a?: AltAction;\n    h?: AltModifier;\n    u?: Bag;\n    k?: Bag;\n    g?: string[];\n    e?: Token;\n}\nexport type Bag = {\n    [key: string]: any;\n};\nexport type FuncRef = `@${string}`;\nexport type FuncRefMap<FT> = Record<FuncRef, FT>;\nexport type Counters = {\n    [key: string]: number;\n};\nexport type Tin = number;\nexport type TokenMap = {\n    [name: string]: Tin;\n};\nexport type TokenSetMap = {\n    [name: string]: Tin[];\n};\nexport type TinMap = {\n    [ref: number]: string;\n};\nexport type TinSetMap = {\n    [ref: number]: string;\n};\nexport type MatchMap = {\n    [name: string]: RegExp | LexMatcher;\n};\nexport type Chars = {\n    [char: string]: number;\n};\nexport type StrMap = {\n    [name: string]: string;\n};\nexport type RuleState = 'o' | 'c';\nexport type RuleStep = 'b' | 'a';\nexport type LexMatcher = (lex: Lex, rule: Rule, tI?: number) => Token | undefined;\nexport type MakeLexMatcher = (cfg: Config, opts: Options) => LexMatcher | null | undefined | false;\nexport type LexCheck = (lex: Lex) => void | undefined | {\n    done: boolean;\n    token: Token | undefined;\n};\nexport type ParsePrepare = (jsonic: Jsonic, ctx: Context, meta?: any) => void;\nexport type RuleSpecMap = {\n    [name: string]: RuleSpec;\n};\nexport type RuleDefiner = (rs: RuleSpec, p: Parser) => void | RuleSpec;\nexport interface NormAltSpec extends AltSpec {\n    s: (Tin | Tin[] | null | undefined)[];\n    p: string | AltNext | null | false;\n    r: string | AltNext | null | false;\n    b: number | AltBack | null | false;\n    S: (number[] | null)[] | null;\n    t: Tin[][];\n    sN: number;\n    c: NormAltCond | null | undefined;\n    g: string[];\n    a: AltAction | null | undefined;\n    e: AltError | null | undefined;\n}\nexport type AltCond = ((rule: Rule, ctx: Context, alt: AltMatch) => boolean) | Record<string, any>;\nexport type NormAltCond = ((rule: Rule, ctx: Context, alt: AltMatch) => boolean);\nexport type AltModifier = (rule: Rule, ctx: Context, alt: AltMatch, next: Rule) => AltMatch;\nexport type AltAction = (rule: Rule, ctx: Context, alt: AltMatch) => any;\nexport type AltNext = (rule: Rule, ctx: Context, alt: AltMatch) => string | null | false | 0;\nexport type AltBack = (rule: Rule, ctx: Context, alt: AltMatch) => number | null | false;\nexport type StateAction = (rule: Rule, ctx: Context, next: Rule, out?: Token | void) => Token | void;\nexport type AltError = (rule: Rule, ctx: Context, alt: AltMatch) => Token | undefined;\nexport type ValModifier = (val: any, lex: Lex, cfg: Config, opts: Options) => string;\nexport type LexSub = (tkn: Token, rule: Rule, ctx: Context) => void;\nexport type RuleSub = (rule: Rule, ctx: Context) => void;\nexport interface Parser {\n    options: Options;\n    cfg: Config;\n    rsm: RuleSpecMap;\n    rule(name?: string, define?: RuleDefiner | null): RuleSpec | RuleSpecMap | undefined;\n    start(src: string, jsonic: any, meta?: any, parent_ctx?: any): any;\n    clone(options: Options, config: Config, jsonic: Jsonic): Parser;\n    norm(): void;\n}\nexport type GrammarSpec = {\n    ref?: Record<FuncRef, Function>;\n    options?: Bag;\n    rule?: Record<string, {\n        open?: GrammarAltSpec[] | {\n            alts: GrammarAltSpec[];\n            inject: {\n                append?: boolean;\n                delete?: number[];\n                move?: number[];\n            };\n        };\n        close?: GrammarAltSpec[] | {\n            alts: GrammarAltSpec[];\n            inject: {\n                append?: boolean;\n                delete?: number[];\n                move?: number[];\n            };\n        };\n    }>;\n};\nexport type GrammarAltSpec = {\n    s?: string | string[];\n    b?: FuncRef | number;\n    p?: FuncRef | string;\n    r?: FuncRef | string;\n    a?: FuncRef;\n    e?: FuncRef;\n    h?: FuncRef;\n    c?: FuncRef | Record<string, any>;\n    n?: Record<string, number>;\n    u?: Record<string, any>;\n    k?: Record<string, any>;\n    g?: string | string[];\n};\nexport {};\n"
  },
  {
    "path": "dist/types.js",
    "content": "\"use strict\";\n/* Copyright (c) 2021-2022 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.STRING = exports.SKIP = exports.INSPECT = exports.EMPTY = exports.AFTER = exports.BEFORE = exports.CLOSE = exports.OPEN = void 0;\n/*  types.ts\n *  Type and constant definitions.\n */\nexports.OPEN = 'o';\nexports.CLOSE = 'c';\nexports.BEFORE = 'b';\nexports.AFTER = 'a';\nexports.EMPTY = '';\nexports.INSPECT = Symbol.for('nodejs.util.inspect.custom');\n// Sentinel value that acts as `undefined` in deep merge — the base value\n// is preserved.  Represented as \"@SKIP\" in grammar options.\nexports.SKIP = Symbol.for('jsonic.SKIP');\n// Empty rule used as a no-value placeholder.\n// export const NONE = ({ name: 'none', state: OPEN } as Rule)\nexports.STRING = 'string';\n//# sourceMappingURL=types.js.map"
  },
  {
    "path": "dist/utility.d.ts",
    "content": "import type { Chars, Config, Context, Lex, Options, RuleSpec, Tin, ListMods } from './types';\ndeclare const keys: (x: any) => string[];\ndeclare const values: <T>(x: {\n    [key: string]: T;\n} | undefined | null) => T[];\ndeclare const entries: <T>(x: {\n    [key: string]: T;\n} | undefined | null) => [string, T][];\ndeclare const assign: (x: any, ...r: any[]) => any;\ndeclare const isarr: (x: any) => x is any[];\ndeclare const defprop: <T>(o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>) => T;\ndeclare const omap: (o: any, f?: (e: any) => any) => any;\ndeclare const S: {\n    indent: string;\n    logindent: string;\n    space: string;\n    gap: string;\n    Object: string;\n    Array: string;\n    object: string;\n    string: string;\n    function: string;\n    unexpected: string;\n    map: string;\n    list: string;\n    elem: string;\n    pair: string;\n    val: string;\n    node: string;\n    no_re_flags: string;\n    unprintable: string;\n    invalid_ascii: string;\n    invalid_unicode: string;\n    invalid_lex_state: string;\n    unterminated_string: string;\n    unterminated_comment: string;\n    lex: string;\n    parse: string;\n    error: string;\n    none: string;\n    imp_map: string;\n    imp_list: string;\n    imp_null: string;\n    end: string;\n    open: string;\n    close: string;\n    rule: string;\n    stack: string;\n    nUll: string;\n    name: string;\n    make: string;\n    colon: string;\n    step: string;\n};\ndeclare function configure(jsonic: any, incfg: Config | undefined, opts: Options): Config;\ndeclare function tokenize<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R, cfg: Config, jsonic?: any): T;\ndeclare function findTokenSet<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R, cfg: Config): T;\ndeclare function mesc(s: string, _?: any): any;\ndeclare function regexp(flags: string | null, ...parts: (string | (String & {\n    esc?: boolean;\n}))[]): RegExp;\ndeclare function escre(s: string | undefined): string;\ndeclare function deep(base?: any, ...rest: any): any;\ndeclare function badlex(lex: Lex, BD: Tin, ctx: Context): Lex;\ndeclare function makelog(ctx: Context, meta: any): ((...rest: any) => void) | undefined;\ndeclare function srcfmt(config: Config): (s: any) => string;\ndeclare function str(o: any, len?: number): string;\ndeclare function snip(s: any, len?: number): string;\ndeclare function clone(class_instance: any): any;\ndeclare function charset(...parts: (string | object | boolean | undefined)[]): Chars;\ndeclare function clean<T>(o: T): T;\ndeclare function filterRules(rs: RuleSpec, cfg: Config): RuleSpec;\ndeclare function modlist(list: any[], mods?: ListMods): any[];\ndeclare function parserwrap(parser: any): {\n    start: (src: string, jsonic: any, meta?: any, parent_ctx?: any) => any;\n};\ndeclare function getpath(root: any, path: string | string[]): any;\ndeclare function resolveFuncRefs(obj: any, ref?: Record<string, Function>): any;\nexport { S, assign, badlex, charset, clean, clone, configure, deep, defprop, entries, escre, filterRules, getpath, isarr, makelog, mesc, regexp, snip, srcfmt, tokenize, parserwrap, str, omap, keys, values, findTokenSet, modlist, resolveFuncRefs, };\n"
  },
  {
    "path": "dist/utility.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2024 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.values = exports.keys = exports.omap = exports.isarr = exports.entries = exports.defprop = exports.assign = exports.S = void 0;\nexports.badlex = badlex;\nexports.charset = charset;\nexports.clean = clean;\nexports.clone = clone;\nexports.configure = configure;\nexports.deep = deep;\nexports.escre = escre;\nexports.filterRules = filterRules;\nexports.getpath = getpath;\nexports.makelog = makelog;\nexports.mesc = mesc;\nexports.regexp = regexp;\nexports.snip = snip;\nexports.srcfmt = srcfmt;\nexports.tokenize = tokenize;\nexports.parserwrap = parserwrap;\nexports.str = str;\nexports.findTokenSet = findTokenSet;\nexports.modlist = modlist;\nexports.resolveFuncRefs = resolveFuncRefs;\nconst types_1 = require(\"./types\");\nconst lexer_1 = require(\"./lexer\");\nconst error_1 = require(\"./error\");\n// Null-safe object and array utilities\n// TODO: should use proper types:\n// https://github.com/microsoft/TypeScript/tree/main/src/lib\nconst keys = (x) => (null == x ? [] : Object.keys(x));\nexports.keys = keys;\nconst values = (x) => null == x ? [] : Object.values(x);\nexports.values = values;\nconst entries = (x) => (null == x ? [] : Object.entries(x));\nexports.entries = entries;\nconst assign = (x, ...r) => Object.assign(null == x ? {} : x, ...r);\nexports.assign = assign;\nconst isarr = (x) => Array.isArray(x);\nexports.isarr = isarr;\nconst defprop = Object.defineProperty;\nexports.defprop = defprop;\n// Map object properties using entries.\nconst omap = (o, f) => {\n    return Object.entries(o || {}).reduce((o, e) => {\n        let me = f ? f(e) : e;\n        if (undefined === me[0]) {\n            delete o[e[0]];\n        }\n        else {\n            o[me[0]] = me[1];\n        }\n        // Additional pairs set additional keys.\n        let i = 2;\n        while (undefined !== me[i]) {\n            o[me[i]] = me[i + 1];\n            i += 2;\n        }\n        return o;\n    }, {});\n};\nexports.omap = omap;\n// TODO: remove!\n// A bit pedantic, but let's be strict about strings.\n// Also improves minification a little.\nconst S = {\n    indent: '. ',\n    logindent: '  ',\n    space: ' ',\n    gap: '  ',\n    Object: 'Object',\n    Array: 'Array',\n    object: 'object',\n    string: 'string',\n    function: 'function',\n    unexpected: 'unexpected',\n    map: 'map',\n    list: 'list',\n    elem: 'elem',\n    pair: 'pair',\n    val: 'val',\n    node: 'node',\n    no_re_flags: types_1.EMPTY,\n    unprintable: 'unprintable',\n    invalid_ascii: 'invalid_ascii',\n    invalid_unicode: 'invalid_unicode',\n    invalid_lex_state: 'invalid_lex_state',\n    unterminated_string: 'unterminated_string',\n    unterminated_comment: 'unterminated_comment',\n    lex: 'lex',\n    parse: 'parse',\n    error: 'error',\n    none: 'none',\n    imp_map: 'imp,map',\n    imp_list: 'imp,list',\n    imp_null: 'imp,null',\n    end: 'end',\n    open: 'open',\n    close: 'close',\n    rule: 'rule',\n    stack: 'stack',\n    nUll: 'null',\n    name: 'name',\n    make: 'make',\n    colon: ':',\n    step: 'step',\n};\nexports.S = S;\n// Idempotent normalization of options.\n// See Config type for commentary.\nfunction configure(jsonic, incfg, opts) {\n    const cfg = incfg || {};\n    cfg.t = cfg.t || {};\n    cfg.tI = cfg.tI || 1;\n    const t = (tn) => tokenize(tn, cfg);\n    // Standard tokens. These names should not be changed.\n    if (false !== opts.standard$) {\n        t('#BD'); // BAD\n        t('#ZZ'); // END\n        t('#UK'); // UNKNOWN\n        t('#AA'); // ANY\n        t('#SP'); // SPACE\n        t('#LN'); // LINE\n        t('#CM'); // COMMENT\n        t('#NR'); // NUMBER\n        t('#ST'); // STRING\n        t('#TX'); // TEXT\n        t('#VL'); // VALUE\n    }\n    cfg.safe = {\n        key: false === opts.safe?.key ? false : true,\n    };\n    cfg.fixed = {\n        lex: !!opts.fixed?.lex,\n        token: opts.fixed\n            ? omap(clean(opts.fixed.token), ([name, src]) => [\n                src,\n                tokenize(name, cfg),\n            ])\n            : {},\n        ref: undefined,\n        check: opts.fixed?.check,\n    };\n    cfg.fixed.ref = omap(cfg.fixed.token, ([tin, src]) => [\n        tin,\n        src,\n    ]);\n    cfg.fixed.ref = Object.assign(cfg.fixed.ref, omap(cfg.fixed.ref, ([tin, src]) => [src, tin]));\n    cfg.match = {\n        lex: !!opts.match?.lex,\n        value: opts.match\n            ? omap(clean(opts.match.value), ([name, spec]) => [\n                name,\n                spec,\n            ])\n            : {},\n        token: opts.match\n            ? omap(clean(opts.match.token), ([name, matcher]) => [\n                tokenize(name, cfg),\n                matcher,\n            ])\n            : {},\n        check: opts.match?.check,\n    };\n    // Lookup tin directly from matcher\n    omap(cfg.match.token, ([tin, matcher]) => [\n        tin,\n        ((matcher.tin$ = +tin), matcher),\n    ]);\n    // Convert tokenSet tokens names to tins\n    const tokenSet = opts.tokenSet\n        ? Object.keys(opts.tokenSet).reduce((a, n) => ((a[n] = opts.tokenSet[n]\n            .filter((x) => null != x)\n            .map((n) => t(n))),\n            a), {})\n        : {};\n    cfg.tokenSet = cfg.tokenSet || {};\n    entries(tokenSet).map((entry) => {\n        let name = entry[0];\n        let tinset = entry[1];\n        if (cfg.tokenSet[name]) {\n            cfg.tokenSet[name].length = 0;\n            cfg.tokenSet[name].push(...tinset);\n        }\n        else {\n            cfg.tokenSet[name] = tinset;\n        }\n    });\n    // Lookup table for token tin in given tokenSet\n    cfg.tokenSetTins = entries(cfg.tokenSet).reduce((a, en) => ((a[en[0]] = a[en[0]] || {}),\n        en[1].map((tin) => (a[en[0]][tin] = true)),\n        a), {});\n    // The IGNORE tokenSet is special and should always exist, even if empty.\n    cfg.tokenSetTins.IGNORE = cfg.tokenSetTins.IGNORE || {};\n    cfg.space = {\n        lex: !!opts.space?.lex,\n        chars: charset(opts.space?.chars),\n        check: opts.space?.check,\n    };\n    cfg.line = {\n        lex: !!opts.line?.lex,\n        chars: charset(opts.line?.chars),\n        rowChars: charset(opts.line?.rowChars),\n        single: !!opts.line?.single,\n        check: opts.line?.check,\n    };\n    cfg.text = {\n        lex: !!opts.text?.lex,\n        modify: (cfg.text?.modify || [])\n            .concat((opts.text?.modify ? [opts.text.modify] : []).flat())\n            .filter((m) => null != m),\n        check: opts.text?.check,\n    };\n    cfg.number = {\n        lex: !!opts.number?.lex,\n        hex: !!opts.number?.hex,\n        oct: !!opts.number?.oct,\n        bin: !!opts.number?.bin,\n        sep: null != opts.number?.sep && '' !== opts.number.sep,\n        exclude: opts.number?.exclude,\n        sepChar: opts.number?.sep,\n        check: opts.number?.check,\n    };\n    // NOTE: these are not value ending tokens\n    cfg.value = {\n        lex: !!opts.value?.lex,\n        def: entries(opts.value?.def || {}).reduce((a, e) => (null == e[1] || false === e[1] || e[1].match || (a[e[0]] = e[1]), a), {}),\n        // Pre-sort by name at configure time so iteration at lex time is\n        // deterministic across runtimes (does not depend on object key order).\n        defre: entries(opts.value?.def || {})\n            .filter(([, spec]) => spec && spec.match)\n            .map(([name, spec]) => ({\n            name,\n            val: spec.val,\n            match: spec.match,\n            consume: !!spec.consume,\n        }))\n            .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),\n        // TODO: just testing, move to a plugin for extended values\n        // 'undefined': { v: undefined },\n        // 'NaN': { v: NaN },\n        // 'Infinity': { v: Infinity },\n        // '+Infinity': { v: +Infinity },\n        // '-Infinity': { v: -Infinity },\n    };\n    cfg.rule = {\n        start: null == opts.rule?.start ? 'val' : opts.rule.start,\n        maxmul: null == opts.rule?.maxmul ? 3 : opts.rule.maxmul,\n        finish: !!opts.rule?.finish,\n        include: opts.rule?.include\n            ? opts.rule.include.split(/\\s*,+\\s*/).filter((g) => '' !== g)\n            : [],\n        exclude: opts.rule?.exclude\n            ? opts.rule.exclude.split(/\\s*,+\\s*/).filter((g) => '' !== g)\n            : [],\n    };\n    cfg.map = {\n        extend: !!opts.map?.extend,\n        merge: opts.map?.merge,\n        child: !!opts.map?.child,\n    };\n    cfg.list = {\n        property: !!opts.list?.property,\n        pair: !!opts.list?.pair,\n        child: !!opts.list?.child,\n    };\n    cfg.info = {\n        map: !!opts.info?.map,\n        list: !!opts.info?.list,\n        text: !!opts.info?.text,\n        marker: opts.info?.marker || '__info__',\n    };\n    let fixedSorted = Object.keys(cfg.fixed.token).sort((a, b) => b.length - a.length);\n    let fixedRE = fixedSorted.map((fixed) => escre(fixed)).join('|');\n    let commentStartRE = opts.comment?.lex\n        ? (opts.comment.def ? values(opts.comment.def) : [])\n            .filter((c) => c && c.lex)\n            .map((c) => escre(c.start))\n            .join('|')\n        : '';\n    // End-marker RE part\n    let enderRE = [\n        '([',\n        escre(keys(charset(cfg.space.lex && cfg.space.chars, cfg.line.lex && cfg.line.chars)).join('')),\n        ']',\n        ('string' === typeof opts.ender\n            ? opts.ender.split('')\n            : Array.isArray(opts.ender)\n                ? opts.ender\n                : [])\n            .map((c) => '|' + escre(c))\n            .join(''),\n        '' === fixedRE ? '' : '|',\n        fixedRE,\n        '' === commentStartRE ? '' : '|',\n        commentStartRE,\n        '|$)', // EOF case\n    ];\n    cfg.rePart = {\n        fixed: fixedRE,\n        ender: enderRE,\n        commentStart: commentStartRE,\n    };\n    // TODO: friendlier names\n    cfg.re = {\n        ender: regexp(null, ...enderRE),\n        // TODO: prebuild these using a property on matcher?\n        rowChars: regexp(null, escre(opts.line?.rowChars)),\n        columns: regexp(null, '[' + escre(opts.line?.chars) + ']', '(.*)$'),\n    };\n    cfg.lex = {\n        empty: !!opts.lex?.empty,\n        emptyResult: opts.lex?.emptyResult,\n        match: opts.lex?.match\n            ? entries(opts.lex.match)\n                .reduce((list, entry) => {\n                let name = entry[0];\n                let matchspec = entry[1];\n                if (matchspec) {\n                    let matcher = matchspec.make(cfg, opts);\n                    if (matcher) {\n                        matcher.matcher = name;\n                        matcher.make = matchspec.make;\n                        matcher.order = matchspec.order;\n                    }\n                    list.push(matcher);\n                }\n                return list;\n            }, [])\n                .filter((m) => null != m && false !== m && -1 < +m.order)\n                .sort((a, b) => a.order - b.order)\n            : [],\n    };\n    cfg.parse = {\n        prepare: values(opts.parse?.prepare),\n    };\n    cfg.debug = {\n        get_console: opts.debug?.get_console || (() => console),\n        maxlen: null == opts.debug?.maxlen ? 99 : opts.debug.maxlen,\n        print: {\n            config: !!opts.debug?.print?.config,\n            src: opts.debug?.print?.src,\n        },\n    };\n    cfg.error = opts.error ?? {};\n    cfg.errmsg = (opts.errmsg ?? { suffix: true });\n    cfg.hint = opts.hint ?? {};\n    // Apply any config modifiers (probably from plugins).\n    if (opts.config?.modify) {\n        keys(opts.config.modify).forEach((modifer) => opts.config.modify[modifer](cfg, opts));\n    }\n    // Debug the config - useful for plugin authors.\n    if (cfg.debug.print.config) {\n        cfg.debug.get_console().dir(cfg, { depth: null });\n    }\n    cfg.result = {\n        fail: [],\n    };\n    if (opts.result) {\n        cfg.result.fail = [...opts.result.fail];\n    }\n    // Parse-time consumed-token history cap (for ctx.rewind).\n    cfg.rewind = {\n        history: null == opts.rewind?.history ? Infinity : opts.rewind.history,\n    };\n    const optscolor = opts.color ?? {};\n    cfg.color = cfg.color ?? {};\n    cfg.color.active = optscolor.active ?? cfg.color.active ?? true;\n    cfg.color.reset = optscolor.reset ?? cfg.color.reset ?? '\\x1b[0m';\n    cfg.color.hi = optscolor.hi ?? cfg.color.hi ?? '\\x1b[91m';\n    cfg.color.lo = optscolor.lo ?? cfg.color.lo ?? '\\x1b[2m';\n    cfg.color.line = optscolor.line ?? cfg.color.line ?? '\\x1b[34m';\n    assign(jsonic.options, opts);\n    assign(jsonic.token, cfg.t);\n    assign(jsonic.tokenSet, cfg.tokenSet);\n    assign(jsonic.fixed, cfg.fixed.ref);\n    return cfg;\n}\n// Uniquely resolve or assign token by name (string) or identification number (Tin),\n// returning the associated Tin (for the name) or name (for the Tin).\nfunction tokenize(ref, cfg, jsonic) {\n    let tokenmap = cfg.t;\n    let token = tokenmap[ref];\n    if (null == token && types_1.STRING === typeof ref) {\n        token = cfg.tI++;\n        tokenmap[token] = ref;\n        tokenmap[ref] = token;\n        tokenmap[ref.substring(1)] = token;\n        if (null != jsonic) {\n            assign(jsonic.token, cfg.t);\n        }\n    }\n    return token;\n}\n// Find a tokenSet by name, or find the name of the TokenSet containing a given Tin.\nfunction findTokenSet(ref, cfg) {\n    let tokenSetMap = cfg.tokenSet;\n    let found = (tokenSetMap[ref] ??\n        ('string' == typeof ref ? tokenSetMap[ref.replace(/#/g, '')] : undefined));\n    return found;\n}\n// Mark a string for escaping by `util.regexp`.\nfunction mesc(s, _) {\n    return (_ = new String(s)), (_.esc = true), _;\n}\n// Construct a RegExp. Use mesc to mark string for escaping.\n// NOTE: flags first allows concatenated parts to be rest.\nfunction regexp(flags, ...parts) {\n    return new RegExp(parts\n        .map((p) => p.esc\n        ? //p.replace(/[-\\\\|\\]{}()[^$+*?.!=]/g, '\\\\$&')\n            escre(p.toString())\n        : p)\n        .join(types_1.EMPTY), null == flags ? '' : flags);\n}\nfunction escre(s) {\n    return null == s\n        ? ''\n        : s\n            .replace(/[-\\\\|\\]{}()[^$+*?.!=]/g, '\\\\$&')\n            .replace(/\\t/g, '\\\\t')\n            .replace(/\\r/g, '\\\\r')\n            .replace(/\\n/g, '\\\\n');\n}\n// Deep override for plain data. Mutates base object and array.\n// Array merge by `over` index, `over` wins non-matching types, except:\n// `undefined` always loses, `over` plain objects inject into functions,\n// and `over` functions always win. Over always copied.\nfunction deep(base, ...rest) {\n    let base_isf = S.function === typeof base;\n    let base_iso = null != base && (S.object === typeof base || base_isf);\n    for (let over of rest) {\n        let over_isf = S.function === typeof over;\n        let over_iso = null != over && (S.object === typeof over || over_isf);\n        let over_ctor;\n        if (base_iso &&\n            over_iso &&\n            !over_isf &&\n            Array.isArray(base) === Array.isArray(over)) {\n            for (let k in over) {\n                base[k] = deep(base[k], over[k]);\n            }\n        }\n        else {\n            base =\n                undefined === over || types_1.SKIP === over\n                    ? base\n                    : over_isf\n                        ? over\n                        : over_iso\n                            ? S.function === typeof (over_ctor = over.constructor) &&\n                                S.Object !== over_ctor.name &&\n                                S.Array !== over_ctor.name\n                                ? over\n                                : deep(Array.isArray(over) ? [] : {}, over)\n                            : over;\n            base_isf = S.function === typeof base;\n            base_iso = null != base && (S.object === typeof base || base_isf);\n        }\n    }\n    return base;\n}\nfunction badlex(lex, BD, ctx) {\n    let next = lex.next.bind(lex);\n    lex.next = (rule, alt, altI, tI) => {\n        let token = next(rule, alt, altI, tI);\n        if (BD === token.tin) {\n            let details = {};\n            if (null != token.use) {\n                details.use = token.use;\n            }\n            throw new error_1.JsonicError(token.why || S.unexpected, details, token, rule, ctx);\n        }\n        return token;\n    };\n    return lex;\n}\n// Special debug logging to console (use Jsonic('...', {log:N})).\n// log:N -> console.dir to depth N\n// log:-1 -> console.dir to depth 1, omitting objects (good summary!)\nfunction makelog(ctx, meta) {\n    let trace = ctx.opts?.plugin?.debug?.trace;\n    if (meta || trace) {\n        if ('number' === typeof meta?.log || trace) {\n            let exclude_objects = false;\n            let logdepth = meta?.log;\n            if (-1 === logdepth || trace) {\n                logdepth = 1;\n                exclude_objects = true;\n            }\n            ctx.log = (...rest) => {\n                if (exclude_objects) {\n                    let logstr = rest\n                        .filter((item) => S.object != typeof item)\n                        .map((item) => (S.function == typeof item ? item.name : item))\n                        .join(S.gap);\n                    ctx.cfg.debug.get_console().log(logstr);\n                }\n                else {\n                    ctx.cfg.debug.get_console().dir(rest, { depth: logdepth });\n                }\n                return undefined;\n            };\n        }\n        else if ('function' === typeof meta.log) {\n            ctx.log = meta.log;\n        }\n    }\n    return ctx.log;\n}\nfunction srcfmt(config) {\n    return 'function' === typeof config.debug.print.src\n        ? config.debug.print.src\n        : (s) => {\n            let out = null == s\n                ? types_1.EMPTY\n                : Array.isArray(s)\n                    ? JSON.stringify(s).replace(/]$/, entries(s)\n                        .filter((en) => isNaN(en[0]))\n                        .map((en, i) => (0 === i ? ', ' : '') +\n                        en[0] +\n                        ': ' +\n                        JSON.stringify(en[1])) + // Just one level of array props!\n                        ']')\n                    : JSON.stringify(s);\n            out =\n                out.substring(0, config.debug.maxlen) +\n                    (config.debug.maxlen < out.length ? '...' : types_1.EMPTY);\n            return out;\n        };\n}\nfunction str(o, len = 44) {\n    let s;\n    try {\n        s = 'object' === typeof o ? JSON.stringify(o) : '' + o;\n    }\n    catch (e) {\n        s = '' + o;\n    }\n    return snip(len < s.length ? s.substring(0, len - 3) + '...' : s, len);\n}\nfunction snip(s, len = 5) {\n    return undefined === s\n        ? ''\n        : ('' + s).substring(0, len).replace(/[\\r\\n\\t]/g, '.');\n}\nfunction clone(class_instance) {\n    return deep(Object.create(Object.getPrototypeOf(class_instance)), class_instance);\n}\n// Lookup map for a set of chars.\nfunction charset(...parts) {\n    return null == parts\n        ? {}\n        : parts\n            .filter((p) => false !== p)\n            .map((p) => ('object' === typeof p ? keys(p).join(types_1.EMPTY) : p))\n            .join(types_1.EMPTY)\n            .split(types_1.EMPTY)\n            .reduce((a, c) => ((a[c] = c.charCodeAt(0)), a), {});\n}\n// Remove all properties with values null or undefined. Note: mutates argument.\nfunction clean(o) {\n    for (let p in o) {\n        if (null == o[p]) {\n            delete o[p];\n        }\n    }\n    return o;\n}\n// TODO: rename to filterAlts\nfunction filterRules(rs, cfg) {\n    let rsnames = ['open', 'close'];\n    for (let rsn of rsnames) {\n        ;\n        rs.def[rsn] = rs.def[rsn]\n            // Convert comma separated rule group name list to string[].\n            .map((as) => ((as.g =\n            'string' === typeof as.g\n                ? (as.g || '').split(/\\s*,+\\s*/)\n                : as.g || []),\n            as))\n            // Keep rule if any group name matches, or if there are no includes.\n            .filter((as) => cfg.rule.include.reduce((a, g) => a || (null != as.g && -1 !== as.g.indexOf(g)), 0 === cfg.rule.include.length))\n            // Drop rule if any group name matches, unless there are no excludes.\n            .filter((as) => cfg.rule.exclude.reduce((a, g) => a && (null == as.g || -1 === as.g.indexOf(g)), true));\n    }\n    return rs;\n}\nfunction prop(obj, path, val) {\n    let root = obj;\n    try {\n        let parts = path.split('.');\n        let pn;\n        for (let pI = 0; pI < parts.length; pI++) {\n            pn = parts[pI];\n            if ('__proto__' === pn) {\n                throw new Error(pn);\n            }\n            if (pI < parts.length - 1) {\n                obj = obj[pn] = obj[pn] || {};\n            }\n        }\n        if (undefined !== val) {\n            if ('__proto__' === pn) {\n                throw new Error(pn);\n            }\n            obj[pn] = val;\n        }\n        return obj[pn];\n    }\n    catch (e) {\n        throw new Error('Cannot ' +\n            (undefined === val ? 'get' : 'set') +\n            ' path ' +\n            path +\n            ' on object: ' +\n            str(root) +\n            (undefined === val ? '' : ' to value: ' + str(val, 22)));\n    }\n}\n// Mutates list based on ListMods.\nfunction modlist(list, mods) {\n    if (mods && list) {\n        if (0 < list.length) {\n            // Delete before move so indexes still make sense, using null to preserve index.\n            if (mods.delete && 0 < mods.delete.length) {\n                for (let i = 0; i < mods.delete.length; i++) {\n                    let mdI = mods.delete[i];\n                    if (mdI < 0 ? -1 * mdI <= list.length : mdI < list.length) {\n                        let dI = (list.length + mdI) % list.length;\n                        list[dI] = null;\n                    }\n                }\n            }\n            // Format: [from,to, from,to, ...]\n            if (mods.move) {\n                for (let i = 0; i < mods.move.length; i += 2) {\n                    let fromI = (list.length + mods.move[i]) % list.length;\n                    let toI = (list.length + mods.move[i + 1]) % list.length;\n                    let entry = list[fromI];\n                    list.splice(fromI, 1);\n                    list.splice(toI, 0, entry);\n                }\n            }\n            // Filter out any deletes.\n            // return list.filter((a: AltSpec) => null != a)\n            let filtered = list.filter((entry) => null != entry);\n            if (filtered.length !== list.length) {\n                list.length = 0;\n                list.push(...filtered);\n            }\n        }\n        // Custom modification of list.\n        if (mods.custom) {\n            let newlist = mods.custom(list);\n            if (null != newlist) {\n                list = newlist;\n            }\n        }\n    }\n    return list;\n}\nfunction parserwrap(parser) {\n    return {\n        start: function (src, \n        // jsonic: Jsonic,\n        jsonic, meta, parent_ctx) {\n            try {\n                return parser.start(src, jsonic, meta, parent_ctx);\n            }\n            catch (ex) {\n                if ('SyntaxError' === ex.name) {\n                    let loc = 0;\n                    let row = 0;\n                    let col = 0;\n                    let tsrc = types_1.EMPTY;\n                    let errloc = ex.message.match(/^Unexpected token (.) .*position\\s+(\\d+)/i);\n                    if (errloc) {\n                        tsrc = errloc[1];\n                        loc = parseInt(errloc[2]);\n                        row = src.substring(0, loc).replace(/[^\\n]/g, types_1.EMPTY).length;\n                        let cI = loc - 1;\n                        while (-1 < cI && '\\n' !== src.charAt(cI))\n                            cI--;\n                        col = Math.max(src.substring(cI, loc).length, 0);\n                    }\n                    let token = ex.token ||\n                        (0, lexer_1.makeToken)('#UK', tokenize('#UK', jsonic.internal().config), undefined, tsrc, (0, lexer_1.makePoint)(tsrc.length, loc, ex.lineNumber || row, ex.columnNumber || col));\n                    throw new error_1.JsonicError(ex.code || 'json', ex.details || {\n                        msg: ex.message,\n                    }, token, {}, \n                    // TODO: this smells\n                    ex.ctx ||\n                        {\n                            uI: -1,\n                            opts: jsonic.options,\n                            cfg: jsonic.internal().config,\n                            token: token,\n                            meta,\n                            src: () => src,\n                            root: () => undefined,\n                            plgn: () => jsonic.internal().plugins,\n                            inst: () => jsonic,\n                            rule: { name: 'no-rule' },\n                            sub: {},\n                            xs: -1,\n                            v2: token,\n                            v1: token,\n                            t: [token, token], // TODO: t[1] should be end token\n                            tC: -1,\n                            kI: -1,\n                            rs: [],\n                            rsI: 0,\n                            rsm: {},\n                            n: {},\n                            log: meta ? meta.log : undefined,\n                            F: srcfmt(jsonic.internal().config),\n                            u: {},\n                            NORULE: { name: 'no-rule' },\n                            NOTOKEN: { name: 'no-token' },\n                        });\n                }\n                else {\n                    throw ex;\n                }\n            }\n        },\n    };\n}\nfunction getpath(root, path) {\n    path = 'string' === typeof path ? path.split('.') : path;\n    let node = root;\n    for (let i = 0; i < path.length && null != node; i++) {\n        node = node[path[i]];\n    }\n    return node;\n}\n// Recursively resolve FuncRef strings in an options object to actual functions,\n// and `@/pattern/flags` strings to RegExp instances.\nfunction resolveFuncRefs(obj, ref) {\n    if (null == obj || 'object' !== typeof obj) {\n        if ('string' === typeof obj && '@' === obj[0]) {\n            // Escape: `@@` prefix produces a literal `@`-prefixed string.\n            if ('@' === obj[1]) {\n                return obj.substring(1);\n            }\n            // Sentinel: `@SKIP` resolves to the SKIP symbol (acts as undefined in deep merge).\n            if ('SKIP' === obj.substring(1)) {\n                return types_1.SKIP;\n            }\n            // Match `@/pattern/flags` — a JSON-serializable RegExp literal.\n            const m = obj.match(/^@\\/(.*)\\/([\\w]*)$/);\n            if (m) {\n                return new RegExp(m[1], m[2]);\n            }\n            if (ref) {\n                const fn = ref[obj];\n                if ('function' === typeof fn) {\n                    return fn;\n                }\n            }\n        }\n        return obj;\n    }\n    if (Array.isArray(obj)) {\n        return obj.map((item) => resolveFuncRefs(item, ref));\n    }\n    // Preserve non-plain objects (RegExp, Date, etc.) without recursion.\n    const ctor = obj.constructor;\n    if (ctor && 'Object' !== ctor.name) {\n        return obj;\n    }\n    const out = {};\n    for (const key of Object.keys(obj)) {\n        out[key] = resolveFuncRefs(obj[key], ref);\n    }\n    return out;\n}\n//# sourceMappingURL=utility.js.map"
  },
  {
    "path": "dist-test/prep.js",
    "content": "\"use strict\";\n// temporary file to help prepare test rewrite in ts \nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.prep = prep;\nfunction prep() { return null; }\n//# sourceMappingURL=prep.js.map"
  },
  {
    "path": "dist-test/tsconfig.tsbuildinfo",
    "content": "{\"root\":[\"../test/prep.ts\"],\"version\":\"6.0.3\"}"
  },
  {
    "path": "doc/api.md",
    "content": "# API Reference\n\n## Parsing\n\n### `Jsonic(src, meta?, parent_ctx?)`\n\nParse a string using default settings. `Jsonic` is both a callable function\nand a namespace for the API below.\n\n```js\nconst { Jsonic } = require('jsonic')\n\nJsonic('a:1')  // {\"a\": 1}\n```\n\nThe optional `meta` parameter passes arbitrary data through to plugins and\nrule actions.\n\n### `instance(src, meta?)`\n\nA configured instance is also directly callable.\n\n```js\nconst j = Jsonic.make({ comment: { lex: false } })\nj('a:1')  // {\"a\": 1}\n```\n\n## Instance Management\n\n### `Jsonic.make(options?)`\n\nCreate a new parser instance with the given options. Unset options fall back\nto defaults. Returns a callable instance with the full API.\n\n```js\nconst strict = Jsonic.make({\n  comment: { lex: false },\n  text: { lex: false }\n})\n```\n\nThe `options` parameter can also be a string shortcut:\n- `'json'` -- strict JSON parser (only JSON-tagged grammar rules)\n- `'jsonic'` -- minimal jsonic parser\n\n### `Jsonic.empty(options?)`\n\nCreate an empty parser with no grammar or defaults. Used as a base for\nbuilding custom parsers from scratch.\n\n## Configuration\n\n### `instance.options`\n\nDirect access to the current options object.\n\n### `instance.options(changes?)`\n\nWhen called as a function, deep-merges `changes` into the options and returns\nthe result. Does not modify the instance in-place (use `make()` for that).\n\n### `instance.config()`\n\nReturns a deep copy of the internal configuration. This is the resolved,\ncompiled form of the options -- useful for debugging.\n\n## Grammar\n\n### `instance.rule(name?, definer?)`\n\nAccess or modify grammar rules.\n\n- `rule()` -- returns the full rule spec map\n- `rule(name)` -- returns the rule spec for `name`\n- `rule(name, definer)` -- calls `definer(ruleSpec)` to modify the rule\n\nEach rule spec has `open` and `close` alternate lists, plus state actions\n(`bo`, `bc`, `ao`, `ac`) for before/after open/close phases.\n\n```js\njsonic.rule('val', (rs) => {\n  rs.open.unshift({\n    s: [[myToken]],\n    a: (rule) => { rule.node = 'custom' }\n  })\n})\n```\n\n### `instance.token(ref)`\n\nGet or create a token type. `ref` is a string name (e.g., `'#OB'` for open\nbrace). When called with a second argument mapping to a source character, it\nregisters a new fixed token.\n\n```js\nconst T_TILDE = jsonic.token('#TL', '~')\n```\n\n### `instance.tokenSet(ref)`\n\nGet a named token set. Built-in sets: `'IGNORE'` (space, line, comment),\n`'VAL'` (text, number, string, value), `'KEY'`.\n\n### `instance.fixed(ref)`\n\nGet the fixed token mapping (source characters to token types).\n\n## Plugins\n\n### `instance.use(plugin, options?)`\n\nRegister and execute a plugin. The plugin function receives the jsonic\ninstance and the options object.\n\n```js\njsonic.use(myPlugin, { key: 'value' })\n```\n\nPlugins are re-applied when deriving child instances.\n\n## Events\n\n### `instance.sub({ lex?, rule? })`\n\nSubscribe to lex and/or rule events.\n\n- `lex(token, rule, ctx)` -- fires after each token is lexed\n- `rule(rule, ctx)` -- fires before each rule processing step\n\n```js\njsonic.sub({\n  lex: (token, rule, ctx) => {\n    console.log('token:', token)\n  }\n})\n```\n\n## Utilities\n\n### `instance.util`\n\nA collection of helper functions for plugin authors:\n\n- `tokenize` -- convert token names to Tin numbers\n- `deep` -- deep merge objects\n- `clone` -- deep clone\n- `regexp` -- safe regex construction\n- `srcfmt` -- format source strings for display\n- `charset` -- build character sets\n- `errmsg`, `strinject` -- error message helpers\n- `prop`, `keys`, `values`, `entries`, `omap` -- object utilities\n- `trimstk`, `makelog`, `clean`, `str`, `mesc`, `escre` -- misc helpers\n\n## Constants\n\n### `OPEN`, `CLOSE`, `BEFORE`, `AFTER`, `EMPTY`\n\nStep constants used in rule definitions and state actions.\n\n## Error Handling\n\nParsing errors throw a `JsonicError` with:\n\n| Property | Description |\n|---|---|\n| `code` | Error code (`'unexpected'`, `'unterminated_string'`, etc.) |\n| `detail` | Human-readable message |\n| `pos` | 0-based character position |\n| `row` | 1-based line number |\n| `col` | 1-based column number |\n| `src` | Source fragment at the error |\n\n## Exports\n\nThe main module exports:\n\n| Export | Description |\n|---|---|\n| `Jsonic` | Main parser (callable + API) |\n| `JsonicError` | Error class |\n| `Parser` | Parser class |\n| `makeLex`, `makeParser` | Factory functions |\n| `makeToken`, `makePoint`, `makeRule`, `makeRuleSpec` | Internal constructors |\n| `makeFixedMatcher`, `makeSpaceMatcher`, `makeLineMatcher` | Lexer matchers |\n| `makeStringMatcher`, `makeCommentMatcher`, `makeNumberMatcher`, `makeTextMatcher` | Lexer matchers |\n| `OPEN`, `CLOSE`, `BEFORE`, `AFTER`, `EMPTY` | Step constants |\n| `util` | Utility functions |\n| `make` | Alias for `Jsonic.make` |\n"
  },
  {
    "path": "doc/bnf-to-jsonic-feasibility.md",
    "content": "# Feasibility Report: Converting BNF Grammars into Jsonic Grammars\n\n## Summary\n\nA converter from Backus–Naur Form (BNF) grammars to jsonic grammar specs is\n**feasible for the LL(2)-friendly subset of BNF** after standard grammar\nrewrites. Jsonic's `grammar()` JSON spec is a clean emission target; the hard\npart is the grammar *normaliser* that has to fit BNF into jsonic's two-token\nlookahead and strictly deterministic alternate model.\n\nThis document describes jsonic's grammar model, maps BNF primitives onto it,\nidentifies where the mapping breaks down, and sketches a conversion pipeline.\n\n---\n\n## 1. What a Jsonic Grammar Actually Is\n\nJsonic is not a classical CFG engine. It is a rule-driven push-down state\nmachine with an explicit two-token lookahead. The moving parts live under\n`src/`:\n\n| File | Role |\n|------|------|\n| `src/types.ts` | `Rule`, `RuleSpec`, `AltSpec`, `Token`, `Context` — the shape of a grammar. |\n| `src/rules.ts` | Rule state machine: `process()` tries each alternate, does push / replace / backtrack, runs state actions. |\n| `src/parser.ts` | Drives the rule stack in a single parse loop. |\n| `src/grammar.ts` | The built-in JSON + Jsonic grammar, authored with `jsonic.grammar({...})`. |\n| `src/lexer.ts` | Token matchers: fixed, text, number, string, comment, regex. |\n| `src/jsonic.ts` | Public API: `rule()`, `grammar()`, `token()`, `tokenSet()`, `use()`. |\n| `src/defaults.ts` | Built-in tokens (`#OB`, `#CB`, `#VAL`, `#ZZ`, …) and matcher config. |\n\nEach rule has two states: **Open** (`o`) and **Close** (`c`). Each state has\nan ordered list of `AltSpec`s. A representative excerpt from\n`src/grammar.ts:220-239`:\n\n```ts\npair: {\n  open: [\n    { s: '#KEY #CL', p: 'val', u: { pair: true },\n      a: '@pairkey', g: 'map,pair,key,json' },\n  ],\n  close: [\n    { s: '#CA', r: 'pair', g: 'map,pair,json' },\n    { s: '#CB', b: 1,    g: 'map,pair,json' },\n  ],\n}\n```\n\nRelevant `AltSpec` fields:\n\n- `s` — token sequence to match, **max 2 tokens** (`s0`, `s1`).\n- `p` — push a child rule by name.\n- `r` — replace the current rule (tail-recursion; gives repetition).\n- `b` — backtrack N tokens.\n- `a` — semantic action (`FuncRef` or function).\n- `c` — condition (counters or predicate).\n- `n` — increment named counters on match.\n- `g` — group tags, used for debugging and filtering.\n\nState-level hooks on `RuleSpec` — `bo` (before open), `ao` (after open),\n`bc` (before close), `ac` (after close) — are used to assemble `rule.node`.\n\nTokens are first-class: fixed tokens (`jsonic.token('#NAME', 'literal')`),\nregex matchers (`options.match.token`), and token sets grouped by tag\n(`IGNORE`, `VAL`, `KEY`).\n\n---\n\n## 2. Mapping BNF Primitives to Jsonic\n\n| BNF construct | Jsonic encoding | Fit |\n|---|---|---|\n| Terminal `\"foo\"` | Fixed token via `jsonic.token('#FOO','foo')` or regex matcher | Good |\n| Non-terminal `<X>` | Rule name pushed with `p:'X'` or replaced with `r:'X'` | Good |\n| Sequence `A B` (≤2 tokens) | `s: '#A #B'` | Good |\n| Sequence `A B C …` (>2 tokens) | Chain of auxiliary rules | Works, but verbose |\n| Alternation `A \\| B` | Multiple entries in `open`/`close`, first match wins | Good |\n| Optional `A?` | Two alternates: one matching `A`, one not | Good |\n| Repetition `A*` | Recursive rule: `open` pushes `A`, `close` uses `r:` on separator and `b:1` on terminator | Good |\n| Repetition `A+` | Same pattern as `*`, with a mandatory first push | Good |\n| Grouping `(A B)` | Auxiliary rule | Good |\n| Lookahead `(?=A)` | Built-in 2-token lookahead via `s` | Limited to depth 2 |\n| Negative lookahead `!A` | Custom `c:` predicate or `e:` error function | Possible but awkward |\n| Left recursion `E → E '+' T` | Static rewrite to `E → T (op T)*`, **or** runtime guard using `k` + token `sI` (see §4) | Workable without static rewrite for common cases |\n| Ambiguity / precedence | Resolved by alternate order + counters; no built-in precedence table | Manual |\n\nCanonical shapes already present in the codebase:\n\n- `elem` at `src/grammar.ts:242-256` is the `A (sep A)*` pattern.\n- `val` at `src/grammar.ts:163-185` is the alternation pattern.\n- `map` / `list` empty-collection handling at\n  `src/grammar.ts:191` and `src/grammar.ts:207` shows how to encode `A?`\n  via the `#OB #CB` / `b:1` trick.\n\n---\n\n## 3. Where the Mapping Breaks Down\n\n1. **Two-token lookahead ceiling.** Any BNF production whose decision\n   requires looking at the 3rd+ token must be refactored into a chain of\n   auxiliary rules. A mechanical converter can do this, but the output\n   grows with the number of \"split points\".\n2. **Left recursion.** Jsonic's stack model cannot execute `E → E op T`\n   naively — a `p: 'E'` at the same token position as the current `E`\n   would infinite-loop. Two options:\n   - **Static rewrite** to `E → T (op T)*` before emission.\n   - **Runtime guard** using the `k` bag and token `sI`; see §4\n     \"Runtime left-recursion handling\" — this avoids the rewrite for\n     direct and many indirect left-recursive cycles but does not give\n     full packrat seed-and-grow semantics.\n3. **Ambiguity resolution.** BNF routinely relies on external precedence\n   and associativity tables (e.g. Yacc `%left`). Jsonic has no such\n   mechanism; ambiguity must be resolved at rewrite time, typically by\n   stratifying `expr / term / factor`.\n4. **Semantic actions.** BNF/YACC-style `{ $$ = $1 + $3 }` has no direct\n   equivalent. Actions can be emitted as `FuncRef` bodies attached via\n   `a:` on an alternate, but the `$N` indexing needs translation to\n   `rule.o0`, `rule.o1`, `rule.child.node`, etc.\n5. **Inherited/synthesized attributes.** Must be hand-mapped to\n   `rule.u` (use), `rule.k` (keep), `rule.n` (counters), or to the node\n   assembly performed by `bo`/`ao`/`bc`/`ac`.\n6. **Per-alternate state hooks.** Jsonic's `bo/ao/bc/ac` are *rule*-scoped,\n   not alternate-scoped. Per-alternate behaviour must be pushed into `a:`\n   functions on individual alternates.\n\n---\n\n## 4. Runtime Left-Recursion Handling via `k` + Token `sI`\n\nStatic left-recursion elimination is the textbook fix, but jsonic\nexposes enough metadata to handle many left-recursive grammars\n**without** rewriting them — useful for indirect cycles where the\nrewrite is noisy, or for converter output that should stay close to\nthe original BNF.\n\n**Two facts make this work.**\n\n1. The `k` bag on a `Rule` is shallow-copied onto the next rule on both\n   `p:` (push) and `r:` (replace). Verified at `src/rules.ts:452-455`\n   and `src/rules.ts:470-473`. So any data placed in `k` flows down\n   and forward through the rule stack.\n2. Every token carries an absolute source index `sI` (plus `rI`, `cI`)\n   from the lexer (`src/lexer.ts:85,106`). Two tokens with the same\n   `sI` are the *same* token instance.\n\n**The pattern.** On first entry to a left-recursive rule, record the\nentry token's `sI` in `k`. Guard the left-recursive alternate with a\n`c:` condition that rejects it when the current token's `sI` matches\nthe one already recorded for this rule. On rejection, the parser falls\nthrough to a non-left-recursive alternate (the \"seed\"). The close\nstate then tail-recurses via `r:` on each operator token, folding the\naccumulated node left-associatively in the `ac` hook. Sketch:\n\n```ts\nexpr: {\n  open: [\n    // Left-recursive alt — skipped on same-position re-entry.\n    { s: '#OP', c: (r, ctx) => r.k.seenExprAt !== ctx.t0.sI, ... },\n    // Seed: non-left-recursive fallback.\n    { p: 'term', a: (r, ctx) => { r.k.seenExprAt = ctx.t0.sI } },\n  ],\n  close: [\n    { s: '#OP', r: 'expr' },\n    { b: 1 },\n  ],\n  ac: (r) => {\n    r.node = r.prev\n      ? { op: r.o0.val, left: r.prev.node, right: r.child.node }\n      : r.child.node\n  },\n}\n```\n\n**What this covers.**\n\n- Direct left recursion: `E → E op T | T`.\n- Indirect left recursion: `A → B x`, `B → A y | ε`, provided the\n  cycle members each record their own `sI` in `k` under distinct keys\n  (`k.seenAAt`, `k.seenBAt`) so the guard is precise.\n- Left-associative tree shape via the `ac` fold.\n\n**What this does *not* cover.**\n\n- **Full packrat seed-and-grow semantics.** Real seed-and-grow\n  iteratively re-parses the same rule at the same position with a\n  progressively richer memoized seed, producing matches that require\n  more than one \"grow\" pass to discover. Jsonic's parse loop\n  (`src/parser.ts:172`) is a single forward pass; there is no\n  primitive to rewind `ctx.t0` and re-enter a completed rule with a\n  seeded result. `k` flows forward only and does not outlive a rule\n  instance, so it cannot serve as a cross-position memo cell.\n- **Ambiguous left recursion** where multiple derivations produce\n  different trees. The runtime guard is deterministic (first matching\n  alternate wins), so it picks one derivation; BNF that relied on\n  ambiguity plus a precedence table still needs stratification.\n\n**Implication for the converter.** Left recursion no longer has to be\nstatically eliminated as a hard precondition. The converter can:\n\n1. Detect left-recursive cycles in the grammar AST.\n2. For each cycle, emit a `k.seenXAt` guard on the offending alternate\n   and an `sI` record on the non-left-recursive alternates.\n3. Emit an `ac` hook that folds left-associatively for left-recursive\n   rules (and right-associatively — the default — otherwise).\n\nGrammars requiring true seed-and-grow (hidden left recursion with\nnullable intermediates, ambiguous left recursion) still need static\nrewrite or fall outside the tractable subset.\n\n---\n\n## 5. Remaining Issues After Metadata-Based Workarounds\n\nOnce the runtime left-recursion guard from §4 is available, the\nresidual list of BNF constructs that jsonic cannot cleanly absorb\nshrinks. This section enumerates what is left, grouped by how much\neach costs a converter.\n\n### 5.1 Hard / still blocking\n\n**Lookahead beyond 2 tokens.** `s:` matches `s0, s1` only. Conditions\n(`c:`) can inspect `ctx.t0` and `ctx.t1` but jsonic does not lex\ntokens 3+ ahead eagerly — `src/lexer.ts` produces tokens on demand.\nProductions whose decisive prefix exceeds two tokens must be split\ninto auxiliary rules, one per decision point. No metadata trick\ncircumvents this because the data has not yet been computed.\n*Cost:* linear blow-up in rule count; unavoidable.\n\n**Ambiguity with external precedence/associativity tables.** Yacc-style\n`%left`, `%right`, `%nonassoc` declarations have no direct jsonic\nequivalent. `n:` counters plus `c:` conditions can encode precedence\nclimbing (track `n.prec`, guard alternates with\n`c: { 'n.prec': { $lte: X } }`), but this is a grammar rewrite, not a\none-to-one mapping. The converter must either stratify\n(`expr/term/factor`) or emit a precedence-climbing scheme.\n*Cost:* a precedence planner is its own subsystem.\n\n**Context-sensitive grammars.** Python-style INDENT/DEDENT, heredocs,\nC typedef-sensitive parsing. Jsonic's lexer is regular; custom\nmatchers can be registered via `src/lexer.ts:216`, but that is\nper-language lexer work, not a grammar transformation.\n*Cost:* out of scope for a pure BNF → jsonic converter.\n\n### 5.2 Partially resolved, with caveats\n\n**Hidden left recursion through nullable intermediates.** The `k` +\n`sI` guard handles direct and clean indirect left recursion. It does\n*not* handle cases like `A → B x`, `B → A y | ε` where true packrat\nseed-and-grow is needed. The converter must either statically rewrite\nthese or flag them as unsupported.\n\n**Non-associative operators** (e.g. disallowing `a < b < c`). Needs an\n`n:` counter to detect repeat occurrence plus an `e:` error function.\nMechanically emittable but grammar-specific.\n\n**Per-alternate semantic actions.** BNF's `$1`, `$3` positional\nreferences map to `rule.o0`, `rule.o1`, `rule.child.node`, but BNF\ntypically attaches actions to individual productions whereas\n`bo/ao/bc/ac` are rule-scoped. The fix is to emit a dispatcher in the\nalternate's `a:` function, which works but means the emitter\nsynthesises and routes rather than emitting a clean rule-level hook.\n\n### 5.3 Fine but worth flagging\n\n**Empty productions / nullable alternates.** Fully supportable\n(matching alternate, plus a `b:1` fallback), but every nullable rule\nmultiplies the alternate count and is the main source of lookahead\nconflicts inside recursive sequences.\n\n**Keywords vs identifiers.** Jsonic's fixed tokens outrank `#TX` text\nmatching, so `if`/`while`/etc. can be declared as fixed tokens and\nwill win over identifier matching. The converter must decide which\nBNF terminals become fixed tokens and which become regex-matched.\n\n**Error messages / error recovery.** BNF does not specify these.\nJsonic has per-alternate `e:` error functions and `bad()` in\n`src/rules.ts:440`, but no panic-mode recovery or error productions.\nGenerated output will have generic \"unexpected token\" errors unless\nthe source BNF is annotated.\n\n**Parameterised rules** (ANTLR `list[sep]<T>` and similar). Must be\nmonomorphised at convert time — emit a concrete rule per\ninstantiation. Mechanical but adds a specialisation pass.\n\n**EBNF extensions** — `{n,m}` counted repetition, `A - B` exclusion,\ncharacter classes. All need explicit desugaring:\n\n- Character classes → regex matchers.\n- Counted repetition → `n:` counter plus `c:` guard.\n- Exclusion `A - B` → `a:` action with a rejection check.\n\n### 5.4 The shortlist\n\nIf constrained to naming the three issues that most shape the\nconverter's design after §4's guard is in place:\n\n1. **>2-token lookahead** — inflates rule count, cannot be avoided.\n2. **Precedence/associativity planning** — needs a real algorithm in\n   the normaliser, not a rewrite rule.\n3. **Context-sensitivity and custom lexer states** — outside scope.\n\nEverything else is either solvable with metadata (left recursion,\nattributes, non-associativity) or is a mechanical desugar (EBNF\nsugar, parameterised rules, empty productions).\n\n---\n\n## 6. Suggested Conversion Pipeline\n\nA BNF → jsonic converter is realistic for the subset of BNF that is\nLL(2)-compatible after standard rewrites. A workable pipeline:\n\n1. **Parse BNF input** (supporting `|`, `?`, `*`, `+`, `( )`, and terminal\n   literals) into an internal grammar AST.\n2. **Normalise:**\n   - Eliminate left recursion.\n   - Desugar `?` / `*` / `+` into named recursive helpers.\n   - Split any alternate whose decisive prefix is >2 tokens into\n     auxiliary rules so each decision fits `s0,s1`.\n3. **Allocate tokens:**\n   - Literal terminals → fixed tokens via `jsonic.token('#X','literal')`.\n   - Regex terminals → match tokens via `options.match.token`.\n   - Group related tokens into token sets where reused.\n4. **Emit rules** using the `jsonic.grammar({ rule: { ... } })` spec form\n   — it is JSON-serialisable and matches the style of `src/grammar.ts`:\n   - Map each BNF production to `open` alternates.\n   - For recursive productions, emit a `close` alternate with `r:` on\n     the separator and `b:1` on the terminator (mirroring `elem`).\n   - Attach synthesised-attribute code via `ac` or per-alternate `a:`\n     references.\n5. **Emit a start rule** that wraps the top production and closes on\n   `#ZZ`, mirroring the `val` rule in `src/grammar.ts:163-185`.\n\n---\n\n## 7. Feasibility Verdict\n\n- **Feasible today** for: JSON-like config languages, straightforward\n  DSLs, expression grammars after left-recursion removal and precedence\n  stratification, and most \"list of thing, separator, thing\" shapes.\n- **Feasible with manual help** for: grammars needing deep lookahead,\n  grammars with rich semantic actions, or grammars with ambiguity that\n  BNF originally resolved via a precedence table.\n- **Not feasible** for: context-sensitive grammars, grammars requiring\n  unbounded lookahead, or grammars depending on a GLR/Earley-style\n  ambiguous parse.\n\nThe hard part of this project is not code generation — jsonic's\n`grammar()` spec is already a clean emission target — but the **grammar\nrewriter** that normalises BNF into the LL(2) shape jsonic can execute.\n\n---\n\n## 8. If the User Wants to Build This\n\n1. Pick a BNF dialect to accept (classic BNF, ISO EBNF, or ANTLR-lite).\n2. Build the grammar-AST and normaliser first; test it on small grammars\n   (arithmetic expressions, JSON itself) before wiring to jsonic.\n3. Emit to the `grammar()` JSON form rather than the chained `rule()`\n   API — it is easier to diff and easier to snapshot-test.\n4. Validate by round-tripping: feed the generated jsonic grammar a\n   corpus that the original BNF accepts, and compare parse outcomes.\n5. For actions, start with \"no actions → tree of raw tokens\" and layer\n   action support on top once the structural conversion is solid.\n\n### Verification Plan\n\n- Unit-test the normaliser on textbook grammars (JSON, arithmetic, a\n  tiny Lisp) and compare normalised output to hand-written LL(2)\n  versions.\n- Snapshot-test emitted `grammar()` specs.\n- End-to-end: load the emitted spec via `jsonic.grammar(spec)` in a\n  test and parse known-good inputs, asserting the produced tree.\n- Cross-check against `test/grammar.test.js` patterns — the existing\n  examples there are the most reliable reference for what jsonic will\n  accept.\n"
  },
  {
    "path": "doc/lsp-feasibility.md",
    "content": "# Feasibility Report: Language Server Protocol Support for Jsonic\n\n## Summary\n\nAdding a Language Server Protocol (LSP) implementation for jsonic is\n**feasible and well-matched to the existing parser architecture**, but it\nrequires one non-trivial extension: the parser must be able to **collect\nmultiple errors** and **keep going after a parse error**. Today it throws on\nthe first failure and unwinds.\n\nThis report covers two design questions that block a useful LSP:\n\n1. Can the jsonic parser be extended to collect multiple errors and recover\n   from parse errors?\n2. What sync-point mechanism should error recovery use — and how do we make it\n   general enough to survive jsonic's plugin model?\n\nThe short answer: **moderate difficulty**, with a clean design available by\nreusing existing parser concepts (rule stack, alternate group tags) rather\nthan inventing new ones.\n\n---\n\n## 1. Why LSP Needs This\n\nA Language Server reports *diagnostics* for an entire document. If the parser\nbails on the first `,` missing at line 3, the user gets one squiggle and\nnothing for the rest of the file. Every real-world LSP (TypeScript, Roslyn,\nRust-analyzer, tree-sitter-based servers, tolerant-php-parser) solves this\nthe same way: collect errors into a list, and after each error skip to a\n*sync point* where parsing can safely resume.\n\nJsonic is additionally extensible — plugins add rules, tokens, and\nbracket-like structures (HJSON, JSONL, CSV, TOML-style, custom DSLs). A\nhard-coded sync set like `{',', '}', ']'}` breaks as soon as a plugin\nintroduces a new terminator. The recovery mechanism has to be driven by the\ngrammar itself.\n\n---\n\n## 2. Current Error Model (Single-Shot, Fail-Fast)\n\nErrors are raised as `JsonicError` exceptions and immediately unwind the\nparser.\n\n| Location | Behaviour |\n|---|---|\n| `src/error.ts:32-44` | `JsonicError extends SyntaxError`; has `code`, `lineNumber`, `columnNumber`, `why` — already LSP-shaped. |\n| `src/parser.ts:145,191,198` | Three explicit `throw new JsonicError()` sites inside the main loop. |\n| `src/rules.ts:533` | `RuleSpec.bad()` throws when no alternate matches. |\n| `src/utility.ts:572-596` | `badlex()` wraps the lexer; converts `#BD` (bad) tokens into throws. |\n| `src/lexer.ts:1137-1159` | Lexer itself does **not** throw — it emits a `#BD` token. The throw is imposed by `badlex`. |\n| `src/types.ts:387-417` | `Context` has no `errs[]` field. |\n| `go/parser.go:158-168` | Go port mirrors the single-error model (`ctx.ParseErr`, not a slice). |\n\nThree things about this are **favourable** for adding recovery:\n\n1. The main parse loop in `src/parser.ts:84-202` is **iterative**, not\n   recursive. `rule.process(ctx, lex)` runs inside a `while (rule !==\n   NORULE)` loop, so there is no deep call stack to unwind — just a rule\n   stack array.\n2. Tokens already carry `err` and `why` diagnostic fields\n   (`src/types.ts:669-688`). Errors can travel on tokens without a side\n   channel.\n3. The lexer already distinguishes a *bad token* from a *throw* — the throw\n   is layered on top by `badlex`. Removing that layer in recovery mode is\n   local.\n\nWhat's missing: no `ctx.errs` accumulator, no try/catch around the main loop\nbody, no notion of \"after an error, where do I resume?\".\n\n---\n\n## 3. Parsing Model and Why It Can Resume\n\nJsonic is a **stack-based rule engine with 2-token lookahead**, not\nrecursive descent.\n\n- `RuleSpec` (`src/rules.ts:161-205`, `src/types.ts:300-336`) has two\n  ordered `AltSpec[]` lists: `open` and `close`.\n- An `AltSpec` (`src/types.ts:692-720`) carries:\n  - `s` — 1- or 2-token match sequence.\n  - `p` / `r` — push a child rule / replace current rule.\n  - `b` — backtrack count.\n  - `c` — condition closure.\n  - `n` — counter deltas.\n  - `g` — semantic group tags (`'open'`, `'close'`, `'comma'`, `'val'`,\n    `'pair'`, `'map'`, `'list'`, `'json'`, …), validated at\n    `src/rules.ts:706` and sorted at `src/rules.ts:727`.\n- `parse_alts()` (`src/rules.ts:558-715`) picks the first alternate whose\n  token bitsets match and whose condition passes.\n- `Context.rs` (`src/types.ts:408`, populated at `src/rules.ts:447`) is a\n  plain array indexed by depth `ctx.rsI`. Each entry is a live `Rule` with\n  its `spec`, `state` (`OPEN`/`CLOSE`), and counters.\n\nThe rule stack **is** the parser's structural context. It is walkable,\ninspectable, and already tracks exactly what jsonic thinks the legal\ncontinuations are. This is the ideal substrate for error recovery.\n\n---\n\n## 4. Multi-Error Collection — What Has to Change\n\nMinimum viable set of changes:\n\n| # | Change | Where |\n|---|---|---|\n| 1 | Add `errs: JsonicError[]` to `Context`. | `src/types.ts:387-417` |\n| 2 | In `badlex`, on `#BD`, push to `ctx.errs` and return a synthetic token instead of throwing. | `src/utility.ts:572-596` |\n| 3 | In `RuleSpec.bad()`, collect instead of throw; hand off to a recovery routine. | `src/rules.ts:533` |\n| 4 | Wrap the iteration body in `src/parser.ts:172-187` in try/catch; on catch, record the error and attempt recovery. | `src/parser.ts` |\n| 5 | Expose errors on the parse result: `{ value, errors }`, or leave errors on `ctx` for callers (LSP) to read. | `src/jsonic.ts` |\n| 6 | Make the new behaviour opt-in via a config flag. | `src/defaults.ts` / `src/types.ts` |\n\nThe opt-in flag matters: changing the default would break every existing\nconsumer who relies on \"first error throws\". An LSP host turns the flag on;\neveryone else is untouched.\n\n**Main risk:** after skipping to a sync point, the parser's local state\n(counters, pair context) may no longer agree with the grammar and produce\n*cascading* errors for the next several tokens. Every mature error-recovery\nimplementation has fought this. Mitigations:\n\n- Suppress new errors for *N* tokens after a recovery.\n- Prefer coarse sync (to the next structural closer) over fine sync (to the\n  next separator).\n- Cap recoveries per parse to prevent pathological loops.\n\n---\n\n## 5. Sync Points — The Design Question\n\nA naïve recovery says \"skip until `,` or `}` or `]`\". This is wrong for\njsonic because:\n\n- Plugins add new terminators (e.g. newline in JSONL, `;` in some dialects).\n- The correct sync set depends on *where* in the rule stack we are —\n  inside `[…]` the sync set includes `]` but not `}`.\n- A hard-coded set cannot be overridden without forking the parser.\n\nThe design question: **what is the smallest declarative mechanism that\nlets the grammar itself tell the recovery routine where it is safe to\nresume?**\n\n### 5.1 Candidate mechanisms considered\n\n| Mechanism | Summary | Verdict |\n|---|---|---|\n| Hard-coded token set | Skip until next `,` `}` `]`. | Too brittle; breaks under plugins. |\n| New `sync` field on `AltSpec` | Each alternate declares whether it is a sync edge. | Works, but duplicates information already present in `g` (group tags). |\n| Token-insertion (Roslyn-style) | On error, synthesise the missing token and continue. | Powerful but scope-creep; defer. |\n| Tree-sitter error nodes | GLR-style parallel branches wrap skipped regions. | Architecturally alien to jsonic's single-pass engine. |\n| tolerant-php-parser skip-tokens | Skip until a sync point, record the skipped span. | Closest fit; jsonic's engine is already panic-mode friendly. |\n| **Dynamic sync derived from rule stack + group tags** | Walk the live `ctx.rs` at error time; collect `Tin`s from close-state alternates whose `g` intersects a configured group set. | **Recommended.** |\n\n### 5.2 Why the group-tag approach wins\n\nThe `AltSpec.g` field is already populated across the whole built-in grammar\nwith semantic labels: `'close'`, `'comma'`, `'end'`, `'val'`, `'pair'`,\n`'map'`, `'list'`, `'json'`, etc. (`src/grammar.ts`, `src/rules.ts:706`).\nPlugins tag their own alternates with the same vocabulary because it already\ndrives debug output and `reg()` alternate filtering.\n\nThis means the grammar **already declares** its own sync points — we just\nhaven't been reading them.\n\n### 5.3 Recommended design\n\nA single new option subtree and a single new helper function.\n\n**Option surface** (added to `src/defaults.ts` / `src/types.ts:256-258`):\n\n```ts\nparse: {\n  recover: {\n    enabled: false,                       // opt-in; default off\n    syncGroups: ['close', 'comma', 'end'], // AltSpec.g tags that mark sync edges\n    syncTokens: [],                        // optional explicit token-name overrides\n    popUntilValid: true,                   // walk stack vs. fixed-depth pop\n    maxSkip: 64,                           // cap forward token skip per recovery\n    maxRecoveries: 32,                     // cap recoveries per parse\n  }\n}\n```\n\n**Helper** (new, colocated with `src/rules.ts`):\n\n```\ncomputeSyncTokens(ctx, cfg): Tin[]\n  for depth in ctx.rsI-1 .. 0\n    rule = ctx.rs[depth]\n    for alt in rule.spec.def.close\n      if intersects(alt.g, cfg.parse.recover.syncGroups)\n        collect alt's leading tokens into the sync set\n  return sync set\n```\n\nThe sync set is **computed per error** from the live stack, so it is fully\ndynamic: inside `{a:[1,` the set includes `,` `]` `}`; inside `[1,2`\nit includes `,` `]` but not `}`.\n\n**Recovery routine** (replaces the throw in `src/rules.ts:533`):\n\n```\nonRuleError:\n  ctx.errs.push(error)\n  if ctx.errs.length > cfg.parse.recover.maxRecoveries: give up and throw\n  syncSet = computeSyncTokens(ctx, cfg)\n  skip = 0\n  while ctx.t0.tin not in syncSet and skip < cfg.parse.recover.maxSkip:\n    advance lexer\n    skip++\n  if not found: give up and throw\n  pop ctx.rs until top rule accepts ctx.t0 in its close state\n  resume main loop\n```\n\n### 5.4 Why this is the right shape\n\n- **General.** It has no opinion about which tokens exist. Any plugin whose\n  alternates carry sensible `g` tags gets recovery for free.\n- **Configurable.** Consumers add sync semantics by extending `syncGroups`;\n  no code change required. Example: an HJSON plugin adds `'eol'` to its\n  terminating alternates and appends `'eol'` to `syncGroups`.\n- **Dynamic.** Sync set is a function of the rule stack at error time, not\n  a compile-time constant.\n- **Cheap.** With per-rule bitsets precomputed at config time, each\n  recovery is `O(stack depth)`.\n- **Composable.** Reuses existing `g` vocabulary; does not add a parallel\n  declaration mechanism.\n- **Minimally invasive.** One option subtree, one helper function, three\n  local edits in `rules.ts` / `parser.ts` / `utility.ts`.\n\n---\n\n## 6. Open Design Questions\n\nThese should be settled before implementation — none of them block\nfeasibility.\n\n1. **Token insertion.** Should recovery also synthesise a missing token\n   (e.g. an absent `,`) and keep the current rule running? This is\n   Roslyn-style and pairs well with jsonic's 2-token lookahead but\n   meaningfully enlarges the design. Recommend **deferring** to a second\n   iteration.\n2. **Structural sync fallback.** When `syncSet` is empty (deep stack, no\n   matching group), should recovery pop one rule unconditionally and retry?\n   Useful for pathological inputs.\n3. **Additive vs replacing `syncGroups`.** Plugins should almost certainly\n   *add* groups rather than replace the defaults. The options merge layer\n   (`src/jsonic.ts:172-220`) already supports this pattern via array\n   concat; confirm and document.\n4. **Error result shape.** Should `jsonic(src)` in recovery mode return\n   `{value, errors}`, or mutate the passed `meta`/`ctx`, or a third option?\n   An LSP only needs the errors — the value may be partial and is often\n   discarded.\n5. **Cascading-error suppression.** After a recovery, should subsequent\n   errors within the next *N* tokens be silently dropped, merged, or\n   reported? Affects diagnostic quality far more than any other tuning.\n\n---\n\n## 7. Recommended Roadmap\n\nStage the work so every step leaves the repo shippable.\n\n1. **Error list on context** — `ctx.errs`, off by default; no behavioural\n   change.\n2. **Opt-in recovery flag** — `parse.recover.enabled`, still no default\n   change.\n3. **Dynamic sync computation** — add helper, wire to `RuleSpec.bad()`\n   under the flag.\n4. **Lexer soft mode** — `badlex` stops throwing when `recover.enabled`;\n   emits `#BD` tokens that feed the same recovery path.\n5. **Result surface** — expose `errors` on the return shape in recovery\n   mode.\n6. **LSP server** — thin `vscode-languageserver/node` wrapper consuming\n   the above; diagnostics + document symbols for a v1, formatting and\n   hover for v2.\n7. **Go port parity** (later) — mirror the design in `go/parser.go`; the\n   rule-stack shape is already the same.\n\n---\n\n## 8. Conclusion\n\nThe jsonic parser was not designed for error recovery, but its core data\nstructures — stack-based rules, group-tagged alternates, non-throwing\nlexer, iterative main loop — align with a panic-mode recovery scheme\nalmost exactly. The recommended design adds one option subtree and one\nhelper function, preserves the existing fail-fast API as the default, and\nderives its sync behaviour from information the grammar already carries.\nThat is the right foundation for a Language Server.\n"
  },
  {
    "path": "doc/options.md",
    "content": "# Options Reference\n\nOptions are passed to `Jsonic.make()` to create a configured parser instance.\nAll fields are optional -- unset fields use defaults.\n\n```js\nconst j = Jsonic.make({\n  comment: { lex: false },\n  number: { hex: false }\n})\n```\n\n## `fixed`\n\nControls recognition of fixed structural tokens (`{`, `}`, `[`, `]`, `:`, `,`).\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable fixed token recognition |\n| `token` | object | (built-in) | Map of token name to source character |\n\n## `space`\n\nControls whitespace handling.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable space recognition |\n| `chars` | string | `\" \\t\"` | Characters treated as space |\n\n## `line`\n\nControls line ending handling.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable line recognition |\n| `chars` | string | `\"\\r\\n\"` | Characters treated as line endings |\n| `rowChars` | string | `\"\\n\"` | Characters that increment the row counter |\n| `single` | boolean | `false` | Generate a separate token per newline |\n\n## `text`\n\nControls unquoted text lexing.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable text matching |\n| `modify` | function[] | `[]` | Pipeline of value transformers applied after matching |\n\n## `number`\n\nControls numeric literal parsing.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable number matching |\n| `hex` | boolean | `true` | Support `0x` hexadecimal |\n| `oct` | boolean | `true` | Support `0o` octal |\n| `bin` | boolean | `true` | Support `0b` binary |\n| `sep` | string\\|null | `\"_\"` | Separator character (null to disable) |\n| `exclude` | RegExp | -- | Pattern to exclude from number matching |\n\n## `comment`\n\nControls comment handling.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable all comment lexing |\n| `def` | object | (see below) | Comment type definitions |\n\nDefault comment definitions:\n\n```js\n{\n  hash:  { line: true, start: '#' },\n  slash: { line: true, start: '//' },\n  block: { line: false, start: '/*', end: '*/' }\n}\n```\n\nEach definition has:\n\n| Field | Type | Description |\n|---|---|---|\n| `line` | boolean | `true` for line comments, `false` for block |\n| `start` | string | Start marker |\n| `end` | string | End marker (block comments only) |\n| `eatline` | boolean | Consume trailing newline after comment |\n\n## `string`\n\nControls quoted string parsing.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable string matching |\n| `chars` | string | `\"'\\\"\\`` | Quote characters |\n| `multiChars` | string | `` \"`\" `` | Characters that allow multiline strings |\n| `escapeChar` | string | `\"\\\\\"` | Escape character |\n| `escape` | object | (standard) | Escape sequence mappings |\n| `allowUnknown` | boolean | `true` | Allow unknown escape sequences |\n| `abandon` | boolean | `false` | On error, let next matcher try |\n| `replace` | object | -- | Character replacement map during scanning |\n\n## `map`\n\nControls object/map behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `extend` | boolean | `true` | Deep-merge duplicate keys |\n| `merge` | function | -- | Custom merge function: `(prev, curr) => result` |\n| `child` | boolean | `false` | Parse bare colon as `child$` key |\n\n## `list`\n\nControls array/list behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `property` | boolean | `true` | Allow key-value pairs in arrays |\n| `pair` | boolean | `false` | Push pairs as object elements |\n| `child` | boolean | `false` | Parse bare colon as child value |\n\n## `value`\n\nControls keyword recognition.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `true` | Enable value matching |\n| `def` | object | (see below) | Keyword definitions |\n\nDefault value definitions:\n\n```js\n{\n  true:  { val: true },\n  false: { val: false },\n  null:  { val: null }\n}\n```\n\nAdd custom keywords:\n\n```js\nJsonic.make({\n  value: {\n    def: {\n      yes: { val: true },\n      no:  { val: false }\n    }\n  }\n})\n```\n\n## `match`\n\nControls custom matcher tokens and values.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `lex` | boolean | `false` | Enable custom matchers |\n| `token` | object | -- | Map of token name to RegExp or matcher function |\n| `value` | object | -- | Map of value name to `{match, val?}` |\n\n## `rule`\n\nControls parser rule behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `start` | string | `\"val\"` | Name of the starting rule |\n| `finish` | boolean | `true` | Auto-close unclosed structures at EOF |\n| `maxmul` | number | `3` | Rule occurrence multiplier limit |\n| `include` | string | -- | Include only rules with these group tags |\n| `exclude` | string | -- | Exclude rules with these group tags |\n\n## `lex`\n\nControls global lexer behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `empty` | boolean | `true` | Allow empty source input |\n| `emptyResult` | any | `undefined` | Value returned for empty input |\n\n## `safe`\n\nControls security features.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `key` | boolean | `true` | Block `__proto__` and `constructor` keys |\n\n## `error`\n\nCustom error message templates, keyed by error code.\n\n```js\nJsonic.make({\n  error: { unexpected: 'bad character: ' }\n})\n```\n\n## `hint`\n\nAdditional explanatory text per error code, appended to error messages.\n\n## `debug`\n\nControls debug output.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `get_console` | function | -- | Returns the console object for logging |\n| `maxlen` | number | -- | Max output length for debug strings |\n| `print` | object | -- | `{config?, src?}` debug print options |\n"
  },
  {
    "path": "doc/plugins.md",
    "content": "# Writing Plugins\n\nPlugins extend jsonic by modifying the grammar, adding new token types,\nregistering custom matchers, or subscribing to parse events.\n\n## Plugin Structure\n\nA plugin is a function that receives a jsonic instance and optional\nconfiguration:\n\n```js\nfunction myPlugin(jsonic, options) {\n  // Modify the parser here\n}\n\nconst j = Jsonic.make()\nj.use(myPlugin, { key: 'value' })\n```\n\nPlugins are re-applied when a child instance is derived with `make()`.\n\n## Adding Tokens\n\nRegister a new fixed token by providing a name and source character:\n\n```js\nfunction tildePlugin(jsonic) {\n  const T_TILDE = jsonic.token('#TL', '~')\n}\n```\n\nToken names conventionally use `#XX` format. Built-in tokens:\n\n| Name | Src | Description |\n|---|---|---|\n| `#OB` | `{` | Open brace |\n| `#CB` | `}` | Close brace |\n| `#OS` | `[` | Open square |\n| `#CS` | `]` | Close square |\n| `#CL` | `:` | Colon |\n| `#CA` | `,` | Comma |\n| `#NR` | -- | Number |\n| `#ST` | -- | String |\n| `#TX` | -- | Text |\n| `#VL` | -- | Value (keyword) |\n| `#SP` | -- | Space |\n| `#LN` | -- | Line |\n| `#CM` | -- | Comment |\n| `#BD` | -- | Bad (error) |\n| `#ZZ` | -- | End |\n\n## Modifying Rules\n\nThe parser uses named rules, each with `open` and `close` alternate lists.\nAlternates match token patterns and fire actions.\n\n```js\nfunction myPlugin(jsonic) {\n  const T_TILDE = jsonic.token('#TL', '~')\n\n  jsonic.rule('val', (rs) => {\n    // Add a new alternate at the start of the open phase\n    rs.open.unshift({\n      // Match a tilde token\n      s: [[T_TILDE]],\n      // Action: set the node value\n      a: (rule) => {\n        rule.node = 42\n      }\n    })\n  })\n}\n```\n\n### Alternate Spec Fields\n\n| Field | Description |\n|---|---|\n| `s` | Token pattern to match (array of arrays of Tin) |\n| `a` | Action function: `(rule, ctx) => void` |\n| `p` | Push a new rule onto the stack by name |\n| `r` | Replace current rule with another |\n| `b` | Backtrack: number of tokens to put back |\n| `g` | Group tag string (e.g., `'json'`, `'jsonic,map'`) |\n| `h` | Custom handler: `(alt, rule, ctx) => alt` |\n| `e` | Error function: `(rule, ctx) => token` |\n\n### State Actions\n\nEach rule spec has four hook points:\n\n| Hook | When |\n|---|---|\n| `bo` | Before open -- runs before open alternates are tried |\n| `ao` | After open -- runs after an open alternate matches |\n| `bc` | Before close -- runs before close alternates are tried |\n| `ac` | After close -- runs after a close alternate matches |\n\n```js\njsonic.rule('map', (rs) => {\n  const original_ao = rs.ao\n  rs.ao = (rule, ctx) => {\n    if (original_ao) original_ao(rule, ctx)\n    console.log('opened a map at', rule.node)\n  }\n})\n```\n\n## Custom Matchers\n\nFor syntax that doesn't fit the built-in matchers, add a custom lexer matcher\nvia the `match` option:\n\n```js\nconst j = Jsonic.make({\n  match: {\n    lex: true,\n    value: {\n      date: {\n        match: /^\\d{4}-\\d{2}-\\d{2}/,\n        val: (res) => new Date(res[0])\n      }\n    }\n  }\n})\n\nj('d: 2024-01-15')  // { d: Date('2024-01-15') }\n```\n\n## Subscribing to Events\n\nPlugins can observe the parse process without modifying it:\n\n```js\nfunction loggingPlugin(jsonic) {\n  jsonic.sub({\n    lex: (token, rule, ctx) => {\n      console.log('lexed:', token)\n    },\n    rule: (rule, ctx) => {\n      console.log('rule:', rule.name, rule.state)\n    }\n  })\n}\n```\n\n## Token Sets\n\nAccess groups of tokens for use in alternate patterns:\n\n```js\nconst ignoreTokens = jsonic.tokenSet('IGNORE') // [#SP, #LN, #CM]\nconst valueTokens  = jsonic.tokenSet('VAL')    // [#TX, #NR, #ST, #VL]\nconst keyTokens    = jsonic.tokenSet('KEY')     // [#TX, #NR, #ST, #VL]\n```\n\n## Example: CSV Plugin\n\nA simplified CSV plugin that treats commas as separators and newlines as\nrow boundaries:\n\n```js\nfunction csvPlugin(jsonic, options) {\n  const sep = options?.sep ?? ','\n\n  // Remove default comment handling\n  jsonic.options({ comment: { lex: false } })\n\n  // Modify grammar to treat each line as a row\n  jsonic.rule('val', (rs) => {\n    // ... add alternates for row/cell parsing\n  })\n}\n```\n"
  },
  {
    "path": "doc/syntax.md",
    "content": "# Syntax Reference\n\njsonic accepts standard JSON and extends it with the relaxations described\nbelow. All extensions are optional -- valid JSON always parses correctly.\n\n## Objects\n\nStandard JSON objects work as expected. jsonic also supports:\n\n### Unquoted Keys\n\nKeys do not need quotes when they contain only word characters (letters,\ndigits, underscore) or spaces followed by a colon.\n\n```\n{a: 1, b: 2}         → {\"a\": 1, \"b\": 2}\n{my key: \"value\"}     → {\"my key\": \"value\"}\n```\n\n### Implicit Top-Level Object\n\nThe outer `{}` braces are optional when the input contains key-value pairs.\n\n```\na:1, b:2              → {\"a\": 1, \"b\": 2}\n```\n\n### Object Merging\n\nWhen the same key appears multiple times, object values are deep-merged.\n\n```\na:{b:1}, a:{c:2}      → {\"a\": {\"b\": 1, \"c\": 2}}\n```\n\n### Path Diving\n\nNested objects can be expressed inline using chained colons.\n\n```\na:b:1, a:c:2          → {\"a\": {\"b\": 1, \"c\": 2}}\n```\n\n## Arrays\n\nStandard JSON arrays work as expected. jsonic also supports:\n\n### Implicit Top-Level Array\n\nWhen the input contains comma- or newline-separated values without colons,\nit is parsed as an array.\n\n```\na, b, c               → [\"a\", \"b\", \"c\"]\n1, 2, 3               → [1, 2, 3]\n```\n\n### Named Properties in Arrays\n\nBy default, key-value pairs inside arrays are allowed.\n\n```\n[a:1, b:2]            → [{\"a\": 1}, {\"b\": 2}]\n```\n\nThis can be controlled with the `list.property` option.\n\n## Strings\n\n### Quoted Strings\n\nDouble quotes, single quotes, and backticks all work as string delimiters.\n\n```\n\"hello\"   'hello'   `hello`\n```\n\n### Unquoted Strings\n\nValues that are not numbers, booleans, or null are treated as unquoted strings.\nUnquoted strings extend to the next structural character (comma, colon,\nbracket, brace) or end of line.\n\n```\n{a: hello world}      → {\"a\": \"hello world\"}\n```\n\n### Multiline Strings\n\nBacktick strings can span multiple lines. Newlines are preserved.\n\n```\n`line one\nline two`             → \"line one\\nline two\"\n```\n\n### Indent-Adjusted Strings\n\nTriple-quoted strings (`'''...'''`) strip the common leading indentation.\n\n```\n  '''\n  line one\n  line two\n  '''                 → \"line one\\nline two\"\n```\n\n## Numbers\n\nAll standard JSON number formats, plus:\n\n| Format | Example | Value |\n|---|---|---|\n| Decimal | `42`, `3.14`, `.5` | 42, 3.14, 0.5 |\n| Signed | `+1`, `-1` | 1, -1 |\n| Scientific | `1e2`, `1.5e-3` | 100, 0.0015 |\n| Hexadecimal | `0xFF`, `0x0a` | 255, 10 |\n| Octal | `0o17` | 15 |\n| Binary | `0b1010` | 10 |\n| Separators | `1_000_000` | 1000000 |\n\n## Comments\n\nThree comment styles are supported by default:\n\n```\n# hash line comment\n// slash line comment\n/* block\n   comment */\n```\n\nComments are discarded and do not appear in the output.\n\n## Keywords\n\nThe following keywords are recognized (case-sensitive):\n\n| Keyword | Value |\n|---|---|\n| `true` | `true` |\n| `false` | `false` |\n| `null` | `null` |\n\nCustom keywords can be added via the `value.def` option.\n\n## Trailing Commas\n\nTrailing commas are always allowed and ignored.\n\n```\n{a:1, b:2,}           → {\"a\": 1, \"b\": 2}\n[1, 2, 3,]            → [1, 2, 3]\n```\n\n## Auto-Close\n\nUnclosed `{` or `[` at end-of-input are closed automatically. This can be\ndisabled with the `rule.finish` option.\n\n```\n{a:1                  → {\"a\": 1}\n[1, 2                 → [1, 2]\n```\n\n## Escape Sequences\n\nStandard JSON escape sequences work in all quoted strings:\n\n| Sequence | Character |\n|---|---|\n| `\\\\` | Backslash |\n| `\\\"` | Double quote |\n| `\\'` | Single quote |\n| `\\n` | Newline |\n| `\\r` | Carriage return |\n| `\\t` | Tab |\n| `\\b` | Backspace |\n| `\\f` | Form feed |\n| `\\uXXXX` | Unicode code point |\n| `\\xXX` | ASCII code point |\n"
  },
  {
    "path": "docs/404.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/8.0a081a71.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/2.34930047.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><div class=\"theme-default-content\"><h1>404</h1> <blockquote>Looks like we've got some broken links.</blockquote> <a href=\"/\" class=\"router-link-active\">\n      Take me home.\n    </a></div></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/8.0a081a71.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/CNAME",
    "content": "jsonic.richardrodger.com"
  },
  {
    "path": "docs/assets/css/0.styles.610e9dca.css",
    "content": "code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.theme-default-content code{color:#476582;padding:.25rem .5rem;margin:0;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.theme-default-content code .token.deleted{color:#ec5975}.theme-default-content code .token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.4;padding:1.25rem 1.5rem;margin:.85rem 0;background-color:#282c34;border-radius:6px;overflow:auto}.theme-default-content pre[class*=language-] code,.theme-default-content pre code{color:#fff;padding:0;background-color:transparent;border-radius:0}div[class*=language-]{position:relative;background-color:#282c34;border-radius:6px}div[class*=language-] .highlight-lines{-webkit-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlighted{background-color:rgba(0,0,0,.66)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent;position:relative;z-index:1}div[class*=language-]:before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:hsla(0,0%,100%,.4)}div[class*=language-]:not(.line-numbers-mode) .line-numbers-wrapper{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlighted{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlighted:before{content:\" \";position:absolute;z-index:3;left:0;top:0;display:block;width:3.5rem;height:100%;background-color:rgba(0,0,0,.66)}div[class*=language-].line-numbers-mode pre{padding-left:4.5rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers-wrapper{position:absolute;top:0;width:3.5rem;text-align:center;color:hsla(0,0%,100%,.3);padding:1.25rem 0;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers-wrapper br{-webkit-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number{position:relative;z-index:4;-webkit-user-select:none;user-select:none;font-size:.85em}div[class*=language-].line-numbers-mode:after{content:\"\";position:absolute;z-index:2;top:0;left:0;width:3.5rem;height:100%;border-radius:6px 0 0 6px;border-right:1px solid rgba(0,0,0,.66);background-color:#282c34}div[class~=language-js]:before{content:\"js\"}div[class~=language-ts]:before{content:\"ts\"}div[class~=language-html]:before{content:\"html\"}div[class~=language-md]:before{content:\"md\"}div[class~=language-vue]:before{content:\"vue\"}div[class~=language-css]:before{content:\"css\"}div[class~=language-sass]:before{content:\"sass\"}div[class~=language-scss]:before{content:\"scss\"}div[class~=language-less]:before{content:\"less\"}div[class~=language-stylus]:before{content:\"stylus\"}div[class~=language-go]:before{content:\"go\"}div[class~=language-java]:before{content:\"java\"}div[class~=language-c]:before{content:\"c\"}div[class~=language-sh]:before{content:\"sh\"}div[class~=language-yaml]:before{content:\"yaml\"}div[class~=language-py]:before{content:\"py\"}div[class~=language-docker]:before{content:\"docker\"}div[class~=language-dockerfile]:before{content:\"dockerfile\"}div[class~=language-makefile]:before{content:\"makefile\"}div[class~=language-javascript]:before{content:\"js\"}div[class~=language-typescript]:before{content:\"ts\"}div[class~=language-markup]:before{content:\"html\"}div[class~=language-markdown]:before{content:\"md\"}div[class~=language-json]:before{content:\"json\"}div[class~=language-ruby]:before{content:\"rb\"}div[class~=language-python]:before{content:\"py\"}div[class~=language-bash]:before{content:\"sh\"}div[class~=language-php]:before{content:\"php\"}.custom-block .custom-block-title{font-weight:600;margin-bottom:-.4rem}.custom-block.danger,.custom-block.tip,.custom-block.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-block.tip{background-color:#f3f5f7;border-color:#42b983}.custom-block.warning{background-color:rgba(255,229,100,.3);border-color:#e7c000;color:#6b5900}.custom-block.warning .custom-block-title{color:#b29400}.custom-block.warning a{color:#2c3e50}.custom-block.danger{background-color:#ffe6e6;border-color:#c00;color:#4d0000}.custom-block.danger .custom-block-title{color:#900}.custom-block.danger a{color:#2c3e50}.custom-block.details{display:block;position:relative;border-radius:2px;margin:1.6em 0;padding:1.6em;background-color:#eee}.custom-block.details h4{margin-top:0}.custom-block.details figure:last-child,.custom-block.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-block.details summary{outline:none;cursor:pointer}.arrow{display:inline-block;width:0;height:0}.arrow.up{border-bottom:6px solid #ccc}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.down{border-top:6px solid #ccc}.arrow.right{border-left:6px solid #ccc}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.left{border-right:6px solid #ccc}.theme-default-content:not(.custom){max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.theme-default-content:not(.custom){padding:2rem}}@media (max-width:419px){.theme-default-content:not(.custom){padding:1.5rem}}.table-of-contents .badge{vertical-align:middle}body,html{padding:0;margin:0;background-color:#fff}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:16px;color:#2c3e50}.page{padding-left:20rem}.navbar{z-index:20;right:0;height:3.6rem;background-color:#fff;box-sizing:border-box;border-bottom:1px solid #eaecef}.navbar,.sidebar-mask{position:fixed;top:0;left:0}.sidebar-mask{z-index:9;width:100vw;height:100vh;display:none}.sidebar{font-size:16px;background-color:#fff;width:20rem;position:fixed;z-index:10;margin:0;top:3.6rem;left:0;bottom:0;box-sizing:border-box;border-right:1px solid #eaecef;overflow-y:auto}.theme-default-content:not(.custom)>:first-child{margin-top:3.6rem}.theme-default-content:not(.custom) a:hover{text-decoration:underline}.theme-default-content:not(.custom) p.demo{padding:1rem 1.5rem;border:1px solid #ddd;border-radius:4px}.theme-default-content:not(.custom) img{max-width:100%}.theme-default-content.custom{padding:0;margin:0}.theme-default-content.custom img{max-width:100%}a{font-weight:500;text-decoration:none}a,p a code{color:#3eaf7c}p a code{font-weight:400}kbd{background:#eee;border:.15rem solid #ddd;border-bottom:.25rem solid #ddd;border-radius:.15rem;padding:0 .15em}blockquote{font-size:1rem;color:#999;border-left:.2rem solid #dfe2e5;margin:1rem 0;padding:.25rem 0 .25rem 1rem}blockquote>p{margin:0}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}.theme-default-content:not(.custom)>h1,.theme-default-content:not(.custom)>h2,.theme-default-content:not(.custom)>h3,.theme-default-content:not(.custom)>h4,.theme-default-content:not(.custom)>h5,.theme-default-content:not(.custom)>h6{margin-top:-3.1rem;padding-top:4.6rem;margin-bottom:0}.theme-default-content:not(.custom)>h1:first-child,.theme-default-content:not(.custom)>h2:first-child,.theme-default-content:not(.custom)>h3:first-child,.theme-default-content:not(.custom)>h4:first-child,.theme-default-content:not(.custom)>h5:first-child,.theme-default-content:not(.custom)>h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.theme-default-content:not(.custom)>h1:first-child+.custom-block,.theme-default-content:not(.custom)>h1:first-child+p,.theme-default-content:not(.custom)>h1:first-child+pre,.theme-default-content:not(.custom)>h2:first-child+.custom-block,.theme-default-content:not(.custom)>h2:first-child+p,.theme-default-content:not(.custom)>h2:first-child+pre,.theme-default-content:not(.custom)>h3:first-child+.custom-block,.theme-default-content:not(.custom)>h3:first-child+p,.theme-default-content:not(.custom)>h3:first-child+pre,.theme-default-content:not(.custom)>h4:first-child+.custom-block,.theme-default-content:not(.custom)>h4:first-child+p,.theme-default-content:not(.custom)>h4:first-child+pre,.theme-default-content:not(.custom)>h5:first-child+.custom-block,.theme-default-content:not(.custom)>h5:first-child+p,.theme-default-content:not(.custom)>h5:first-child+pre,.theme-default-content:not(.custom)>h6:first-child+.custom-block,.theme-default-content:not(.custom)>h6:first-child+p,.theme-default-content:not(.custom)>h6:first-child+pre{margin-top:2rem}h1:focus .header-anchor,h1:hover .header-anchor,h2:focus .header-anchor,h2:hover .header-anchor,h3:focus .header-anchor,h3:hover .header-anchor,h4:focus .header-anchor,h4:hover .header-anchor,h5:focus .header-anchor,h5:hover .header-anchor,h6:focus .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid #eaecef}h3{font-size:1.35rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a.header-anchor:focus,a.header-anchor:hover{text-decoration:none}.line-number,code,kbd{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid #eaecef}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background-color:#f6f8fa}td,th{border:1px solid #dfe2e5;padding:.6em 1em}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.no-navbar .theme-default-content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .page{padding-left:0}}@media (max-width:959px){.sidebar{font-size:15px;width:16.4rem}.page{padding-left:16.4rem}}@media (max-width:719px){.sidebar{top:0;padding-top:3.6rem;transform:translateX(-100%);transition:transform .2s ease}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}div[class~=language-jsonic]:before{content:\"jsonic\"}#nprogress{pointer-events:none}#nprogress .bar{background:#3eaf7c;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #3eaf7c,0 0 5px #3eaf7c;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border-color:#3eaf7c transparent transparent #3eaf7c;border-style:solid;border-width:2px;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.icon.outbound{color:#aaa;display:inline-block;vertical-align:middle;position:relative;top:-1px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.home{padding:3.6rem 2rem 0;max-width:960px;margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.8rem auto}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:#6a8bad}.home .hero .action-button{display:inline-block;font-size:1.2rem;color:#fff;background-color:#3eaf7c;padding:.8rem 1.6rem;border-radius:4px;transition:background-color .1s ease;box-sizing:border-box;border-bottom:1px solid #389d70}.home .hero .action-button:hover{background-color:#4abf8a}.home .features{border-top:1px solid #eaecef;padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:#3a5169}.home .feature p{color:#4e6e8e}.home .footer{padding:2.5rem;border-top:1px solid #eaecef;text-align:center;color:#4e6e8e}@media (max-width:719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width:419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.search-box{display:inline-block;position:relative;margin-right:1rem}.search-box input{cursor:text;width:10rem;height:2rem;color:#4e6e8e;display:inline-block;border:1px solid #cfd4db;border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all .2s ease;background:#fff url(/assets/img/search.83621669.svg) .6rem .5rem no-repeat;background-size:1rem}.search-box input:focus{cursor:auto;border-color:#3eaf7c}.search-box .suggestions{background:#fff;width:20rem;position:absolute;top:2rem;border:1px solid #cfd4db;border-radius:6px;padding:.4rem;list-style-type:none}.search-box .suggestions.align-right{right:0}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion a{white-space:normal;color:#5d82a6}.search-box .suggestion a .page-title{font-weight:600}.search-box .suggestion a .header{font-size:.9em;margin-left:.25em}.search-box .suggestion.focused{background-color:#f3f4f5}.search-box .suggestion.focused a{color:#3eaf7c}@media (max-width:959px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}.search-box input:focus{cursor:text;left:0;width:10rem}}@media (-ms-high-contrast:none){.search-box input{height:2rem}}@media (max-width:959px) and (min-width:719px){.search-box .suggestions{left:0}}@media (max-width:719px){.search-box{margin-right:0}.search-box input{left:1rem}.search-box .suggestions{right:0}}@media (max-width:419px){.search-box .suggestions{width:calc(100vw - 4rem)}.search-box input:focus{width:8rem}}.sidebar-button{cursor:pointer;display:none;width:1.25rem;height:1.25rem;position:absolute;padding:.6rem;top:.6rem;left:1rem}.sidebar-button .icon{display:block;width:1.25rem;height:1.25rem}@media (max-width:719px){.sidebar-button{display:block}}.dropdown-enter,.dropdown-leave-to{height:0!important}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper .dropdown-title,.dropdown-wrapper .mobile-dropdown-title{display:block;font-size:.9rem;font-family:inherit;cursor:inherit;padding:inherit;line-height:1.4rem;background:transparent;border:none;font-weight:500;color:#2c3e50}.dropdown-wrapper .dropdown-title:hover,.dropdown-wrapper .mobile-dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .arrow,.dropdown-wrapper .mobile-dropdown-title .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.dropdown-wrapper .mobile-dropdown-title{display:none;font-weight:600}.dropdown-wrapper .mobile-dropdown-title font-size inherit:hover{color:#3eaf7c}.dropdown-wrapper .nav-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .nav-dropdown .dropdown-item h4{margin:.45rem 0 0;border-top:1px solid #eee;padding:1rem 1.5rem .45rem 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper{padding:0;list-style:none}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper .dropdown-subitem{font-size:.9em}.dropdown-wrapper .nav-dropdown .dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active,.dropdown-wrapper .nav-dropdown .dropdown-item a:hover{color:#3eaf7c}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{content:\"\";width:0;height:0;border-left:5px solid #3eaf7c;border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4{margin-top:0;padding-top:0;border-top:0}@media (max-width:719px){.dropdown-wrapper.open .dropdown-title{margin-bottom:.5rem}.dropdown-wrapper .dropdown-title{display:none}.dropdown-wrapper .mobile-dropdown-title{display:block}.dropdown-wrapper .nav-dropdown{transition:height .1s ease-out;overflow:hidden}.dropdown-wrapper .nav-dropdown .dropdown-item h4{border-top:0;margin-top:0;padding-top:0}.dropdown-wrapper .nav-dropdown .dropdown-item>a,.dropdown-wrapper .nav-dropdown .dropdown-item h4{font-size:15px;line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem{font-size:14px;padding-left:1rem}}@media (min-width:719px){.dropdown-wrapper{height:1.8rem}.dropdown-wrapper.open .nav-dropdown,.dropdown-wrapper:hover .nav-dropdown{display:block!important}.dropdown-wrapper.open:blur{display:none}.dropdown-wrapper .nav-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:#fff;padding:.6rem 0;border:1px solid;border-color:#ddd #ddd #ccc;text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}}.nav-links{display:inline-block}.nav-links a{line-height:1.4rem;color:inherit}.nav-links a.router-link-active,.nav-links a:hover{color:#3eaf7c}.nav-links .nav-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:2rem}.nav-links .nav-item:first-child{margin-left:0}.nav-links .repo-link{margin-left:1.5rem}@media (max-width:719px){.nav-links .nav-item,.nav-links .repo-link{margin-left:0}}@media (min-width:719px){.nav-links a.router-link-active,.nav-links a:hover{color:#2c3e50}.nav-item>a:not(.external).router-link-active,.nav-item>a:not(.external):hover{margin-bottom:-2px;border-bottom:2px solid #46bd87}}.navbar{padding:.7rem 1.5rem;line-height:2.2rem}.navbar a,.navbar img,.navbar span{display:inline-block}.navbar .logo{height:2.2rem;min-width:2.2rem;margin-right:.8rem;vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:#2c3e50;position:relative}.navbar .links{padding-left:1.5rem;box-sizing:border-box;background-color:#fff;white-space:nowrap;font-size:.9rem;position:absolute;right:1.5rem;top:.7rem;display:flex}.navbar .links .search-box{flex:0 0 auto;vertical-align:top}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .links{padding-left:1.5rem}.navbar .site-name{width:calc(100vw - 9.4rem);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}}.page-edit{max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.page-edit{padding:2rem}}@media (max-width:419px){.page-edit{padding:1.5rem}}.page-edit{padding-top:1rem;padding-bottom:1rem;overflow:auto}.page-edit .edit-link{display:inline-block}.page-edit .edit-link a{color:#4e6e8e;margin-right:.25rem}.page-edit .last-updated{float:right;font-size:.9em}.page-edit .last-updated .prefix{font-weight:500;color:#4e6e8e}.page-edit .last-updated .time{font-weight:400;color:#767676}@media (max-width:719px){.page-edit .edit-link{margin-bottom:.5rem}.page-edit .last-updated{font-size:.8em;float:none;text-align:left}}.page-nav{max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.page-nav{padding:2rem}}@media (max-width:419px){.page-nav{padding:1.5rem}}.page-nav{padding-top:1rem;padding-bottom:0}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid #eaecef;padding-top:1rem;overflow:auto}.page-nav .next{float:right}.page{padding-bottom:2rem;display:block}.sidebar-group .sidebar-group{padding-left:.5em}.sidebar-group:not(.collapsable) .sidebar-heading:not(.clickable){cursor:auto;color:inherit}.sidebar-group.is-sub-group{padding-left:0}.sidebar-group.is-sub-group>.sidebar-heading{font-size:.95em;line-height:1.4;font-weight:400;padding-left:2rem}.sidebar-group.is-sub-group>.sidebar-heading:not(.clickable){opacity:.5}.sidebar-group.is-sub-group>.sidebar-group-items{padding-left:1rem}.sidebar-group.is-sub-group>.sidebar-group-items>li>.sidebar-link{font-size:.95em;border-left:none}.sidebar-group.depth-2>.sidebar-heading{border-left:none}.sidebar-heading{color:#2c3e50;transition:color .15s ease;cursor:pointer;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0;border-left:.25rem solid transparent}.sidebar-heading.open,.sidebar-heading:hover{color:inherit}.sidebar-heading .arrow{position:relative;top:-.12em;left:.5em}.sidebar-heading.clickable.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-heading.clickable:hover{color:#3eaf7c}.sidebar-group-items{transition:height .1s ease-out;font-size:.95em;overflow:hidden}.sidebar .sidebar-sub-headers{padding-left:1rem;font-size:.95em}a.sidebar-link{font-size:1em;font-weight:400;display:inline-block;color:#2c3e50;border-left:.25rem solid transparent;padding:.35rem 1rem .35rem 1.25rem;line-height:1.4;width:100%;box-sizing:border-box}a.sidebar-link:hover{color:#3eaf7c}a.sidebar-link.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-group a.sidebar-link{padding-left:2rem}.sidebar-sub-headers a.sidebar-link{padding-top:.25rem;padding-bottom:.25rem;border-left:none}.sidebar-sub-headers a.sidebar-link.active{font-weight:500}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .nav-links{display:none;border-bottom:1px solid #eaecef;padding:.5rem 0 .75rem}.sidebar .nav-links a{font-weight:600}.sidebar .nav-links .nav-item,.sidebar .nav-links .repo-link{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar>.sidebar-links{padding:1.5rem 0}.sidebar>.sidebar-links>li>a.sidebar-link{font-size:1.1em;line-height:1.7;font-weight:700}.sidebar>.sidebar-links>li:not(:first-child){margin-top:.75rem}@media (max-width:719px){.sidebar .nav-links{display:block}.sidebar .nav-links .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{top:calc(1rem - 2px)}.sidebar>.sidebar-links{padding:1rem 0}}span.jsn-name-self{font-family:Karma,Verdana;color:#a61f53;padding:0 4px}.badge[data-v-15b7b770]{display:inline-block;font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:#fff}.badge.green[data-v-15b7b770],.badge.tip[data-v-15b7b770],.badge[data-v-15b7b770]{background-color:#42b983}.badge.error[data-v-15b7b770]{background-color:#da5961}.badge.warn[data-v-15b7b770],.badge.warning[data-v-15b7b770],.badge.yellow[data-v-15b7b770]{background-color:#e7c000}.badge+.badge[data-v-15b7b770]{margin-left:5px}.theme-code-block[data-v-759a7d02]{display:none}.theme-code-block__active[data-v-759a7d02]{display:block}.theme-code-block>pre[data-v-759a7d02]{background-color:orange}.theme-code-group__nav[data-v-deefee04]{margin-bottom:-35px;background-color:#282c34;padding-bottom:22px;border-top-left-radius:6px;border-top-right-radius:6px;padding-left:10px;padding-top:10px}.theme-code-group__ul[data-v-deefee04]{margin:auto 0;padding-left:0;display:inline-flex;list-style:none}.theme-code-group__nav-tab[data-v-deefee04]{border:0;padding:5px;cursor:pointer;background-color:transparent;font-size:.85em;line-height:1.4;color:hsla(0,0%,100%,.9);font-weight:600}.theme-code-group__nav-tab-active[data-v-deefee04]{border-bottom:1px solid #42b983}.pre-blank[data-v-deefee04]{color:#42b983}"
  },
  {
    "path": "docs/assets/js/10.88d9a57b.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[10],{193:function(t,s,r){\"use strict\";r.r(s);var a=r(6),e=Object(a.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"custom-parsers\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#custom-parsers\"}},[this._v(\"#\")]),this._v(\" Custom parsers\")])])}),[],!1,null,null,null);s.default=e.exports}}]);"
  },
  {
    "path": "docs/assets/js/11.7988a5fa.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[11],{195:function(t,s,a){\"use strict\";a.r(s);var n=a(6),e=Object(n.a)({},(function(){var t=this,s=t._self._c;return s(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[s(\"h1\",{attrs:{id:\"getting-started\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#getting-started\"}},[t._v(\"#\")]),t._v(\" Getting Started\")]),t._v(\" \"),s(\"p\",[t._v(\"Make sure you have \"),s(\"name-self\"),t._v(\" \"),s(\"a\",{attrs:{href:\"install\"}},[t._v(\"installed\")]),t._v(\". The installation\\ninstructions cover your various options and environments in more\\ndetail.\")],1),t._v(\" \"),s(\"p\",[t._v(\"Then load into your source code:\")]),t._v(\" \"),s(\"h6\",{attrs:{id:\"use-require-if-you-re-writing-for-node-hjs\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#use-require-if-you-re-writing-for-node-hjs\"}},[t._v(\"#\")]),t._v(\" use \"),s(\"code\",[t._v(\"require\")]),t._v(\" if you're writing for Node.hjs:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"const\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"h6\",{attrs:{id:\"use-import-if-you-re-writing-for-a-browser\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#use-import-if-you-re-writing-for-a-browser\"}},[t._v(\"#\")]),t._v(\" use \"),s(\"code\",[t._v(\"import\")]),t._v(\" if you're writing for a browser:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic'\")]),t._v(\"\\n\")])])]),s(\"h2\",{attrs:{id:\"parse-some-easy-going-json\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#parse-some-easy-going-json\"}},[t._v(\"#\")]),t._v(\" Parse some easy-going JSON!\")]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" variable is a function that takes in a string and returns\\nan object:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" data \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:1'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\nconsole\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"log\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"JSON\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"stringify\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"data\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// prints {\"a\":1}')]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"You pass in your jsonic source as a string. The string is parsed and\\nconverted into a JavaScript variable.\")]),t._v(\" \"),s(\"h2\",{attrs:{id:\"what-does-jsonic-syntax-give-you\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#what-does-jsonic-syntax-give-you\"}},[t._v(\"#\")]),t._v(\" What does \"),s(\"em\",[t._v(\"jsonic\")]),t._v(\" syntax give you?\")]),t._v(\" \"),s(\"p\",[t._v(\"Standard JSON syntax \"),s(\"em\",[t._v(\"almost\")]),t._v(\" provides a good developer experience,\\nbut not quite. We need a few extra things:\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"trailing-commas-are-ignored\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#trailing-commas-are-ignored\"}},[t._v(\"#\")]),t._v(\" Trailing commas are ignored\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'[ 1, 2, ]'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === [1, 2]\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\\'{ \"a\":3, \"b\":4, }\\'')]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\":3, \"b\":4}')]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Just like a modern JavaScript. This one you can't live without.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"object-keys-don-t-need-to-to-be-quoted\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#object-keys-don-t-need-to-to-be-quoted\"}},[t._v(\"#\")]),t._v(\" Object keys don't need to to be quoted\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'{a:1}'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\":1}')]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Now object keys are more readable.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"single-quote-strings-work\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#single-quote-strings-work\"}},[t._v(\"#\")]),t._v(\" Single quote strings work\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'\\\"a\\\"'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'a'\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\\"'a'\\\"\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'a'\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Just like normal JavaScript.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"backtick-multi-line-strings-work\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#backtick-multi-line-strings-work\"}},[t._v(\"#\")]),t._v(\" Backtick multi-line strings work\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\":1, \"b\":2}')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"{\\n  a: 1,\\n  b: 2,\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Again, just like JavaScript.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"you-get-all-the-comments\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#you-get-all-the-comments\"}},[t._v(\"#\")]),t._v(\" You get all the comments\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\":1, \"b\":2}')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"{\\n  a: 1,\\n  // A comment\\n  # Also a comment\\n  /*\\n   * Still a comment\\n   */\\n  b: 2,\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Now you can comment your configuration files properly.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"actually-who-needs-commas-when-newlines-will-do\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#actually-who-needs-commas-when-newlines-will-do\"}},[t._v(\"#\")]),t._v(\" Actually, who needs commas when newlines will do\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === { \"a\": 1, \"b\": [ 2, 3 ] }')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"{\\n  a: 1\\n  b: [\\n    2\\n    3\\n  ]\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Why should YAML have all the fun?\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"and-quoting-every-string-is-tedious-right\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#and-quoting-every-string-is-tedious-right\"}},[t._v(\"#\")]),t._v(\" And quoting every string is tedious, right?\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\":\"foo\", \"b\":\"bar\"}')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'{a:foo, b:bar}'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"When a value isn't a number, \"),s(\"code\",[t._v(\"true\")]),t._v(\", \"),s(\"code\",[t._v(\"false\")]),t._v(\", or \"),s(\"code\",[t._v(\"null\")]),t._v(\", it has to be a string.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"don-t-worry-about-spaces-inside-text-either\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#don-t-worry-about-spaces-inside-text-either\"}},[t._v(\"#\")]),t._v(\" Don't worry about spaces inside text either\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === { \"a\": \"foo bar\" }')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'{a: foo bar }'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Surrounding space is trimmed as you would expect.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"implicit-objects-and-arrays-at-the-top-level\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implicit-objects-and-arrays-at-the-top-level\"}},[t._v(\"#\")]),t._v(\" Implicit objects and arrays at the top level\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\":1, \"b\":2}')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:1,b:2'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === [\"a\", \"b\"]')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a,b'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"This is very convenient for quickly defining small objects.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"all-the-javascript-numbers-and-string-escapes\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#all-the-javascript-numbers-and-string-escapes\"}},[t._v(\"#\")]),t._v(\" All the JavaScript numbers and string escapes\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === [20.0, 20, 20, 20, 20]\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'20.0, 2e1, 0x14, 0o24, 0b10100'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['a', 'a', 'a']\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\\'\"\\\\x61\", \"\\\\u0061\", \"\\\\u{000061}\"\\'')]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Parity with JavaScript string handling is important to \"),s(\"name-self\"),t._v(\".\")],1),t._v(\" \"),s(\"h3\",{attrs:{id:\"block-text-with-indent-removal\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#block-text-with-indent-removal\"}},[t._v(\"#\")]),t._v(\" Block text with indent removal\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'ship!\\\\ndish!\\\\nball!'\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\n  '''\\n  ship!\\n  dish!\\n  ball!\\n  '''\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"A great idea from \"),s(\"a\",{attrs:{href:\"https://hjson.github.io/\",target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(\"HJson\"),s(\"OutboundLink\")],1),t._v(\", so we had to have it!\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"deep-property-one-liners\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#deep-property-one-liners\"}},[t._v(\"#\")]),t._v(\" Deep property one liners\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === { \"a\": { \"b\": { \"c\": 1 } } }')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a: b: c: 1'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Also a great idea from the \"),s(\"a\",{attrs:{href:\"https://cuelang.org/\",target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(\"Cue configuration language\"),s(\"OutboundLink\")],1),t._v(\".\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"merging-of-duplicate-properties\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#merging-of-duplicate-properties\"}},[t._v(\"#\")]),t._v(\" Merging of duplicate properties\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": {\"b\":1, \"c\":2, \"d\": 3}}')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\n  a: {b: 1}\\n  a: {c: 2}\\n  a: d: 3\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"This is super useful when writing system specifications.\")]),t._v(\" \"),s(\"h2\",{attrs:{id:\"developer-friendly-error-messages\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#developer-friendly-error-messages\"}},[t._v(\"#\")]),t._v(\" Developer-friendly error messages\")]),t._v(\" \"),s(\"p\",[t._v(\"Let's give \"),s(\"name-self\"),t._v(\" a syntax error:\")],1),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"{\\n  a: 1\\n  ]\\n}\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Here's what the error looks like:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-sh extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-sh\"}},[s(\"code\",[t._v(\"JsonicError \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),t._v(\"SyntaxError\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),t._v(\": \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),t._v(\"jsonic/unexpected\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),t._v(\": unexpected character\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"s\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\": \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\"]\"')]),t._v(\"\\n  --\"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\">\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"<\")]),t._v(\"no-file\"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\">\")]),t._v(\":2:2\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"|\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"1\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"|\")]),t._v(\"   a: \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"1\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"2\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"|\")]),t._v(\"   \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),t._v(\"\\n        ^ unexpected character\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"s\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\": \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\"]\"')]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"3\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"|\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"4\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"|\")]),t._v(\" \\n  The character\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"s\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\"]\"')]),t._v(\" were not expected at this point as they \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"do\")]),t._v(\" not\\n  match the expected syntax, even under the relaxed jsonic rules. If it\\n  is not obviously wrong, the actual syntax error may be elsewhere. Try\\n  commenting out larger areas around this point \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"until\")]),t._v(\" you get no errors,\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"then\")]),t._v(\" remove the comments \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"in\")]),t._v(\" small sections \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"until\")]),t._v(\" you \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"find\")]),t._v(\" the\\n  offending syntax. NOTE: Also check \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"if\")]),t._v(\" any plugins you are using\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"expect\")]),t._v(\" different syntax \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"in\")]),t._v(\" this case.\\n  https://jsonic.richardrodger.com\\n  --internal: \"),s(\"span\",{pre:!0,attrs:{class:\"token assign-left variable\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\"pair~close\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\";\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token assign-left variable\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"#CS; plugins=--\")]),t._v(\"\\n\\nat Object.\"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"<\")]),t._v(\"anonymous\"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\">\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"~/jsonic/test/syntax-error.js:2:1\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"..\")]),t._v(\".\\n  code: \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'unexpected'\")]),t._v(\",\\n  details: \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" state: \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'close'\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\",\\n  meta: \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\",\\n  fileName: undefined,\\n  lineNumber: \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"2\")]),t._v(\",\\n  columnNumber: \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"2\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\\n\")])])]),s(\"h2\",{attrs:{id:\"nice-but-what-does-it-all-cost\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#nice-but-what-does-it-all-cost\"}},[t._v(\"#\")]),t._v(\" Nice, but what does it all cost?\")]),t._v(\" \"),s(\"p\",[t._v(\"Jsonic is not going to be as fast at the builtin in JSON\\nparser. Jsonic is for inline data in source code, settings, options,\\nand configuration. Even if you have tens of thousands of line of\\nconfiguration, Jsonic is \"),s(\"a\",{attrs:{href:\"/ref/performance\"}},[t._v(\"plenty fast enough\")]),t._v(\".\")]),t._v(\" \"),s(\"p\",[t._v(\"Fine, but does it scale? Yes, Jsonic is \"),s(\"code\",[t._v(\"O(n)\")]),t._v(\". Larger and larger\\nsource text won't make the world implode.\")]),t._v(\" \"),s(\"p\",[t._v(\"Memory usage? It doesn't leak.\")]),t._v(\" \"),s(\"p\",[t._v(\"What about the call stack? All good. \"),s(\"name-self\"),t._v(\" doesn't use\\nrecursive functions (it's iterative) so can handle data of any depth,\\nat any depth in the call stack of your system.\")],1),t._v(\" \"),s(\"p\",[t._v(\"Every \"),s(\"name-self\"),t._v(\" release is measured for complexity, memory, and\\nstack safety.\")],1),t._v(\" \"),s(\"p\",[t._v(\"Bugs that break this commitment get the highest priority.\")]),t._v(\" \"),s(\"h2\",{attrs:{id:\"customizable-and-extendable\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#customizable-and-extendable\"}},[t._v(\"#\")]),t._v(\" Customizable and extendable\")]),t._v(\" \"),s(\"p\",[t._v(\"All of the JSON enhancements can be \"),s(\"a\",{attrs:{href:\"/guide/customize\"}},[t._v(\"customized\")]),t._v(\".\")]),t._v(\" \"),s(\"p\",[t._v(\"The top level \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" function cannot be customized. First, create a\\nnew instance with:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" myjsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Then you can set some options. let's turn off comments:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language- extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[s(\"code\",[t._v('myjsonic.options({comment:{lex:false}})\\n\\nmyjsonic(\\'a:1,#b:2\\') // === {\"a\":1, \"#b\":2}\\nJsonic(\\'a:1,#b:2\\') // === {\"a\":1}\\n')])])]),s(\"p\",[t._v(\"Maybe you're just against \"),s(\"code\",[t._v(\"#\")]),t._v(\" style comments, but fine with \"),s(\"code\",[t._v(\"//\")]),t._v(\"?  OK,\\nlet's get rid of just \"),s(\"code\",[t._v(\"#\")]),t._v(\" comments:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language- extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[s(\"code\",[t._v(\"let nohash = Jsonic.make({comment:{marker:{'#':false}})\\n\\nnohash('a:1,#b:2') // === {\\\"a\\\":1, \\\"#b\\\":2}\\nnohash('a:1,//b:2') // === {\\\"a\\\":1}\\n\")])])]),s(\"p\",[t._v(\"You can pass custom options directly to \"),s(\"code\",[t._v(\"Jsonic.make\")]),t._v(\" when creating a\\nnew instance.`\")]),t._v(\" \"),s(\"p\",[t._v(\"All the options are explained in the \"),s(\"a\",{attrs:{href:\"/ref/options\"}},[t._v(\"options reference\")]),t._v(\".\")]),t._v(\" \"),s(\"p\",[t._v(\"​\"),s(\"name-self\"),t._v(\" can be extended with plugins. A few are included\\nin the standard package to get you started.\")],1),t._v(\" \"),s(\"p\",[t._v(\"Let's parse some CSV:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"const\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Csv \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/csv'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" csv \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Csv\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === [{\"a\": 1, \"b\": 2}, {\"a\": 3, \"b\": 4}]')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"csv\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\na, b\\n1, 2\\n3, 4\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"The available plugins are listed in the \"),s(\"RouterLink\",{attrs:{to:\"/plugin/\"}},[t._v(\"plugins section\")]),t._v(\",\\nalong with tutorials to help you write your own plugins.\")],1),t._v(\" \"),s(\"h1\",{attrs:{id:\"where-to-go-next\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#where-to-go-next\"}},[t._v(\"#\")]),t._v(\" Where to go next\")]),t._v(\" \"),s(\"p\",[t._v(\"If you want to write custom parsers with \"),s(\"name-self\"),t._v(\", start by\\nfollowing the \"),s(\"a\",{attrs:{href:\"/guide/tutorials\"}},[t._v(\"tutorials\")]),t._v(\".\")],1)])}),[],!1,null,null,null);s.default=e.exports}}]);"
  },
  {
    "path": "docs/assets/js/12.d9f3d941.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[12],{194:function(t,s,e){\"use strict\";e.r(s);var i=e(6),r=Object(i.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"guide\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#guide\"}},[this._v(\"#\")]),this._v(\" Guide\")]),this._v(\" \"),t(\"p\",[this._v(\"Lorem ipsum\")])])}),[],!1,null,null,null);s.default=r.exports}}]);"
  },
  {
    "path": "docs/assets/js/13.775f91ca.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[13],{198:function(s,t,a){\"use strict\";a.r(t);var n=a(6),e=Object(n.a)({},(function(){var s=this,t=s._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":s.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"install\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#install\"}},[s._v(\"#\")]),s._v(\" Install\")]),s._v(\" \"),t(\"p\",[s._v(\"You can use \"),t(\"name-self\"),s._v(\" in the browser and on the server.\")],1),s._v(\" \"),t(\"p\",[s._v(\"You'll need to install the package into your project first:\")]),s._v(\" \"),t(\"div\",{staticClass:\"language-sh extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-sh\"}},[t(\"code\",[s._v(\"$ \"),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"npm\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"install\")]),s._v(\" jsonic \\n\")])])]),t(\"p\",[s._v(\"To validate your install, run the following code:\")]),s._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[s._v(\"console\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"This should output some strict JSON: \"),t(\"code\",[s._v('{\"a\":1}')]),s._v(\".\")]),s._v(\" \"),t(\"p\",[s._v(\"​\"),t(\"name-self\"),s._v(\" also provides a command line utility\\nthat converts jsonic arguments into traditional JSON. If you install\\n\"),t(\"name-self\"),s._v(\" globally, then you can run this utility directly:\")],1),s._v(\" \"),t(\"div\",{staticClass:\"language-sh extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-sh\"}},[t(\"code\",[s._v(\"$ \"),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"npm\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"install\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token parameter variable\"}},[s._v(\"-g\")]),s._v(\" jsonic\\n$ jsonic a:1\\n\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v('\"a\"')]),s._v(\":1\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"}\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"If you plan on writing custom plugins, this utility is very handy for\\ndebugging (try \"),t(\"code\",[s._v(\"jsonic -d\")]),s._v(\" to get a debug log).\")]),s._v(\" \"),t(\"h2\",{attrs:{id:\"node-js\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#node-js\"}},[s._v(\"#\")]),s._v(\" Node.js\")]),s._v(\" \"),t(\"p\",[s._v(\"The recommended way to load Jsonic is:\")]),s._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"const\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"{\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"}\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[s._v(\"=\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"require\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\\n\"),t(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[s._v(\"// And then just use it directly!\")]),s._v(\"\\nconsole\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"However, if you are upgrading from \"),t(\"name-self\"),s._v(\" 0.x or 1.x, then the\\nold way will still work (and is \"),t(\"em\",[s._v(\"not\")]),s._v(\" deprecated—this is a perfectly valid traditional Node.js idiom!):\")],1),s._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"const\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[s._v(\"=\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"require\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\\n\"),t(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[s._v(\"// Nothing broken!\")]),s._v(\"\\nconsole\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"es-module\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#es-module\"}},[s._v(\"#\")]),s._v(\" ES Module\")]),s._v(\" \"),t(\"p\",[s._v(\"If you are using Node version 14 or higher and want to load \"),t(\"name-self\"),s._v(\" into your\\nown modules, then load the jsonic.js file directly, like so:\")],1),s._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"import\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"from\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),s._v(\"\\nconsole\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"This will also work:\")]),s._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"import\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"{\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"}\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"from\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),s._v(\"\\nconsole\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"Note that \"),t(\"name-self\"),s._v(\" is a CommonJS module, so this required Node\\nversion 14 or higher.\")],1),s._v(\" \"),t(\"p\",[s._v(\"In other scenarios (such as browser usage), your packaging solution\\n(webpack, rollup, etc.) will expose \"),t(\"name-self\"),s._v(\" to you as an ES Module.\")],1),s._v(\" \"),t(\"h2\",{attrs:{id:\"typescript\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#typescript\"}},[s._v(\"#\")]),s._v(\" Typescript\")]),s._v(\" \"),t(\"p\",[s._v(\"​\"),t(\"name-self\"),s._v(\" is written in TypeScript, and the\\npackage includes type information. Import and go!\")],1),s._v(\" \"),t(\"div\",{staticClass:\"language-ts extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-ts\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"import\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"from\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),s._v(\"\\n\"),t(\"span\",{pre:!0,attrs:{class:\"token builtin\"}},[s._v(\"console\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"This will also work:\")]),s._v(\" \"),t(\"div\",{staticClass:\"language-ts extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-ts\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"import\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"{\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"}\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"from\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),s._v(\"\\n\"),t(\"span\",{pre:!0,attrs:{class:\"token builtin\"}},[s._v(\"console\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"log\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[s._v(\"Jsonic\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'a:1'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\")\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"The package also exports all of the types needed for plugin development:\")]),s._v(\" \"),t(\"div\",{staticClass:\"language-ts extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-ts\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[s._v(\"// Some of the exported types (there are more...)\")]),s._v(\"\\n\"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"import\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"{\")]),s._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\",\")]),s._v(\" Plugin\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\",\")]),s._v(\" Rule\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\",\")]),s._v(\" Context \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"}\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"from\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),s._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"browser\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#browser\"}},[s._v(\"#\")]),s._v(\" Browser\")]),s._v(\" \"),t(\"p\",[s._v(\"If you use a packager (such as webpack, rollup, parcel, ...), then\\nimport \"),t(\"name-self\"),s._v(\" and the packager will look after things for you.\")],1),s._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"import\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"{\")]),s._v(\" Jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"}\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[s._v(\"from\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[s._v(\"'jsonic'\")]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"You can also load \"),t(\"name-self\"),s._v(\" directly using the \"),t(\"code\",[s._v(\"script\")]),s._v(\" tag. The\\npackage includes a standalone minified version \"),t(\"code\",[s._v(\"jsonic.min.js\")]),s._v(\".\")],1),s._v(\" \"),t(\"div\",{staticClass:\"language-html extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-html\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token tag\"}},[t(\"span\",{pre:!0,attrs:{class:\"token tag\"}},[t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"<\")]),s._v(\"script\")]),s._v(\" \"),t(\"span\",{pre:!0,attrs:{class:\"token attr-name\"}},[s._v(\"src\")]),t(\"span\",{pre:!0,attrs:{class:\"token attr-value\"}},[t(\"span\",{pre:!0,attrs:{class:\"token punctuation attr-equals\"}},[s._v(\"=\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v('\"')]),s._v(\"jsonic.min.js\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v('\"')])]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\">\")])]),t(\"span\",{pre:!0,attrs:{class:\"token script\"}}),t(\"span\",{pre:!0,attrs:{class:\"token tag\"}},[t(\"span\",{pre:!0,attrs:{class:\"token tag\"}},[t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\"</\")]),s._v(\"script\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[s._v(\">\")])]),s._v(\"\\n\")])])]),t(\"p\",[s._v(\"This will make the global variable \"),t(\"code\",[s._v(\"Jsonic\")]),s._v(\" available for direct use.\")]),s._v(\" \"),t(\"h2\",{attrs:{id:\"getting-help\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#getting-help\"}},[s._v(\"#\")]),s._v(\" Getting Help\")]),s._v(\" \"),t(\"p\",[s._v(\"If you need help installing \"),t(\"name-self\"),s._v(\", please post a question on\\nthe \"),t(\"a\",{attrs:{href:\"https://github.com/rjrodger/jsonic/discussions/26\",target:\"_blank\",rel:\"noopener noreferrer\"}},[s._v(\"Installation\"),t(\"OutboundLink\")],1),s._v(\"\\ndiscussion board.\")],1),s._v(\" \"),t(\"h2\",{attrs:{id:\"next-steps\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#next-steps\"}},[s._v(\"#\")]),s._v(\" Next Steps\")]),s._v(\" \"),t(\"p\",[s._v(\"Follow the \"),t(\"a\",{attrs:{href:\"getting-started\"}},[s._v(\"Getting Started\")]),s._v(\" guide to learn the extended\\n\"),t(\"name-self\"),s._v(\" syntax.\")],1)])}),[],!1,null,null,null);t.default=e.exports}}]);"
  },
  {
    "path": "docs/assets/js/14.f56c2700.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[14],{197:function(e,a,t){\"use strict\";t.r(a);var n=t(6),s=Object(n.a)({},(function(){var e=this,a=e._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":e.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"syntax-introduction\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax-introduction\"}},[e._v(\"#\")]),e._v(\" Syntax introduction\")]),e._v(\" \"),a(\"p\",[e._v(\"The syntax that \"),a(\"name-self\"),e._v(\" uses is configurable. There is a standard\\nversion that you will learn about first in this guide.\")],1),e._v(\" \"),a(\"p\",[e._v(\"​\"),a(\"name-self\"),e._v(\" syntax is a super set of traditional\\n\"),a(\"em\",[e._v(\"JSON\")]),e._v(\". Every valid \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" document is also a valid \"),a(\"name-self\"),e._v(\"\\ndocument.\")],1),e._v(\" \"),a(\"h2\",{attrs:{id:\"developer-experience\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#developer-experience\"}},[e._v(\"#\")]),e._v(\" Developer experience\")]),e._v(\" \"),a(\"p\",[e._v(\"The developer experience when using \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" is OK but not great. When\\n\"),a(\"em\",[e._v(\"JSON\")]),e._v(\" is used for editable documents, say for configuration, things\\nget tricky. The lack of comments make life more difficult than it\\nneeds to be. The ceremony of quoting all strings is tedious. And\\ndealing with multi-line strings is really nasty.\")]),e._v(\" \"),a(\"p\",[e._v(\"There are many extensions to \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" that solve these problems (and\\nothers) in various ways.  All the alternatives that I know about are\\nlisted on the \"),a(\"a\",{attrs:{href:\"/guide/alternatives\"}},[e._v(\"alternatives\")]),e._v(\" page.\")]),e._v(\" \"),a(\"p\",[e._v(\"I have taken as many of the good ideas as possible, and combined them\\ninto a configurable \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" parser. The basic guiding is: if it isn't\\nambiguous, it's cool.\")]),e._v(\" \"),a(\"p\",[e._v(\"​\"),a(\"name-self\"),e._v(\" is also something else&emdash;an\\nextensible parser. If you want to add your own little Domain Specific\\nLanguages (DSL) into your \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" documents, \"),a(\"name-self\"),e._v(\" is built\\nexactly for that purpose! See\\nthe \"),a(\"a\",{attrs:{href:\"/guide/custom-parsers\"}},[e._v(\"custom parsers\")]),e._v(\" section for a guide to\\nwriting your own \"),a(\"em\",[e._v(\"JSON\")]),e._v(\"-based DSLs with \"),a(\"name-self\")],1),e._v(\" \"),a(\"h2\",{attrs:{id:\"walkthrough\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#walkthrough\"}},[e._v(\"#\")]),e._v(\" Walkthrough\")]),e._v(\" \"),a(\"p\",[e._v(\"Let's first informally describe the syntax extensions that\\n\"),a(\"name-self\"),e._v(\" provides. For a slightly more formal description, See\\nthe \"),a(\"RouterLink\",{attrs:{to:\"/ref/#railroad-diagrams\"}},[e._v(\"railroad diagrams\")]),e._v(\" section.\")],1),e._v(\" \"),a(\"p\",[e._v(\"​\"),a(\"name-self\"),e._v(\" is an extension\\nof \"),a(\"a\",{attrs:{href:\"https://json.org\",target:\"_blank\",rel:\"noopener noreferrer\"}},[e._v(\"JSON\"),a(\"OutboundLink\")],1),e._v(\", so all the usual rules of \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" syntax\\nstill work.\")],1),e._v(\" \"),a(\"h3\",{attrs:{id:\"comments\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#comments\"}},[e._v(\"#\")]),e._v(\" Comments\")]),e._v(\" \"),a(\"p\",[e._v(\"Single line comments can be introduced by \"),a(\"code\",[e._v(\"#\")]),e._v(\" or \"),a(\"code\",[e._v(\"//\")]),e._v(\" and run to the\\nend of the current line:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('{\\n  \"a\": 1, # a comment\\n  \"b\": 2, // also a comment\\n}\\n')])])]),a(\"p\",[e._v(\"You also get multi-line comments with:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('{\\n  \"a\": 1,\\n  /* \\n   * A Multiline comment\\n   */\\n  \"b\": 2,\\n  \"foo\": foo,\\n}\\n\\n')])])]),a(\"p\",[e._v(\"Sometimes you need to comment out a section that already has a\\nmulti-line comment within it. This can be annoying with traditional\\nmulti-line syntax, as the multi-line comment ends with the first end\\nmarker (\"),a(\"code\",[e._v(\"*/\")]),e._v(\") seen.  \"),a(\"name-self\"),e._v(\" allows multi-line comments to nest\\nso you don't have to worry about this anymore:\")],1),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('{\\n  \"a\": 1,\\n  /* \\n   * /* a multi-line comment\\n   *  * inside a multi-line comment.\\n   *  */\\n   */\\n  \"b\": 2,\\n}\\n\\n')])])]),a(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[a(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[e._v(\"(Even the syntax highlighter struggles with this one!)\")])]),e._v(\" \"),a(\"h3\",{attrs:{id:\"keys\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#keys\"}},[e._v(\"#\")]),e._v(\" Keys\")]),e._v(\" \"),a(\"p\",[e._v(\"You don't have to quote keys. This deals with the most tedious part of\\nediting pure \"),a(\"em\",[e._v(\"JSON\")]),e._v(\" by hand. We go a little easier than pure\\n\"),a(\"code\",[e._v(\"JavaScript\")]),e._v(\" too—you only have to quote keys if they contain\\nspaces or punctuation.\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n{               {\\n  a: 1,           \"a\": 1,\\n  1: 2,           \"1\": 2,\\n  1a: 3,          \"1a\": 3,\\n  \"1 a\": 4,       \"1 a\": 4,\\n  \"{}\": 5,         \"{}\": 5,\\n  true: 6         \"true\": 6\\n}               }\\n')])])]),a(\"h3\",{attrs:{id:\"commas\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#commas\"}},[e._v(\"#\")]),e._v(\" Commas\")]),e._v(\" \"),a(\"p\",[e._v(\"This is another essential convenience. You can having trailing commas,\\nwhich makes cut-and-paste editing much easier.\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n{               {\\n  a: 1,           \"a\": 1,\\n  1: 2,           \"1\": 2\\n}               }\\n')])])]),a(\"p\",[e._v(\"Actually, you don't need commas at all. Spaces, tabs, new lines and \"),a(\"em\",[e._v(\"nothing\")]),e._v(\" also\\nseparate elements for you:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n{               {\\n  a: 1           \"a\": 1,\\n  1: 2           \"1\": 2,\\n  b: [3 4]       \"b\": [3, 4]\\n  c: [[5][6]]    \"c\": [[5], [6]] \\n}               }\\n')])])]),a(\"p\",[e._v(\"Repeated commas do have a special meaning. Any comma without a\\n\"),a(\"strong\",[e._v(\"preceding value\")]),e._v(\" generates a \"),a(\"code\",[e._v(\"null\")]),e._v(\" value (\"),a(\"em\",[e._v(\"JSON\")]),e._v(\" only has \"),a(\"code\",[e._v(\"null\")]),e._v(\", not\\n\"),a(\"code\",[e._v(\"undefined\")]),e._v(\"):\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n[a,]           [\"a\"]\\n[,a]           [null, \"a\"]\\n[,a,]          [null, \"a\"]\\n[,a,,]         [null, \"a\", null]\\n[,,,]          [null, null, null]\\n[,,]           [null, null]\\n[,]            [null]\\n')])])]),a(\"p\",[e._v(\"You also get \"),a(\"code\",[e._v(\"null\")]),e._v(\" when a property value is missing:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n{a:,b:}         {\"a\":null, \"b\":null}\\n')])])]),a(\"h3\",{attrs:{id:\"strings\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#strings\"}},[e._v(\"#\")]),e._v(\" Strings\")]),e._v(\" \"),a(\"p\",[e._v(\"Single and double quoted strings work the same way as in JavaScript:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n\"a\"             \"a\"\\n\\'b\\'             \"b\"\\n\"c\\'c\"           \"c\\'c\"\\n\\'d\"d\\'           \"d\\\\\"d\"\\n\\'e\\\\te\\'          \"e\\\\te\"\\n')])])]),a(\"p\",[e._v(\"You also get backticks, which are multi-line:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n`a             \"a\\\\nb\"\\nb`           \\n')])])]),a(\"p\",[e._v(\"And a convenience syntax for indented blocks of text:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v(\"// jsonic       // JSON\\n  '''           \\\"red\\\\ngreen\\\\nblue\\\"\\n  red                          \\n  green\\n  blue\\n  '''\\n\")])])]),a(\"h3\",{attrs:{id:\"numbers\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#numbers\"}},[e._v(\"#\")]),e._v(\" Numbers\")]),e._v(\" \"),a(\"p\",[e._v(\"You get all the JavaScript number formats:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v(\"// jsonic       // JSON\\n20              20\\n20.0            20\\n2e1             20\\n0x14            20\\n0o24            20\\n0b10100         20\\n\")])])]),a(\"p\",[e._v(\"And underscore as way to make large numbers easier to read:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v(\"// jsonic       // JSON\\n2_000_000       2000000\\n\")])])]),a(\"p\",[e._v(\"The special JavaScript number value literals (such as \"),a(\"code\",[e._v(\"Infinity\")]),e._v(\") are\\nnot supported, but you can have them (and other things, like\\n\"),a(\"code\",[e._v(\"undefined\")]),e._v(\") if you use the \"),a(\"a\",{attrs:{href:\"/plugin/native\"}},[e._v(\"native\")]),e._v(\" plugin.\")]),e._v(\" \"),a(\"h3\",{attrs:{id:\"merges\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#merges\"}},[e._v(\"#\")]),e._v(\" Merges\")]),e._v(\" \"),a(\"p\",[e._v(\"Duplicate keys merge their values when they are objects or arrays,\\notherwise the last value wins.\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic                  // JSON\\n{a:1, a:2}                 {\"a\":2}\\n{a:{b:1}, a:{c:2}}         {\"a\":{\"b\":1, \"c\":2}}\\n{a:[1,2], a:[3]}           {\"a\":[3, 2]}\\n')])])]),a(\"h3\",{attrs:{id:\"shortcuts\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#shortcuts\"}},[e._v(\"#\")]),e._v(\" Shortcuts\")]),e._v(\" \"),a(\"p\",[e._v(\"At the top level, you can skip braces and square brackets:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\na:1,b:2         {\"a\":1, \"b\":2}\\n1,2             [1, 2]\\n')])])]),a(\"p\",[e._v(\"You can use repeated key-colon pairs to set a single deep property:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\na:b:1           {\"a\": {\"b\":1}}\\na:b:[2]         {\"a\": {\"b\": [2]}}\\na:b:2, a:c:3    {\"a\": {\"b\": 2, \"c\":3}}\\n')])])]),a(\"p\",[e._v(\"Open objects and arrays close themselves:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n{a:{b:{c:[1     {\"a\":{\"b\":{\"c\":[1]}}}\\n')])])]),a(\"h3\",{attrs:{id:\"things-that-still-aren-t-allowed\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#things-that-still-aren-t-allowed\"}},[e._v(\"#\")]),e._v(\" Things that still aren't allowed\")]),e._v(\" \"),a(\"p\",[e._v(\"You still need to be able to detect actual syntax errors, like misused\\npunctuation in property keys or array elements:\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v(\"a{b:1           // nope!\\na}b:1           // nope!\\na[b:1           // nope!\\na]b:1           // nope!\\n[{]             // nope!\\n[}]             // nope!\\n\")])])]),a(\"p\",[e._v(\"Punctuation cannot occur in property values either as it terminates\\nthe value (which is what you want).\")]),e._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[e._v('// jsonic       // JSON\\n{a:}            {\"a\": null}\\n{a:{}           {\"a\": {}}\\n{a:]}           // nope!\\n{a:[}           // nope!\\n')])])])])}),[],!1,null,null,null);a.default=s.exports}}]);"
  },
  {
    "path": "docs/assets/js/15.00058088.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[15],{196:function(t,a,r){\"use strict\";r.r(a);var s=r(6),i=Object(s.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"tutorials\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#tutorials\"}},[t._v(\"#\")]),t._v(\" Tutorials\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"writing-a-plugin\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#writing-a-plugin\"}},[t._v(\"#\")]),t._v(\" Writing a plugin\")]),t._v(\" \"),a(\"p\",[t._v(\"Short desc.\")]),t._v(\" \"),a(\"p\",[t._v(\"Start the \"),a(\"a\",{attrs:{href:\"/tutorial/write-a-plugin\"}},[t._v(\"writing a plugin\")]),t._v(\" tutorial.\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"parsing-csv-into-json\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#parsing-csv-into-json\"}},[t._v(\"#\")]),t._v(\" Parsing CSV into JSON\")]),t._v(\" \"),a(\"p\",[t._v(\"Short desc.\")]),t._v(\" \"),a(\"p\",[t._v(\"Start the \"),a(\"a\",{attrs:{href:\"/tutorial/parsing-csv\"}},[t._v(\"parsing CSV\")]),t._v(\" tutorial.\")])])}),[],!1,null,null,null);a.default=i.exports}}]);"
  },
  {
    "path": "docs/assets/js/16.70a6eea0.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[16],{199:function(t,s,n){\"use strict\";n.r(s);var e=n(6),o=Object(e.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"p\",[this._v(\"Jsonic items\")])])}),[],!1,null,null,null);s.default=o.exports}}]);"
  },
  {
    "path": "docs/assets/js/17.0d1f36b1.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[17],{203:function(t,s,a){\"use strict\";a.r(s);var e=a(6),n=Object(e.a)({},(function(){var t=this,s=t._self._c;return s(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[s(\"h1\",{attrs:{id:\"csv\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#csv\"}},[t._v(\"#\")]),t._v(\" \"),s(\"code\",[t._v(\"csv\")])]),t._v(\" \"),s(\"p\",[t._v(\"The standard \"),s(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),s(\"code\",[t._v(\"true\")]),t._v(\", \"),s(\"code\",[t._v(\"false\")]),t._v(\", \"),s(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),s(\"p\",[t._v(\"Wouldn't it be nice to have \"),s(\"code\",[t._v(\"undefined\")]),t._v(\", \"),s(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),s(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),s(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),s(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\"csv\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),s(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),s(\"h2\",{attrs:{id:\"usage\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),s(\"p\",[t._v(\"To use the plugin, \"),s(\"code\",[t._v(\"require\")]),t._v(\" or \"),s(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),s(\"code\",[t._v(\"jsonic/plugin/csv\")]),t._v(\":\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Csv \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/csv'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Csv \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/csv'\")]),t._v(\"\\n\")])])]),s(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[s(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),s(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),s(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"quick-example\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Csv \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/csv'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Csv\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),s(\"h2\",{attrs:{id:\"syntax\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),s(\"p\",[t._v(\"The standard \"),s(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),s(\"h3\",{attrs:{id:\"csv-value-keywords\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#csv-value-keywords\"}},[t._v(\"#\")]),t._v(\" Csv value keywords\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),s(\"code\",[t._v(\"+\")]),t._v(\" or \"),s(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),s(\"h3\",{attrs:{id:\"regular-expressions\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),s(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),s(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),s(\"h3\",{attrs:{id:\"iso-dates\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),s(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),s(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-jsonic extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[s(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),s(\"h2\",{attrs:{id:\"options\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),s(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),s(\"h2\",{attrs:{id:\"implementation\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),s(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),s(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/csv.ts\"}},[s(\"code\",[t._v(\"plugin/csv.ts\")])]),t._v(\".\")]),t._v(\" \"),s(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);s.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/18.0f184e32.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[18],{200:function(t,a,s){\"use strict\";s.r(a);var e=s(6),n=Object(e.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"dynamic\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#dynamic\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"dynamic\")])]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),a(\"code\",[t._v(\"true\")]),t._v(\", \"),a(\"code\",[t._v(\"false\")]),t._v(\", \"),a(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),a(\"p\",[t._v(\"Wouldn't it be nice to have \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),a(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),a(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),a(\"p\",[t._v(\"The \"),a(\"code\",[t._v(\"dynamic\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),a(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),a(\"h2\",{attrs:{id:\"usage\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),a(\"p\",[t._v(\"To use the plugin, \"),a(\"code\",[t._v(\"require\")]),t._v(\" or \"),a(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),a(\"code\",[t._v(\"jsonic/plugin/dynamic\")]),t._v(\":\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Dynamic \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/dynamic'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Dynamic \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/dynamic'\")]),t._v(\"\\n\")])])]),a(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[a(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),a(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),a(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),a(\"h3\",{attrs:{id:\"quick-example\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Dynamic \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/dynamic'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Dynamic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),a(\"h2\",{attrs:{id:\"syntax\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),a(\"h3\",{attrs:{id:\"dynamic-value-keywords\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#dynamic-value-keywords\"}},[t._v(\"#\")]),t._v(\" Dynamic value keywords\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),a(\"code\",[t._v(\"+\")]),t._v(\" or \"),a(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"regular-expressions\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),a(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),a(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"iso-dates\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),a(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),a(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),a(\"h2\",{attrs:{id:\"options\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),a(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"implementation\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),a(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),a(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/dynamic.ts\"}},[a(\"code\",[t._v(\"plugin/dynamic.ts\")])]),t._v(\".\")]),t._v(\" \"),a(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);a.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/19.57ad231b.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[19],{202:function(t,a,s){\"use strict\";s.r(a);var e=s(6),n=Object(e.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"hoover\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#hoover\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"hoover\")])]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),a(\"code\",[t._v(\"true\")]),t._v(\", \"),a(\"code\",[t._v(\"false\")]),t._v(\", \"),a(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),a(\"p\",[t._v(\"Wouldn't it be nice to have \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),a(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),a(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),a(\"p\",[t._v(\"The \"),a(\"code\",[t._v(\"hoover\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),a(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),a(\"h2\",{attrs:{id:\"usage\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),a(\"p\",[t._v(\"To use the plugin, \"),a(\"code\",[t._v(\"require\")]),t._v(\" or \"),a(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),a(\"code\",[t._v(\"jsonic/plugin/hoover\")]),t._v(\":\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Hoover \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/hoover'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Hoover \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/hoover'\")]),t._v(\"\\n\")])])]),a(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[a(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),a(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),a(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),a(\"h3\",{attrs:{id:\"quick-example\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Hoover \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/hoover'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Hoover\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),a(\"h2\",{attrs:{id:\"syntax\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),a(\"h3\",{attrs:{id:\"hoover-value-keywords\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#hoover-value-keywords\"}},[t._v(\"#\")]),t._v(\" Hoover value keywords\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),a(\"code\",[t._v(\"+\")]),t._v(\" or \"),a(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"regular-expressions\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),a(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),a(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"iso-dates\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),a(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),a(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),a(\"h2\",{attrs:{id:\"options\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),a(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"implementation\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),a(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),a(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/hoover.ts\"}},[a(\"code\",[t._v(\"plugin/hoover.ts\")])]),t._v(\".\")]),t._v(\" \"),a(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);a.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/2.34930047.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{149:function(t,e,n){\"use strict\";n.d(e,\"d\",(function(){return s})),n.d(e,\"a\",(function(){return a})),n.d(e,\"i\",(function(){return r})),n.d(e,\"f\",(function(){return l})),n.d(e,\"g\",(function(){return u})),n.d(e,\"h\",(function(){return c})),n.d(e,\"b\",(function(){return h})),n.d(e,\"e\",(function(){return p})),n.d(e,\"k\",(function(){return d})),n.d(e,\"l\",(function(){return f})),n.d(e,\"c\",(function(){return m})),n.d(e,\"j\",(function(){return b}));const s=/#.*$/,i=/\\.(md|html)$/,a=/\\/$/,r=/^[a-z]+:/i;function o(t){return decodeURI(t).replace(s,\"\").replace(i,\"\")}function l(t){return r.test(t)}function u(t){return/^mailto:/.test(t)}function c(t){return/^tel:/.test(t)}function h(t){if(l(t))return t;const e=t.match(s),n=e?e[0]:\"\",i=o(t);return a.test(i)?t:i+\".html\"+n}function p(t,e){const n=decodeURIComponent(t.hash),i=function(t){const e=t.match(s);if(e)return e[0]}(e);if(i&&n!==i)return!1;return o(t.path)===o(e)}function d(t,e,n){if(l(e))return{type:\"external\",path:e};n&&(e=function(t,e,n){const s=t.charAt(0);if(\"/\"===s)return t;if(\"?\"===s||\"#\"===s)return e+t;const i=e.split(\"/\");n&&i[i.length-1]||i.pop();const a=t.replace(/^\\//,\"\").split(\"/\");for(let t=0;t<a.length;t++){const e=a[t];\"..\"===e?i.pop():\".\"!==e&&i.push(e)}\"\"!==i[0]&&i.unshift(\"\");return i.join(\"/\")}(e,n));const s=o(e);for(let e=0;e<t.length;e++)if(o(t[e].regularPath)===s)return Object.assign({},t[e],{type:\"page\",path:h(t[e].path)});return console.error(`[vuepress] No matching page found for sidebar item \"${e}\"`),{}}function f(t,e,n,s){const{pages:i,themeConfig:a}=n,r=s&&a.locales&&a.locales[s]||a;if(\"auto\"===(t.frontmatter.sidebar||r.sidebar||a.sidebar))return g(t);const o=r.sidebar||a.sidebar;if(o){const{base:n,config:s}=function(t,e){if(Array.isArray(e))return{base:\"/\",config:e};for(const s in e)if(0===(n=t,/(\\.html|\\/)$/.test(n)?n:n+\"/\").indexOf(encodeURI(s)))return{base:s,config:e[s]};var n;return{}}(e,o);return\"auto\"===s?g(t):s?s.map(t=>function t(e,n,s,i=1){if(\"string\"==typeof e)return d(n,e,s);if(Array.isArray(e))return Object.assign(d(n,e[0],s),{title:e[1]});{const a=e.children||[];return 0===a.length&&e.path?Object.assign(d(n,e.path,s),{title:e.title}):{type:\"group\",path:e.path,title:e.title,sidebarDepth:e.sidebarDepth,initialOpenGroupIndex:e.initialOpenGroupIndex,children:a.map(e=>t(e,n,s,i+1)),collapsable:!1!==e.collapsable}}}(t,i,n)):[]}return[]}function g(t){const e=m(t.headers||[]);return[{type:\"group\",collapsable:!1,title:t.title,path:null,children:e.map(e=>({type:\"auto\",title:e.title,basePath:t.path,path:t.path+\"#\"+e.slug,children:e.children||[]}))}]}function m(t){let e;return(t=t.map(t=>Object.assign({},t))).forEach(t=>{2===t.level?e=t:e&&(e.children||(e.children=[])).push(t)}),t.filter(t=>2===t.level)}function b(t){return Object.assign(t,{type:t.items&&t.items.length?\"links\":\"link\"})}},150:function(t,e,n){},151:function(t,e,n){},152:function(t,e,n){},153:function(t,e,n){},154:function(t,e,n){},155:function(t,e,n){},156:function(t,e,n){},157:function(t,e){t.exports=function(t){return null==t}},158:function(t,e,n){},159:function(t,e,n){},160:function(t,e,n){},161:function(t,e,n){},162:function(t,e,n){},163:function(t,e,n){},168:function(t,e,n){\"use strict\";n.r(e);var s=n(149),i={name:\"SidebarGroup\",components:{DropdownTransition:n(169).a},props:[\"item\",\"open\",\"collapsable\",\"depth\"],beforeCreate(){this.$options.components.SidebarLinks=n(168).default},methods:{isActive:s.e}},a=(n(181),n(6)),r=Object(a.a)(i,(function(){var t=this,e=t._self._c;return e(\"section\",{staticClass:\"sidebar-group\",class:[{collapsable:t.collapsable,\"is-sub-group\":0!==t.depth},\"depth-\"+t.depth]},[t.item.path?e(\"RouterLink\",{staticClass:\"sidebar-heading clickable\",class:{open:t.open,active:t.isActive(t.$route,t.item.path)},attrs:{to:t.item.path},nativeOn:{click:function(e){return t.$emit(\"toggle\")}}},[e(\"span\",[t._v(t._s(t.item.title))]),t._v(\" \"),t.collapsable?e(\"span\",{staticClass:\"arrow\",class:t.open?\"down\":\"right\"}):t._e()]):e(\"p\",{staticClass:\"sidebar-heading\",class:{open:t.open},on:{click:function(e){return t.$emit(\"toggle\")}}},[e(\"span\",[t._v(t._s(t.item.title))]),t._v(\" \"),t.collapsable?e(\"span\",{staticClass:\"arrow\",class:t.open?\"down\":\"right\"}):t._e()]),t._v(\" \"),e(\"DropdownTransition\",[t.open||!t.collapsable?e(\"SidebarLinks\",{staticClass:\"sidebar-group-items\",attrs:{items:t.item.children,\"sidebar-depth\":t.item.sidebarDepth,\"initial-open-group-index\":t.item.initialOpenGroupIndex,depth:t.depth+1}}):t._e()],1)],1)}),[],!1,null,null,null).exports;function o(t,e,n,s,i){const a={props:{to:e,activeClass:\"\",exactActiveClass:\"\"},class:{active:s,\"sidebar-link\":!0}};return i>2&&(a.style={\"padding-left\":i+\"rem\"}),t(\"RouterLink\",a,n)}function l(t,e,n,i,a,r=1){return!e||r>a?null:t(\"ul\",{class:\"sidebar-sub-headers\"},e.map(e=>{const u=Object(s.e)(i,n+\"#\"+e.slug);return t(\"li\",{class:\"sidebar-sub-header\"},[o(t,n+\"#\"+e.slug,e.title,u,e.level-1),l(t,e.children,n,i,a,r+1)])}))}var u={functional:!0,props:[\"item\",\"sidebarDepth\"],render(t,{parent:{$page:e,$site:n,$route:i,$themeConfig:a,$themeLocaleConfig:r},props:{item:u,sidebarDepth:c}}){const h=Object(s.e)(i,u.path),p=\"auto\"===u.type?h||u.children.some(t=>Object(s.e)(i,u.basePath+\"#\"+t.slug)):h,d=\"external\"===u.type?function(t,e,n){return t(\"a\",{attrs:{href:e,target:\"_blank\",rel:\"noopener noreferrer\"},class:{\"sidebar-link\":!0}},[n,t(\"OutboundLink\")])}(t,u.path,u.title||u.path):o(t,u.path,u.title||u.path,p),f=[e.frontmatter.sidebarDepth,c,r.sidebarDepth,a.sidebarDepth,1].find(t=>void 0!==t),g=r.displayAllHeaders||a.displayAllHeaders;if(\"auto\"===u.type)return[d,l(t,u.children,u.basePath,i,f)];if((p||g)&&u.headers&&!s.d.test(u.path)){return[d,l(t,Object(s.c)(u.headers),u.path,i,f)]}return d}};n(182);function c(t,e){if(\"group\"===e.type){const n=e.path&&Object(s.e)(t,e.path),i=e.children.some(e=>\"group\"===e.type?c(t,e):\"page\"===e.type&&Object(s.e)(t,e.path));return n||i}return!1}var h={name:\"SidebarLinks\",components:{SidebarGroup:r,SidebarLink:Object(a.a)(u,void 0,void 0,!1,null,null,null).exports},props:[\"items\",\"depth\",\"sidebarDepth\",\"initialOpenGroupIndex\"],data(){return{openGroupIndex:this.initialOpenGroupIndex||0}},watch:{$route(){this.refreshIndex()}},created(){this.refreshIndex()},methods:{refreshIndex(){const t=function(t,e){for(let n=0;n<e.length;n++){const s=e[n];if(c(t,s))return n}return-1}(this.$route,this.items);t>-1&&(this.openGroupIndex=t)},toggleGroup(t){this.openGroupIndex=t===this.openGroupIndex?-1:t},isActive(t){return Object(s.e)(this.$route,t.regularPath)}}},p=Object(a.a)(h,(function(){var t=this,e=t._self._c;return t.items.length?e(\"ul\",{staticClass:\"sidebar-links\"},t._l(t.items,(function(n,s){return e(\"li\",{key:s},[\"group\"===n.type?e(\"SidebarGroup\",{attrs:{item:n,open:s===t.openGroupIndex,collapsable:n.collapsable||n.collapsible,depth:t.depth},on:{toggle:function(e){return t.toggleGroup(s)}}}):e(\"SidebarLink\",{attrs:{\"sidebar-depth\":t.sidebarDepth,item:n}})],1)})),0):t._e()}),[],!1,null,null,null);e.default=p.exports},169:function(t,e,n){\"use strict\";var s={name:\"DropdownTransition\",methods:{setHeight(t){t.style.height=t.scrollHeight+\"px\"},unsetHeight(t){t.style.height=\"\"}}},i=(n(173),n(6)),a=Object(i.a)(s,(function(){return(0,this._self._c)(\"transition\",{attrs:{name:\"dropdown\"},on:{enter:this.setHeight,\"after-enter\":this.unsetHeight,\"before-leave\":this.setHeight}},[this._t(\"default\")],2)}),[],!1,null,null,null);e.a=a.exports},170:function(t,e,n){\"use strict\";n(150)},171:function(t,e,n){\"use strict\";n(151)},172:function(t,e,n){\"use strict\";n(152)},173:function(t,e,n){\"use strict\";n(153)},174:function(t,e,n){\"use strict\";n(154)},175:function(t,e,n){\"use strict\";n(155)},176:function(t,e,n){\"use strict\";n(156)},177:function(t,e,n){\"use strict\";n(158)},178:function(t,e,n){var s=n(4),i=n(0),a=n(3);t.exports=function(t){return\"string\"==typeof t||!i(t)&&a(t)&&\"[object String]\"==s(t)}},179:function(t,e,n){\"use strict\";n(159)},180:function(t,e,n){\"use strict\";n(160)},181:function(t,e,n){\"use strict\";n(161)},182:function(t,e,n){\"use strict\";n(162)},183:function(t,e,n){\"use strict\";n(163)},188:function(t,e,n){\"use strict\";n.r(e);var s=n(149),i={name:\"NavLink\",props:{item:{required:!0}},computed:{link(){return Object(s.b)(this.item.link)},exact(){return this.$site.locales?Object.keys(this.$site.locales).some(t=>t===this.link):\"/\"===this.link},isNonHttpURI(){return Object(s.g)(this.link)||Object(s.h)(this.link)},isBlankTarget(){return\"_blank\"===this.target},isInternal(){return!Object(s.f)(this.link)&&!this.isBlankTarget},target(){return this.isNonHttpURI?null:this.item.target?this.item.target:Object(s.f)(this.link)?\"_blank\":\"\"},rel(){return this.isNonHttpURI||!1===this.item.rel?null:this.item.rel?this.item.rel:this.isBlankTarget?\"noopener noreferrer\":null}},methods:{focusoutAction(){this.$emit(\"focusout\")}}},a=n(6),r=Object(a.a)(i,(function(){var t=this,e=t._self._c;return t.isInternal?e(\"RouterLink\",{staticClass:\"nav-link\",attrs:{to:t.link,exact:t.exact},nativeOn:{focusout:function(e){return t.focusoutAction.apply(null,arguments)}}},[t._v(\"\\n  \"+t._s(t.item.text)+\"\\n\")]):e(\"a\",{staticClass:\"nav-link external\",attrs:{href:t.link,target:t.target,rel:t.rel},on:{focusout:t.focusoutAction}},[t._v(\"\\n  \"+t._s(t.item.text)+\"\\n  \"),t.isBlankTarget?e(\"OutboundLink\"):t._e()],1)}),[],!1,null,null,null).exports,o={name:\"Home\",components:{NavLink:r},computed:{data(){return this.$page.frontmatter},actionLink(){return{link:this.data.actionLink,text:this.data.actionText}}}},l=(n(170),Object(a.a)(o,(function(){var t=this,e=t._self._c;return e(\"main\",{staticClass:\"home\",attrs:{\"aria-labelledby\":null!==t.data.heroText?\"main-title\":null}},[e(\"header\",{staticClass:\"hero\"},[t.data.heroImage?e(\"img\",{attrs:{src:t.$withBase(t.data.heroImage),alt:t.data.heroAlt||\"hero\"}}):t._e(),t._v(\" \"),null!==t.data.heroText?e(\"h1\",{attrs:{id:\"main-title\"}},[t._v(\"\\n      \"+t._s(t.data.heroText||t.$title||\"Hello\")+\"\\n    \")]):t._e(),t._v(\" \"),null!==t.data.tagline?e(\"p\",{staticClass:\"description\"},[t._v(\"\\n      \"+t._s(t.data.tagline||t.$description||\"Welcome to your VuePress site\")+\"\\n    \")]):t._e(),t._v(\" \"),t.data.actionText&&t.data.actionLink?e(\"p\",{staticClass:\"action\"},[e(\"NavLink\",{staticClass:\"action-button\",attrs:{item:t.actionLink}})],1):t._e()]),t._v(\" \"),t.data.features&&t.data.features.length?e(\"div\",{staticClass:\"features\"},t._l(t.data.features,(function(n,s){return e(\"div\",{key:s,staticClass:\"feature\"},[e(\"h2\",[t._v(t._s(n.title))]),t._v(\" \"),e(\"p\",[t._v(t._s(n.details))])])})),0):t._e(),t._v(\" \"),e(\"Content\",{staticClass:\"theme-default-content custom\"}),t._v(\" \"),t.data.footer?e(\"div\",{staticClass:\"footer\"},[t._v(\"\\n    \"+t._s(t.data.footer)+\"\\n  \")]):e(\"Content\",{staticClass:\"footer\",attrs:{\"slot-key\":\"footer\"}})],1)}),[],!1,null,null,null).exports),u=n(48),c=n.n(u),h=(t,e,n=null)=>{let s=c()(e,\"title\",\"\");return c()(e,\"frontmatter.tags\")&&(s+=\" \"+e.frontmatter.tags.join(\" \")),n&&(s+=\" \"+n),p(t,s)};const p=(t,e)=>{const n=t=>t.replace(/[-/\\\\^$*+?.()|[\\]{}]/g,\"\\\\$&\"),s=new RegExp(\"[^\\0-]\"),i=t.split(/\\s+/g).map(t=>t.trim()).filter(t=>!!t);if(s.test(t))return i.some(t=>e.toLowerCase().indexOf(t)>-1);{const s=t.endsWith(\" \");return new RegExp(i.map((t,e)=>i.length!==e+1||s?`(?=.*\\\\b${n(t)}\\\\b)`:`(?=.*\\\\b${n(t)})`).join(\"\")+\".+\",\"gi\").test(e)}};var d={name:\"SearchBox\",data:()=>({query:\"\",focused:!1,focusIndex:0,placeholder:void 0}),computed:{showSuggestions(){return this.focused&&this.suggestions&&this.suggestions.length},suggestions(){const t=this.query.trim().toLowerCase();if(!t)return;const{pages:e}=this.$site,n=this.$site.themeConfig.searchMaxSuggestions||5,s=this.$localePath,i=[];for(let a=0;a<e.length&&!(i.length>=n);a++){const r=e[a];if(this.getPageLocalePath(r)===s&&this.isSearchable(r))if(h(t,r))i.push(r);else if(r.headers)for(let e=0;e<r.headers.length&&!(i.length>=n);e++){const n=r.headers[e];n.title&&h(t,r,n.title)&&i.push(Object.assign({},r,{path:r.path+\"#\"+n.slug,header:n}))}}return i},alignRight(){return(this.$site.themeConfig.nav||[]).length+(this.$site.repo?1:0)<=2}},mounted(){this.placeholder=this.$site.themeConfig.searchPlaceholder||\"\",document.addEventListener(\"keydown\",this.onHotkey)},beforeDestroy(){document.removeEventListener(\"keydown\",this.onHotkey)},methods:{getPageLocalePath(t){for(const e in this.$site.locales||{})if(\"/\"!==e&&0===t.path.indexOf(e))return e;return\"/\"},isSearchable(t){let e=null;return null===e||(e=Array.isArray(e)?e:new Array(e),e.filter(e=>t.path.match(e)).length>0)},onHotkey(t){t.srcElement===document.body&&[\"s\",\"/\"].includes(t.key)&&(this.$refs.input.focus(),t.preventDefault())},onUp(){this.showSuggestions&&(this.focusIndex>0?this.focusIndex--:this.focusIndex=this.suggestions.length-1)},onDown(){this.showSuggestions&&(this.focusIndex<this.suggestions.length-1?this.focusIndex++:this.focusIndex=0)},go(t){this.showSuggestions&&(this.$router.push(this.suggestions[t].path),this.query=\"\",this.focusIndex=0)},focus(t){this.focusIndex=t},unfocus(){this.focusIndex=-1}}},f=(n(171),Object(a.a)(d,(function(){var t=this,e=t._self._c;return e(\"div\",{staticClass:\"search-box\"},[e(\"input\",{ref:\"input\",class:{focused:t.focused},attrs:{\"aria-label\":\"Search\",placeholder:t.placeholder,autocomplete:\"off\",spellcheck:\"false\"},domProps:{value:t.query},on:{input:function(e){t.query=e.target.value},focus:function(e){t.focused=!0},blur:function(e){t.focused=!1},keyup:[function(e){return!e.type.indexOf(\"key\")&&t._k(e.keyCode,\"enter\",13,e.key,\"Enter\")?null:t.go(t.focusIndex)},function(e){return!e.type.indexOf(\"key\")&&t._k(e.keyCode,\"up\",38,e.key,[\"Up\",\"ArrowUp\"])?null:t.onUp.apply(null,arguments)},function(e){return!e.type.indexOf(\"key\")&&t._k(e.keyCode,\"down\",40,e.key,[\"Down\",\"ArrowDown\"])?null:t.onDown.apply(null,arguments)}]}}),t._v(\" \"),t.showSuggestions?e(\"ul\",{staticClass:\"suggestions\",class:{\"align-right\":t.alignRight},on:{mouseleave:t.unfocus}},t._l(t.suggestions,(function(n,s){return e(\"li\",{key:s,staticClass:\"suggestion\",class:{focused:s===t.focusIndex},on:{mousedown:function(e){return t.go(s)},mouseenter:function(e){return t.focus(s)}}},[e(\"a\",{attrs:{href:n.path},on:{click:function(t){t.preventDefault()}}},[e(\"span\",{staticClass:\"page-title\"},[t._v(t._s(n.title||n.path))]),t._v(\" \"),n.header?e(\"span\",{staticClass:\"header\"},[t._v(\"> \"+t._s(n.header.title))]):t._e()])])})),0):t._e()])}),[],!1,null,null,null).exports),g=(n(172),Object(a.a)({},(function(){var t=this,e=t._self._c;return e(\"div\",{staticClass:\"sidebar-button\",on:{click:function(e){return t.$emit(\"toggle-sidebar\")}}},[e(\"svg\",{staticClass:\"icon\",attrs:{xmlns:\"http://www.w3.org/2000/svg\",\"aria-hidden\":\"true\",role:\"img\",viewBox:\"0 0 448 512\"}},[e(\"path\",{attrs:{fill:\"currentColor\",d:\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"}})])])}),[],!1,null,null,null).exports),m=n(169),b=n(49),v=n.n(b),k={name:\"DropdownLink\",components:{NavLink:r,DropdownTransition:m.a},props:{item:{required:!0}},data:()=>({open:!1}),computed:{dropdownAriaLabel(){return this.item.ariaLabel||this.item.text}},watch:{$route(){this.open=!1}},methods:{setOpen(t){this.open=t},isLastItemOfArray:(t,e)=>v()(e)===t,handleDropdown(){0===event.detail&&this.setOpen(!this.open)}}},_=(n(174),{name:\"NavLinks\",components:{NavLink:r,DropdownLink:Object(a.a)(k,(function(){var t=this,e=t._self._c;return e(\"div\",{staticClass:\"dropdown-wrapper\",class:{open:t.open}},[e(\"button\",{staticClass:\"dropdown-title\",attrs:{type:\"button\",\"aria-label\":t.dropdownAriaLabel},on:{click:t.handleDropdown}},[e(\"span\",{staticClass:\"title\"},[t._v(t._s(t.item.text))]),t._v(\" \"),e(\"span\",{staticClass:\"arrow down\"})]),t._v(\" \"),e(\"button\",{staticClass:\"mobile-dropdown-title\",attrs:{type:\"button\",\"aria-label\":t.dropdownAriaLabel},on:{click:function(e){return t.setOpen(!t.open)}}},[e(\"span\",{staticClass:\"title\"},[t._v(t._s(t.item.text))]),t._v(\" \"),e(\"span\",{staticClass:\"arrow\",class:t.open?\"down\":\"right\"})]),t._v(\" \"),e(\"DropdownTransition\",[e(\"ul\",{directives:[{name:\"show\",rawName:\"v-show\",value:t.open,expression:\"open\"}],staticClass:\"nav-dropdown\"},t._l(t.item.items,(function(n,s){return e(\"li\",{key:n.link||s,staticClass:\"dropdown-item\"},[\"links\"===n.type?e(\"h4\",[t._v(\"\\n          \"+t._s(n.text)+\"\\n        \")]):t._e(),t._v(\" \"),\"links\"===n.type?e(\"ul\",{staticClass:\"dropdown-subitem-wrapper\"},t._l(n.items,(function(s){return e(\"li\",{key:s.link,staticClass:\"dropdown-subitem\"},[e(\"NavLink\",{attrs:{item:s},on:{focusout:function(e){t.isLastItemOfArray(s,n.items)&&t.isLastItemOfArray(n,t.item.items)&&t.setOpen(!1)}}})],1)})),0):e(\"NavLink\",{attrs:{item:n},on:{focusout:function(e){t.isLastItemOfArray(n,t.item.items)&&t.setOpen(!1)}}})],1)})),0)])],1)}),[],!1,null,null,null).exports},computed:{userNav(){return this.$themeLocaleConfig.nav||this.$site.themeConfig.nav||[]},nav(){const{locales:t}=this.$site;if(t&&Object.keys(t).length>1){const e=this.$page.path,n=this.$router.options.routes,s=this.$site.themeConfig.locales||{},i={text:this.$themeLocaleConfig.selectText||\"Languages\",ariaLabel:this.$themeLocaleConfig.ariaLabel||\"Select language\",items:Object.keys(t).map(i=>{const a=t[i],r=s[i]&&s[i].label||a.lang;let o;return a.lang===this.$lang?o=e:(o=e.replace(this.$localeConfig.path,i),n.some(t=>t.path===o)||(o=i)),{text:r,link:o}})};return[...this.userNav,i]}return this.userNav},userLinks(){return(this.nav||[]).map(t=>Object.assign(Object(s.j)(t),{items:(t.items||[]).map(s.j)}))},repoLink(){const{repo:t}=this.$site.themeConfig;return t?/^https?:/.test(t)?t:\"https://github.com/\"+t:null},repoLabel(){if(!this.repoLink)return;if(this.$site.themeConfig.repoLabel)return this.$site.themeConfig.repoLabel;const t=this.repoLink.match(/^https?:\\/\\/[^/]+/)[0],e=[\"GitHub\",\"GitLab\",\"Bitbucket\"];for(let n=0;n<e.length;n++){const s=e[n];if(new RegExp(s,\"i\").test(t))return s}return\"Source\"}}}),x=(n(175),Object(a.a)(_,(function(){var t=this,e=t._self._c;return t.userLinks.length||t.repoLink?e(\"nav\",{staticClass:\"nav-links\"},[t._l(t.userLinks,(function(t){return e(\"div\",{key:t.link,staticClass:\"nav-item\"},[\"links\"===t.type?e(\"DropdownLink\",{attrs:{item:t}}):e(\"NavLink\",{attrs:{item:t}})],1)})),t._v(\" \"),t.repoLink?e(\"a\",{staticClass:\"repo-link\",attrs:{href:t.repoLink,target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(\"\\n    \"+t._s(t.repoLabel)+\"\\n    \"),e(\"OutboundLink\")],1):t._e()],2):t._e()}),[],!1,null,null,null).exports);function C(t,e){return t.ownerDocument.defaultView.getComputedStyle(t,null)[e]}var L={name:\"Navbar\",components:{SidebarButton:g,NavLinks:x,SearchBox:f,AlgoliaSearchBox:{}},data:()=>({linksWrapMaxWidth:null}),computed:{algolia(){return this.$themeLocaleConfig.algolia||this.$site.themeConfig.algolia||{}},isAlgoliaSearch(){return this.algolia&&this.algolia.apiKey&&this.algolia.indexName}},mounted(){const t=parseInt(C(this.$el,\"paddingLeft\"))+parseInt(C(this.$el,\"paddingRight\")),e=()=>{document.documentElement.clientWidth<719?this.linksWrapMaxWidth=null:this.linksWrapMaxWidth=this.$el.offsetWidth-t-(this.$refs.siteName&&this.$refs.siteName.offsetWidth||0)};e(),window.addEventListener(\"resize\",e,!1)}},$=(n(176),Object(a.a)(L,(function(){var t=this,e=t._self._c;return e(\"header\",{staticClass:\"navbar\"},[e(\"SidebarButton\",{on:{\"toggle-sidebar\":function(e){return t.$emit(\"toggle-sidebar\")}}}),t._v(\" \"),e(\"RouterLink\",{staticClass:\"home-link\",attrs:{to:t.$localePath}},[t.$site.themeConfig.logo?e(\"img\",{staticClass:\"logo\",attrs:{src:t.$withBase(t.$site.themeConfig.logo),alt:t.$siteTitle}}):t._e(),t._v(\" \"),t.$siteTitle?e(\"span\",{ref:\"siteName\",staticClass:\"site-name\",class:{\"can-hide\":t.$site.themeConfig.logo}},[t._v(t._s(t.$siteTitle))]):t._e()]),t._v(\" \"),e(\"div\",{staticClass:\"links\",style:t.linksWrapMaxWidth?{\"max-width\":t.linksWrapMaxWidth+\"px\"}:{}},[t.isAlgoliaSearch?e(\"AlgoliaSearchBox\",{attrs:{options:t.algolia}}):!1!==t.$site.themeConfig.search&&!1!==t.$page.frontmatter.search?e(\"SearchBox\"):t._e(),t._v(\" \"),e(\"NavLinks\",{staticClass:\"can-hide\"})],1)],1)}),[],!1,null,null,null).exports),y=n(157),w=n.n(y),O={name:\"PageEdit\",computed:{lastUpdated(){return this.$page.lastUpdated},lastUpdatedText(){return\"string\"==typeof this.$themeLocaleConfig.lastUpdated?this.$themeLocaleConfig.lastUpdated:\"string\"==typeof this.$site.themeConfig.lastUpdated?this.$site.themeConfig.lastUpdated:\"Last Updated\"},editLink(){const t=w()(this.$page.frontmatter.editLink)?this.$site.themeConfig.editLinks:this.$page.frontmatter.editLink,{repo:e,docsDir:n=\"\",docsBranch:s=\"master\",docsRepo:i=e}=this.$site.themeConfig;return t&&i&&this.$page.relativePath?this.createEditLink(e,i,n,s,this.$page.relativePath):null},editLinkText(){return this.$themeLocaleConfig.editLinkText||this.$site.themeConfig.editLinkText||\"Edit this page\"}},methods:{createEditLink(t,e,n,i,a){if(/bitbucket.org/.test(e)){return e.replace(s.a,\"\")+\"/src\"+`/${i}/`+(n?n.replace(s.a,\"\")+\"/\":\"\")+a+`?mode=edit&spa=0&at=${i}&fileviewer=file-view-default`}if(/gitlab.com/.test(e)){return e.replace(s.a,\"\")+\"/-/edit\"+`/${i}/`+(n?n.replace(s.a,\"\")+\"/\":\"\")+a}return(s.i.test(e)?e:\"https://github.com/\"+e).replace(s.a,\"\")+\"/edit\"+`/${i}/`+(n?n.replace(s.a,\"\")+\"/\":\"\")+a}}},S=(n(177),Object(a.a)(O,(function(){var t=this,e=t._self._c;return e(\"footer\",{staticClass:\"page-edit\"},[t.editLink?e(\"div\",{staticClass:\"edit-link\"},[e(\"a\",{attrs:{href:t.editLink,target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(t._s(t.editLinkText))]),t._v(\" \"),e(\"OutboundLink\")],1):t._e(),t._v(\" \"),t.lastUpdated?e(\"div\",{staticClass:\"last-updated\"},[e(\"span\",{staticClass:\"prefix\"},[t._v(t._s(t.lastUpdatedText)+\":\")]),t._v(\" \"),e(\"span\",{staticClass:\"time\"},[t._v(t._s(t.lastUpdated))])]):t._e()])}),[],!1,null,null,null).exports),I=n(178),j=n.n(I),N={name:\"PageNav\",props:[\"sidebarItems\"],computed:{prev(){return P(T.PREV,this)},next(){return P(T.NEXT,this)}}};const T={NEXT:{resolveLink:function(t,e){return A(t,e,1)},getThemeLinkConfig:({nextLinks:t})=>t,getPageLinkConfig:({frontmatter:t})=>t.next},PREV:{resolveLink:function(t,e){return A(t,e,-1)},getThemeLinkConfig:({prevLinks:t})=>t,getPageLinkConfig:({frontmatter:t})=>t.prev}};function P(t,{$themeConfig:e,$page:n,$route:i,$site:a,sidebarItems:r}){const{resolveLink:o,getThemeLinkConfig:l,getPageLinkConfig:u}=t,c=l(e),h=u(n),p=w()(h)?c:h;return!1===p?void 0:j()(p)?Object(s.k)(a.pages,p,i.path):o(n,r)}function A(t,e,n){const s=[];!function t(e,n){for(let s=0,i=e.length;s<i;s++)\"group\"===e[s].type?t(e[s].children||[],n):n.push(e[s])}(e,s);for(let e=0;e<s.length;e++){const i=s[e];if(\"page\"===i.type&&i.path===decodeURIComponent(t.path))return s[e+n]}}var D=N,E=(n(179),{components:{PageEdit:S,PageNav:Object(a.a)(D,(function(){var t=this,e=t._self._c;return t.prev||t.next?e(\"div\",{staticClass:\"page-nav\"},[e(\"p\",{staticClass:\"inner\"},[t.prev?e(\"span\",{staticClass:\"prev\"},[t._v(\"\\n      ←\\n      \"),\"external\"===t.prev.type?e(\"a\",{staticClass:\"prev\",attrs:{href:t.prev.path,target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(\"\\n        \"+t._s(t.prev.title||t.prev.path)+\"\\n\\n        \"),e(\"OutboundLink\")],1):e(\"RouterLink\",{staticClass:\"prev\",attrs:{to:t.prev.path}},[t._v(\"\\n        \"+t._s(t.prev.title||t.prev.path)+\"\\n      \")])],1):t._e(),t._v(\" \"),t.next?e(\"span\",{staticClass:\"next\"},[\"external\"===t.next.type?e(\"a\",{attrs:{href:t.next.path,target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(\"\\n        \"+t._s(t.next.title||t.next.path)+\"\\n\\n        \"),e(\"OutboundLink\")],1):e(\"RouterLink\",{attrs:{to:t.next.path}},[t._v(\"\\n        \"+t._s(t.next.title||t.next.path)+\"\\n      \")]),t._v(\"\\n      →\\n    \")],1):t._e()])]):t._e()}),[],!1,null,null,null).exports},props:[\"sidebarItems\"]}),H=(n(180),Object(a.a)(E,(function(){var t=this._self._c;return t(\"main\",{staticClass:\"page\"},[this._t(\"top\"),this._v(\" \"),t(\"Content\",{staticClass:\"theme-default-content\"}),this._v(\" \"),t(\"PageEdit\"),this._v(\" \"),t(\"PageNav\",this._b({},\"PageNav\",{sidebarItems:this.sidebarItems},!1)),this._v(\" \"),this._t(\"bottom\")],2)}),[],!1,null,null,null).exports),R={name:\"Sidebar\",components:{SidebarLinks:n(168).default,NavLinks:x},props:[\"items\"]},U=(n(183),{name:\"Layout\",components:{Home:l,Page:H,Sidebar:Object(a.a)(R,(function(){var t=this._self._c;return t(\"aside\",{staticClass:\"sidebar\"},[t(\"NavLinks\"),this._v(\" \"),this._t(\"top\"),this._v(\" \"),t(\"SidebarLinks\",{attrs:{depth:0,items:this.items}}),this._v(\" \"),this._t(\"bottom\")],2)}),[],!1,null,null,null).exports,Navbar:$},data:()=>({isSidebarOpen:!1}),computed:{shouldShowNavbar(){const{themeConfig:t}=this.$site,{frontmatter:e}=this.$page;return!1!==e.navbar&&!1!==t.navbar&&(this.$title||t.logo||t.repo||t.nav||this.$themeLocaleConfig.nav)},shouldShowSidebar(){const{frontmatter:t}=this.$page;return!t.home&&!1!==t.sidebar&&this.sidebarItems.length},sidebarItems(){return Object(s.l)(this.$page,this.$page.regularPath,this.$site,this.$localePath)},pageClasses(){const t=this.$page.frontmatter.pageClass;return[{\"no-navbar\":!this.shouldShowNavbar,\"sidebar-open\":this.isSidebarOpen,\"no-sidebar\":!this.shouldShowSidebar},t]}},mounted(){this.$router.afterEach(()=>{this.isSidebarOpen=!1})},methods:{toggleSidebar(t){this.isSidebarOpen=\"boolean\"==typeof t?t:!this.isSidebarOpen,this.$emit(\"toggle-sidebar\",this.isSidebarOpen)},onTouchStart(t){this.touchStart={x:t.changedTouches[0].clientX,y:t.changedTouches[0].clientY}},onTouchEnd(t){const e=t.changedTouches[0].clientX-this.touchStart.x,n=t.changedTouches[0].clientY-this.touchStart.y;Math.abs(e)>Math.abs(n)&&Math.abs(e)>40&&(e>0&&this.touchStart.x<=80?this.toggleSidebar(!0):this.toggleSidebar(!1))}}}),G=Object(a.a)(U,(function(){var t=this,e=t._self._c;return e(\"div\",{staticClass:\"theme-container\",class:t.pageClasses,on:{touchstart:t.onTouchStart,touchend:t.onTouchEnd}},[t.shouldShowNavbar?e(\"Navbar\",{on:{\"toggle-sidebar\":t.toggleSidebar}}):t._e(),t._v(\" \"),e(\"div\",{staticClass:\"sidebar-mask\",on:{click:function(e){return t.toggleSidebar(!1)}}}),t._v(\" \"),e(\"Sidebar\",{attrs:{items:t.sidebarItems},on:{\"toggle-sidebar\":t.toggleSidebar},scopedSlots:t._u([{key:\"top\",fn:function(){return[t._t(\"sidebar-top\")]},proxy:!0},{key:\"bottom\",fn:function(){return[t._t(\"sidebar-bottom\")]},proxy:!0}],null,!0)}),t._v(\" \"),t.$page.frontmatter.home?e(\"Home\"):e(\"Page\",{attrs:{\"sidebar-items\":t.sidebarItems},scopedSlots:t._u([{key:\"top\",fn:function(){return[t._t(\"page-top\")]},proxy:!0},{key:\"bottom\",fn:function(){return[t._t(\"page-bottom\")]},proxy:!0}],null,!0)})],1)}),[],!1,null,null,null);e.default=G.exports}}]);"
  },
  {
    "path": "docs/assets/js/20.58c8b075.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[20],{201:function(t,a,s){\"use strict\";s.r(a);var e=s(6),n=Object(e.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"plugins\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#plugins\"}},[t._v(\"#\")]),t._v(\" Plugins\")]),t._v(\" \"),a(\"p\",[t._v(\"There is a standard \"),a(\"name-self\"),t._v(\" \"),a(\"a\",{attrs:{href:\"/ref/syntax\"}},[t._v(\"syntax\")]),t._v(\", which you\\nshould probably just use if all you want is easy-going JSON.\")],1),t._v(\" \"),a(\"p\",[t._v(\"But \"),a(\"name-self\"),t._v(\" itself is meant to be an extensible parser for\\nJSON-like languages, so you feel the need for a little more power,\\nyou've come to the right place!\")],1),t._v(\" \"),a(\"p\",[t._v(\"A small set of plugins are built into the standard package. You load\\nthem separately (this keeps the core small for web app use cases).\")]),t._v(\" \"),a(\"p\",[t._v(\"There is a wider set of standard plugins under the \"),a(\"a\",{attrs:{href:\"https://www.npmjs.com/org/jsonic\",target:\"_blank\",rel:\"noopener noreferrer\"}},[t._v(\"@jsonic\\norganisation\"),a(\"OutboundLink\")],1),t._v(\".\")]),t._v(\" \"),a(\"p\",[t._v(\"These plugins also serve as examples of how to write your own\\nextensions to JSON. Instead of starting from\\nscratch, \"),a(\"a\",{attrs:{href:\"/guide/improve-plugin-tutorial\"}},[t._v(\"copy and improve\")]),t._v(\"!\")]),t._v(\" \"),a(\"p\",[a(\"a\",{attrs:{name:\"builtin-plugins\"}})]),t._v(\" \"),a(\"h2\",{attrs:{id:\"built-in-plugins\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#built-in-plugins\"}},[t._v(\"#\")]),t._v(\" Built-in plugins\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"a\",{attrs:{href:\"#native\"}},[t._v(\"native\")]),t._v(\": Parse native JavaScript values such as \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\", etc.\")]),t._v(\" \"),a(\"li\",[a(\"a\",{attrs:{href:\"#csv\"}},[t._v(\"csv\")]),t._v(\": Parse CSV data that can contain embedded JSON.\")]),t._v(\" \"),a(\"li\",[a(\"a\",{attrs:{href:\"#hoover\"}},[t._v(\"hoover\")]),t._v(\": TODO.\")]),t._v(\" \"),a(\"li\",[a(\"a\",{attrs:{href:\"#json\"}},[t._v(\"json\")]),t._v(\": TODO.\")]),t._v(\" \"),a(\"li\",[a(\"a\",{attrs:{href:\"#dynamic\"}},[t._v(\"dynamic\")]),t._v(\": TODO.\")]),t._v(\" \"),a(\"li\",[a(\"a\",{attrs:{href:\"#multifile\"}},[t._v(\"multifile\")]),t._v(\": TODO.\")]),t._v(\" \"),a(\"li\",[a(\"a\",{attrs:{href:\"#legacy-stringify\"}},[t._v(\"legacy-stringify\")]),t._v(\": TODO.\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"native\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#native\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"native\")])]),t._v(\" \"),a(\"p\",[a(\"a\",{attrs:{href:\"/plugin/native\"}},[t._v(\"details\")]),t._v(\" →\")]),t._v(\" \"),a(\"p\",[t._v(\"Parse native JavaScript values such as \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\", \"),a(\"code\",[t._v(\"Infinity\")]),t._v(\",\\nliteral regular expressions, and ISO-formatted dates.\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" Native \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/native'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Native\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),a(\"h3\",{attrs:{id:\"csv\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#csv\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"csv\")])]),t._v(\" \"),a(\"p\",[a(\"a\",{attrs:{href:\"/plugin/csv\"}},[t._v(\"details\")]),t._v(\" →\")]),t._v(\" \"),a(\"p\",[t._v(\"Parse CSV data that can contain embedded JSON, and also supports\\ncomments and other \"),a(\"name-self\"),t._v(\" sugar.\")],1),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" Csv \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/csv'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Csv\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === [{\"a\":1, \"b\":2}, {\"a\":3,\"b\":4}]')]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[a(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\na,b      // first line is headers\\n1,2\\n3,4\\n\")]),a(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \\n\")])])]),a(\"p\",[a(\"a\",{attrs:{name:\"standard-plugins\"}})]),t._v(\" \"),a(\"h3\",{attrs:{id:\"hoover\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#hoover\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"hoover\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"json\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#json\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"json\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"dynamic\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#dynamic\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"dynamic\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"multifile\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#multifile\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"multifile\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"legacy-stringify\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#legacy-stringify\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"legacy-stringify\")])]),t._v(\" \"),a(\"h2\",{attrs:{id:\"standard-plugins\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#standard-plugins\"}},[t._v(\"#\")]),t._v(\" Standard plugins\")]),t._v(\" \"),a(\"p\",[a(\"a\",{attrs:{name:\"community-plugins\"}})]),t._v(\" \"),a(\"h2\",{attrs:{id:\"community-plugins\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#community-plugins\"}},[t._v(\"#\")]),t._v(\" Community plugins\")])])}),[],!1,null,null,null);a.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/21.0c46ffa9.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[21],{204:function(t,s,a){\"use strict\";a.r(s);var e=a(6),n=Object(e.a)({},(function(){var t=this,s=t._self._c;return s(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[s(\"h1\",{attrs:{id:\"json\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#json\"}},[t._v(\"#\")]),t._v(\" \"),s(\"code\",[t._v(\"json\")])]),t._v(\" \"),s(\"p\",[t._v(\"The standard \"),s(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),s(\"code\",[t._v(\"true\")]),t._v(\", \"),s(\"code\",[t._v(\"false\")]),t._v(\", \"),s(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),s(\"p\",[t._v(\"Wouldn't it be nice to have \"),s(\"code\",[t._v(\"undefined\")]),t._v(\", \"),s(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),s(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),s(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),s(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\"json\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),s(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),s(\"h2\",{attrs:{id:\"usage\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),s(\"p\",[t._v(\"To use the plugin, \"),s(\"code\",[t._v(\"require\")]),t._v(\" or \"),s(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),s(\"code\",[t._v(\"jsonic/plugin/json\")]),t._v(\":\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Json \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/json'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Json \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/json'\")]),t._v(\"\\n\")])])]),s(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[s(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),s(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),s(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"quick-example\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Json \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/json'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Json\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),s(\"h2\",{attrs:{id:\"syntax\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),s(\"p\",[t._v(\"The standard \"),s(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),s(\"h3\",{attrs:{id:\"json-value-keywords\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#json-value-keywords\"}},[t._v(\"#\")]),t._v(\" Json value keywords\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),s(\"code\",[t._v(\"+\")]),t._v(\" or \"),s(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),s(\"h3\",{attrs:{id:\"regular-expressions\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),s(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),s(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),s(\"h3\",{attrs:{id:\"iso-dates\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),s(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),s(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-jsonic extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[s(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),s(\"h2\",{attrs:{id:\"options\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),s(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),s(\"h2\",{attrs:{id:\"implementation\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),s(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),s(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/json.ts\"}},[s(\"code\",[t._v(\"plugin/json.ts\")])]),t._v(\".\")]),t._v(\" \"),s(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);s.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/22.965cbc0d.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{206:function(t,a,s){\"use strict\";s.r(a);var e=s(6),n=Object(e.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"legacy-stringify\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#legacy-stringify\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"legacy-stringify\")])]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),a(\"code\",[t._v(\"true\")]),t._v(\", \"),a(\"code\",[t._v(\"false\")]),t._v(\", \"),a(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),a(\"p\",[t._v(\"Wouldn't it be nice to have \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),a(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),a(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),a(\"p\",[t._v(\"The \"),a(\"code\",[t._v(\"legacy-stringify\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),a(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),a(\"h2\",{attrs:{id:\"usage\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),a(\"p\",[t._v(\"To use the plugin, \"),a(\"code\",[t._v(\"require\")]),t._v(\" or \"),a(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),a(\"code\",[t._v(\"jsonic/plugin/legacy-stringify\")]),t._v(\":\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Legacy\"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"-\")]),t._v(\"Stringify \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/legacy-stringify'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Legacy\"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"-\")]),t._v(\"Stringify \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/legacy-stringify'\")]),t._v(\"\\n\")])])]),a(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[a(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),a(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),a(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),a(\"h3\",{attrs:{id:\"quick-example\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Legacy\"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"-\")]),t._v(\"Stringify \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/legacy-stringify'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Legacy\"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"-\")]),t._v(\"Stringify\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),a(\"h2\",{attrs:{id:\"syntax\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),a(\"h3\",{attrs:{id:\"legacy-stringify-value-keywords\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#legacy-stringify-value-keywords\"}},[t._v(\"#\")]),t._v(\" Legacy-Stringify value keywords\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),a(\"code\",[t._v(\"+\")]),t._v(\" or \"),a(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"regular-expressions\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),a(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),a(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"iso-dates\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),a(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),a(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),a(\"h2\",{attrs:{id:\"options\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),a(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"implementation\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),a(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),a(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/legacy-stringify.ts\"}},[a(\"code\",[t._v(\"plugin/legacy-stringify.ts\")])]),t._v(\".\")]),t._v(\" \"),a(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);a.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/23.18c270f0.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[23],{207:function(t,a,s){\"use strict\";s.r(a);var e=s(6),n=Object(e.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"multifile\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#multifile\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"multifile\")])]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),a(\"code\",[t._v(\"true\")]),t._v(\", \"),a(\"code\",[t._v(\"false\")]),t._v(\", \"),a(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),a(\"p\",[t._v(\"Wouldn't it be nice to have \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),a(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),a(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),a(\"p\",[t._v(\"The \"),a(\"code\",[t._v(\"multifile\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),a(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),a(\"h2\",{attrs:{id:\"usage\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),a(\"p\",[t._v(\"To use the plugin, \"),a(\"code\",[t._v(\"require\")]),t._v(\" or \"),a(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),a(\"code\",[t._v(\"jsonic/plugin/multifile\")]),t._v(\":\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Multifile \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/multifile'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Multifile \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/multifile'\")]),t._v(\"\\n\")])])]),a(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[a(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),a(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),a(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),a(\"h3\",{attrs:{id:\"quick-example\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Multifile \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/multifile'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Multifile\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),a(\"h2\",{attrs:{id:\"syntax\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),a(\"h3\",{attrs:{id:\"multifile-value-keywords\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#multifile-value-keywords\"}},[t._v(\"#\")]),t._v(\" Multifile value keywords\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),a(\"code\",[t._v(\"+\")]),t._v(\" or \"),a(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"regular-expressions\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),a(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),a(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"iso-dates\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),a(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),a(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),a(\"h2\",{attrs:{id:\"options\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),a(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"implementation\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),a(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),a(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/multifile.ts\"}},[a(\"code\",[t._v(\"plugin/multifile.ts\")])]),t._v(\".\")]),t._v(\" \"),a(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);a.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/24.5895d76a.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[24],{205:function(t,a,s){\"use strict\";s.r(a);var e=s(6),n=Object(e.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"native\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#native\"}},[t._v(\"#\")]),t._v(\" \"),a(\"code\",[t._v(\"native\")])]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax only supports standard JSON value\\nkeywords: \"),a(\"code\",[t._v(\"true\")]),t._v(\", \"),a(\"code\",[t._v(\"false\")]),t._v(\", \"),a(\"code\",[t._v(\"null\")]),t._v(\".\")],1),t._v(\" \"),a(\"p\",[t._v(\"Wouldn't it be nice to have \"),a(\"code\",[t._v(\"undefined\")]),t._v(\", \"),a(\"code\",[t._v(\"Infinity\")]),t._v(\", \"),a(\"code\",[t._v(\"NaN\")]),t._v(\" as well?\\nAnd while we're at it, how about recognizing literal \"),a(\"code\",[t._v(\"/regexp/\")]),t._v(\"\\nsyntax, and ISO (\"),a(\"code\",[t._v(\"2021-03-19T17:15:51.845Z\")]),t._v(\") dates?\")]),t._v(\" \"),a(\"p\",[t._v(\"The \"),a(\"code\",[t._v(\"native\")]),t._v(\" plugin will do this for you!\")]),t._v(\" \"),a(\"p\",[t._v('This is a good plugin to copy and extend if you just want to add some\\n\"magic\" values to your source data.')]),t._v(\" \"),a(\"h2\",{attrs:{id:\"usage\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#usage\"}},[t._v(\"#\")]),t._v(\" Usage\")]),t._v(\" \"),a(\"p\",[t._v(\"To use the plugin, \"),a(\"code\",[t._v(\"require\")]),t._v(\" or \"),a(\"code\",[t._v(\"import\")]),t._v(\" the module path: \"),a(\"code\",[t._v(\"jsonic/plugin/native\")]),t._v(\":\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Node.js\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Native \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/native'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Web\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"import\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Native \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"from\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/native'\")]),t._v(\"\\n\")])])]),a(\"p\",{staticStyle:{color:\"#888\",\"text-align\":\"right\",\"margin-top\":\"-20px\"}},[a(\"small\",{staticStyle:{\"font-size\":\"10px\"}},[t._v(\"(The convention for loading modules that are Jsonic plugins is to deconstruct: \"),a(\"code\",[t._v(\"{ PluginName }\")]),t._v(\" )\")])]),t._v(\" \"),a(\"p\",[t._v(\"Once loaded, parse your source data as normal.\")]),t._v(\" \"),a(\"h3\",{attrs:{id:\"quick-example\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#quick-example\"}},[t._v(\"#\")]),t._v(\" Quick example\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-js extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[a(\"code\",[a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" Native \"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"require\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'jsonic/plugin/native'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// or import\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" extra \"),a(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Native\"),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),a(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"extra\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),a(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:NaN'\")]),a(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),a(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// === {\"a\": NaN}')]),t._v(\"\\n\")])])]),a(\"h2\",{attrs:{id:\"syntax\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[t._v(\"#\")]),t._v(\" Syntax\")]),t._v(\" \"),a(\"p\",[t._v(\"The standard \"),a(\"name-self\"),t._v(\" syntax remains available, with the following\\nextensions:\")],1),t._v(\" \"),a(\"h3\",{attrs:{id:\"native-value-keywords\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#native-value-keywords\"}},[t._v(\"#\")]),t._v(\" Native value keywords\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"undefined\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"NaN\")])]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"Infinity\")]),t._v(\" (optional prefix \"),a(\"code\",[t._v(\"+\")]),t._v(\" or \"),a(\"code\",[t._v(\"-\")]),t._v(\")\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"regular-expressions\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#regular-expressions\"}},[t._v(\"#\")]),t._v(\" Regular Expressions\")]),t._v(\" \"),a(\"p\",[t._v(\"Characters between '/' and '/' are converted to a \"),a(\"code\",[t._v(\"RegExp\")]),t._v(\" object.\")]),t._v(\" \"),a(\"ul\",[a(\"li\",[a(\"code\",[t._v(\"/a/\")]),t._v(\" // === new RegExp('a')\")]),t._v(\" \"),a(\"li\",[a(\"code\",[t._v(\"/\\\\//g\")]),t._v(\" // === new RegExp('\\\\/','g')\")])]),t._v(\" \"),a(\"h3\",{attrs:{id:\"iso-dates\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#iso-dates\"}},[t._v(\"#\")]),t._v(\" ISO Dates\")]),t._v(\" \"),a(\"p\",[t._v(\"Unquoted text that matches the ISO date format is converted into a \"),a(\"code\",[t._v(\"Date\")]),t._v(\" object.\")]),t._v(\" \"),a(\"div\",{staticClass:\"language-jsonic extra-class\"},[a(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[a(\"code\",[t._v(\"when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \\n\")])])]),a(\"h2\",{attrs:{id:\"options\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[t._v(\"#\")]),t._v(\" Options\")]),t._v(\" \"),a(\"p\",[t._v(\"This plugin has no options.\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"implementation\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#implementation\"}},[t._v(\"#\")]),t._v(\" Implementation\")]),t._v(\" \"),a(\"p\",[t._v(\"The source code for this plugin is\\nhere: \"),a(\"a\",{attrs:{href:\"github.com/jsonicjs/jsonic/blob/master/plugin/native.ts\"}},[a(\"code\",[t._v(\"plugin/native.ts\")])]),t._v(\".\")]),t._v(\" \"),a(\"p\",[t._v(\"TODO - discuss\")])])}),[],!1,null,null,null);a.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/25.a347f1d6.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[25],{208:function(t,s,a){\"use strict\";a.r(s);var n=a(6),e=Object(n.a)({},(function(){var t=this,s=t._self._c;return s(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[s(\"h1\",{attrs:{id:\"api\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#api\"}},[t._v(\"#\")]),t._v(\" API\")]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"name-self\"),t._v(\" API is deliberately kept as small as possible.\")],1),t._v(\" \"),s(\"div\",{staticClass:\"custom-block tip\"},[s(\"p\",{staticClass:\"custom-block-title\"},[t._v(\"TIP\")]),t._v(\" \"),s(\"p\",[t._v(\"If all you want to do is parse easy-going JSON, you don't need this API!\\nJust call \"),s(\"code\",[t._v(\"Jsonic(...your-json-source...)\")]),t._v(\" and use the return value.\")]),t._v(\" \"),s(\"p\",[t._v(\"This API is for customizing the JSON parser, so if that is your game, read on...\")])]),t._v(\" \"),s(\"p\",[t._v(\"The default import \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" is intended as utility function and to be\\nused as-is. For customization, and to access the API methods, create a\\nnew \"),s(\"name-self\"),t._v(\" instance with \"),s(\"code\",[t._v(\"Jsonic.make()\")]),t._v(\".\")],1),t._v(\" \"),s(\"p\",[t._v(\"Some plugins may decorate the main \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" object with additional methods.\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[t._v(\"     🍓 🍐 \"),s(\"a\",{attrs:{href:\"#jsonic-just-parse-already\"}},[s(\"code\",[t._v(\"Jsonic\")])])]),t._v(\" \"),s(\"li\",[t._v(\"          🍐 \"),s(\"a\",{attrs:{href:\"#id-unique-instance-identifier\"}},[s(\"code\",[t._v(\"id\")])])]),t._v(\" \"),s(\"li\",[t._v(\"     🍓      \"),s(\"a\",{attrs:{href:\"#tostring-string-description-of-the-jsonic-instance\"}},[s(\"code\",[t._v(\"toString\")])])]),t._v(\" \"),s(\"li\",[t._v(\"     🍓      \"),s(\"a\",{attrs:{href:\"#make-create-a-new-customizable-jsonic-instance\"}},[s(\"code\",[t._v(\"make\")])])]),t._v(\" \"),s(\"li\",[t._v(\"🍒 🍓 🍐 \"),s(\"a\",{attrs:{href:\"#options-get-and-set-options-for-a-jsonic-instance\"}},[s(\"code\",[t._v(\"options\")])])]),t._v(\" \"),s(\"li\",[t._v(\"🍒 🍓      \"),s(\"a\",{attrs:{href:\"#use-register-a-plugin\"}},[s(\"code\",[t._v(\"use\")])])]),t._v(\" \"),s(\"li\",[t._v(\"🍒 🍓      \"),s(\"a\",{attrs:{href:\"#rule-define-or-modify-a-parser-rule\"}},[s(\"code\",[t._v(\"rule\")])])]),t._v(\" \"),s(\"li\",[t._v(\"🍒 🍓      \"),s(\"a\",{attrs:{href:\"#lex-define-a-lex-matcher\"}},[s(\"code\",[t._v(\"lex\")])])]),t._v(\" \"),s(\"li\",[t._v(\"🍒 🍓 🍐 \"),s(\"a\",{attrs:{href:\"#token-resolve-a-token-by-name-or-index\"}},[s(\"code\",[t._v(\"token\")])])])]),t._v(\" \"),s(\"small\",[t._v(\"\\n(🍒 available after \"),s(\"code\",[t._v(\"Jsonic.make()\")]),t._v(\", 🍓 method or function, 🍐 property or set of properties)\\n\")]),t._v(\" \"),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"h2\",{attrs:{id:\"methods\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#methods\"}},[t._v(\"#\")]),t._v(\" Methods\")]),t._v(\" \"),s(\"h3\",{attrs:{id:\"jsonic-just-parse-already\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#jsonic-just-parse-already\"}},[t._v(\"#\")]),t._v(\" 🍓 🍐 \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\": Just parse already!\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\"Jsonic(source: string, meta?: object): any\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": \"),s(\"code\",[t._v(\"any\")]),t._v(\" Object (or value) containing the parsed JSON data.\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"source: string\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"required\")])]),t._v(\" : The JSON source text to parse.\")]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"meta?: object\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Provide meta data for this parse.\")])]),t._v(\" \"),s(\"p\",[t._v(\"This is the top level utility function that parses JSON source text\\nusing the standard syntax extensions of \"),s(\"name-self\"),t._v(\". It cannot be\\ncustomized (call \"),s(\"code\",[t._v(\"Jsonic.make()\")]),t._v(\" to do that), and always behaves the\\nsame way.\")],1),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍓 Function: \"),s(\"code\",[t._v(\"Jsonic(...)\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍐 Property set: \"),s(\"code\",[t._v(\"jsonic.id\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" earth \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'name: Terra, moons: [{name: Luna}]'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\"earth\")]),t._v(\" variable now contains the following data:\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-json extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-json\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// earth ->\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token property\"}},[t._v('\"name\"')]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\"Terra\"')]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token property\"}},[t._v('\"moons\"')]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n      \"),s(\"span\",{pre:!0,attrs:{class:\"token property\"}},[t._v('\"name\"')]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\"Luna\"')]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\")])])]),s(\"h4\",{attrs:{id:\"example-using-the-meta-parameter\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-using-the-meta-parameter\"}},[t._v(\"#\")]),t._v(\" ✨ Example: using the \"),s(\"code\",[t._v(\"meta\")]),t._v(\" parameter\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" one \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'1'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"log\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"-\")]),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"1\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// one === 1\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\"meta\")]),t._v(\" value of \"),s(\"code\",[t._v(\"{log:-1}\")]),t._v(\" prints a debug log of the lexing and\\nparsing process to \"),s(\"code\",[t._v(\"STDOUT\")]),t._v(\". Very useful when you are writing a\\nplugin! See the plugin section for more details.\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-sh extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-sh\"}},[s(\"code\",[t._v(\"lex\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('#NR\\t\"1\"\\t1\\t0:1\\t@LTP')]),t._v(\"\\nlex\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"#ZZ\\t\\t1\\t0:1\\t@LTP\")]),t._v(\"\\nrule\\tval/1\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"open\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('#NR #ZZ]\\t[\"1\" ]')]),t._v(\"\\nparse\\tval/1\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"open\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token assign-left variable\"}},[t._v(\"alt\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"10\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('#NR]\\t0\\tp=\\tr=\\tb=\\t#NR\\t[\"1\"]\\tc:\\tn:')]),t._v(\"\\nlex\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"#ZZ\\t\\t1\\t0:1\\t@LTP\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"node\")]),t._v(\"\\tval/1\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"open\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token assign-left variable\"}},[t._v(\"w\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\"Z\\t\\nstack\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),t._v(\"\\t\\nrule\\tval/1\\tclose\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"1\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"#ZZ #ZZ]\\t[ ]\")]),t._v(\"\\nparse\\tval/1\\tclose\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token assign-left variable\"}},[t._v(\"alt\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"2\")]),t._v(\"\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"#AA]\\t1\\tp=\\tr=\\tb=1\\t#ZZ\\t[null]\\tc:\\tn:\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"node\")]),t._v(\"\\tval/1\\tclose\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token assign-left variable\"}},[t._v(\"w\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\"O\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"1\")]),t._v(\"\\nstack\\t\"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Apart from \"),s(\"code\",[t._v(\"log\")]),t._v(\", the meta object contains plugin-specific\\nparameters. See your friendly neighbourhood plugin documentation for\\nmore.\")]),t._v(\" \"),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"id-unique-instance-identifier\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#id-unique-instance-identifier\"}},[t._v(\"#\")]),t._v(\" 🍐 \"),s(\"code\",[t._v(\"id\")]),t._v(\": Unique instance identifier\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".id: string\")])]),t._v(\" \"),s(\"p\",[t._v(\"It's useful to be able to identify unique instances when you're debugging.\")]),t._v(\" \"),s(\"p\",[t._v(\"Use the \"),s(\"code\",[t._v(\"tag\")]),t._v(\" option to set a custom tag for the instance.\")]),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍐 Property: \"),s(\"code\",[t._v(\"jsonic.id\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-2\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-2\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// format: Jsonic/<Date.now()>/<Math.random()[2:8]>/<options.tag>\")]),t._v(\"\\nJsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"id \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'Jsonic/1614085850042/022636/-'\")]),t._v(\"\\nJsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"tag\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'foo'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"id \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'Jsonic/1614085851902/375149/foo'\")]),t._v(\"\\n\")])])]),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"tostring-string-description-of-the-jsonic-instance\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#tostring-string-description-of-the-jsonic-instance\"}},[t._v(\"#\")]),t._v(\" 🍓 \"),s(\"code\",[t._v(\"toString\")]),t._v(\": String description of the Jsonic instance\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".toString(): string\")])]),t._v(\" \"),s(\"p\",[t._v(\"Returns the value of the \"),s(\"code\",[t._v(\".id\")]),t._v(\" property as the string description of\\nthe instance.\")]),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.toString()\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-3\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-3\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// format: Jsonic/<Date.now()>/<Math.random()[2:8]>/<options.tag>\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"''\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"+\")]),t._v(\"Jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'Jsonic/1614085850042/022636/-'\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"''\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"+\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"tag\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'foo'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'Jsonic/1614085851902/375149/foo'\")]),t._v(\"\\n\")])])]),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"make-create-a-new-customizable-jsonic-instance\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#make-create-a-new-customizable-jsonic-instance\"}},[t._v(\"#\")]),t._v(\" 🍓 \"),s(\"code\",[t._v(\"make\")]),t._v(\": Create a new customizable Jsonic instance.\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".make(options?: object): Jsonic\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" A new Jsonic instance that can be modified.\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"options?: object\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Partial options tree.\")])]),t._v(\" \"),s(\"p\",[t._v(\"The returned function can be used in the same way as the top level\\n\"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" function. It also exposes the rest of the API (such as\\n\"),s(\"code\",[t._v(\"options\")]),t._v(\") so you can customize the parser.\")]),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.make(...)\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-4\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-4\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" array_of_numbers \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'1,2,3'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// array_of_numbers === [1, 2, 3]\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" no_numbers_please \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"number\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"lex\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token boolean\"}},[t._v(\"false\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" array_of_strings \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"no_numbers_please\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'1,2,3'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// array_of_strings === ['1', '2', '3']\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"You must call \"),s(\"code\",[t._v(\"make\")]),t._v(\" to customize \"),s(\"name-self\"),t._v(\". This protects the\\n\"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" import which is a shared global object.\")],1),t._v(\" \"),s(\"p\",[t._v(\"Calling \"),s(\"code\",[t._v(\"make\")]),t._v(\" again on the new \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" instance will generate\\nanother new instance that inherits the configuration of its parent and\\ncan itself be independently customized. Which is what you want.\")]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-child-instances-inherit-from-parent-instances\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-child-instances-inherit-from-parent-instances\"}},[t._v(\"#\")]),t._v(\" ✨ Example: child instances inherit from parent instances\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" no_numbers_please \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"number\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"lex\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token boolean\"}},[t._v(\"false\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"no_numbers_please\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'1,2,3'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['1', '2', '3'] as before\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" pipe_separated \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" no_numbers_please\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token string-property property\"}},[t._v(\"'#CA'\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"c\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'|'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"pipe_separated\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'1|2|3'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['1', '2', '3'], but:\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"pipe_separated\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'1,2,3'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === '1,2,3' !!!\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"To understand how the \"),s(\"code\",[t._v(\"token\")]),t._v(\" option works, and all the other\\noptions, see the \"),s(\"RouterLink\",{attrs:{to:\"/ref/options/\"}},[t._v(\"Options\")]),t._v(\" section.\")],1),t._v(\" \"),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"options-get-and-set-options-for-a-jsonic-instance\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options-get-and-set-options-for-a-jsonic-instance\"}},[t._v(\"#\")]),t._v(\" 🍓 🍐 \"),s(\"code\",[t._v(\"options\")]),t._v(\": Get and set options for a Jsonic instance.\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".options(options: object): object\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": \"),s(\"code\",[t._v(\"object\")]),t._v(\" merged object containing the full option tree\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"options: object\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"required\")])]),t._v(\" : Partial options tree.\")])]),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍒 Only available on: \"),s(\"code\",[t._v(\"jsonic = Jsonic.make()\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.options(...)\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍐 Property set of option tree: \"),s(\"code\",[t._v(\"jsonic.options.number.lex\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-5\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-5\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\njsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"options\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"comment\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"lex \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === true\")]),t._v(\"\\njsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"options\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"comment\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"lex \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === true - as a convenience\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" no_comment \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\nno_comment\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"options\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"comment\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"lex\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token boolean\"}},[t._v(\"false\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// Returns {\"a\": 1, \"#b\": 2}')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"no_comment\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\n  a: 1\\n  #b: 2\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v('// Whereas this returns only {\"a\": 1} as # starts a one line comment')]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-string\"}},[s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"\\n  a: 1\\n  #b: 2\\n\")]),s(\"span\",{pre:!0,attrs:{class:\"token template-punctuation string\"}},[t._v(\"`\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\")])])]),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"use-register-a-plugin\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#use-register-a-plugin\"}},[t._v(\"#\")]),t._v(\" 🍒 🍓 \"),s(\"code\",[t._v(\"use\")]),t._v(\": Register a plugin.\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".use(plugin: function, plugin_options?: object): Jsonic\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": Jsonic instance (this allows chaining)\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"plugin: function\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"required\")])]),t._v(\" : Plugin definition function\\n\"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"(jsonic: Jsonic) => Jsonic\")])])])]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"plugin_options?: object\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Plugin-specific options\")])]),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍒 Only available on: \"),s(\"code\",[t._v(\"jsonic = Jsonic.make()\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.use(...)\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-6\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-6\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"piper\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"options\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token string-property property\"}},[t._v(\"'#CA'\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"c\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'|'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a|b|c'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['a', 'b', 'c']\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Plugins are defined by a function that takes the \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" instance as\\na first parameter, and then changes the options and parsing rules of\\nthat instance. For more, see the \"),s(\"a\",{attrs:{href:\"/guide/write-a-plugin\"}},[t._v(\"plugin writing guide\")]),t._v(\".\")]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-plugin-options\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-plugin-options\"}},[t._v(\"#\")]),t._v(\" ✨ Example: plugin options\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"sepper\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" sep \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"options\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"plugin\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"sepper\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"sep\\n  jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"options\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token string-property property\"}},[t._v(\"'#CA'\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"c\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\"sep\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"sepper\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"sep\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"';'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a;b;c'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['a', 'b', 'c']\")]),t._v(\"\\n\")])])]),s(\"p\",[t._v(\"Plugin options are added to the main options under the \"),s(\"code\",[t._v(\"plugin\")]),t._v(\" key using the\\nname of the plugin function as a sub-key. Thus \"),s(\"code\",[t._v(\"function sepper(...)\")]),t._v(\" means that\\n\"),s(\"code\",[t._v(\"jsonic.options.plugin.sepper\")]),t._v(\" contains the plugin option.\")]),t._v(\" \"),s(\"p\",[t._v(\"Notice that you can refer to options directly as properties of the\\n\"),s(\"code\",[t._v(\".options\")]),t._v(\" method, as a convenience.\")]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-plugin-chaining\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-plugin-chaining\"}},[t._v(\"#\")]),t._v(\" ✨ Example: plugin chaining\")]),t._v(\" \"),s(\"p\",[t._v(\"When defining a custom \"),s(\"name-self\"),t._v(\" instance, you'll probably be registering\\nmultiple plugins. The \"),s(\"code\",[t._v(\".use\")]),t._v(\" method can be chained to make this easier.\")],1),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"foo\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function-variable function\"}},[t._v(\"foo\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"return\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"1\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"bar\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function-variable function\"}},[t._v(\"bar\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"return\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"this\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"foo\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"*\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"2\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"foo\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"use\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"bar\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// jsonic.foo() === 1\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// jsonic.bar() === 2\")]),t._v(\"\\n\")])])]),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"rule-define-or-modify-a-parser-rule\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#rule-define-or-modify-a-parser-rule\"}},[t._v(\"#\")]),t._v(\" 🍒 🍓 \"),s(\"code\",[t._v(\"rule\")]),t._v(\": Define or modify a parser rule.\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".rule(name?: string, define?: function): RuleSpec\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": \"),s(\"code\",[t._v(\"RuleSpec\")]),t._v(\" Rule specification\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"name?: string\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Rule name\")]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"define?: function\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Rule definition function\\n\"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"(rs: RuleSpec, rsm: RuleSpecMap) => RuleSpec\")])])])])]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\".rule\")]),t._v(\" method (and the \"),s(\"code\",[t._v(\".lex\")]),t._v(\" and \"),s(\"code\",[t._v(\".token\")]),t._v(\") methods ar intended\\nmostly for use inside plugin definitions. They allow you to modify the way that\\n\"),s(\"name-self\"),t._v(\" works.\")],1),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\".rule\")]),t._v(\" method takes the name of a rule and if it exists, provides\\nthe rule specification as first parameter to the rule definition\\nfunction. If the rule does not exist, you can create a new rule\\nspecification and return that to define a new rule.\")]),t._v(\" \"),s(\"p\",[t._v(\"The details of rule definition are covered in the \"),s(\"RouterLink\",{attrs:{to:\"/plugins/\"}},[t._v(\"Plugins\")]),t._v(\"\\nsection.\")],1),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍒 Only available on: \"),s(\"code\",[t._v(\"jsonic = Jsonic.make()\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.rule(...)\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-7\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-7\"}},[t._v(\"#\")]),t._v(\" 🍒 ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" concat \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Get all the rules\")]),t._v(\"\\nObject\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"keys\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"concat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['val', 'map', 'list', 'pair', 'elem']\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Get a rule by name\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" val_rule \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'val'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// val_rule.name === 'val'\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Modify a rule \")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"ST\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" concat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"ST\")]),t._v(\"\\nconcat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'val'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=>\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Concatentate strings (ST) instead of forming array elements\")]),t._v(\"\\n  rule\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"def\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"open\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"unshift\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"s\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"ST\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"ST\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function-variable function\"}},[t._v(\"h\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"alt\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\"rule\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\"ctx\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=>\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n    rule\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"node \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" ctx\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"t0\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"val \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"+\")]),t._v(\" ctx\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"t1\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"val\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Disable default value handling\")]),t._v(\"\\n    rule\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"bc \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token boolean\"}},[t._v(\"false\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"concat\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\\'\"a\" \"b\"\\'')]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 'ab'\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"Jsonic\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v('\\'\"a\" \"b\"\\'')]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === ['a', 'b']\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Create a new rule (for a new token)\")]),t._v(\"\\nconcat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"options\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token string-property property\"}},[t._v(\"'#HH'\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"c\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'%'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"HH\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" concat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"HH\")]),t._v(\"\\nconcat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'hundred'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=>\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"return\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"new\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token class-name\"}},[t._v(\"RuleSpec\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token function-variable function\"}},[t._v(\"after_open\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=>\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n      \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// % always becomes the value 100\")]),t._v(\"\\n      rule\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"node \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"100\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\nconcat\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"rule\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'val'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"rulespec\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=>\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n  rulespec\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"def\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"open\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"unshift\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"s\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"HH\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"p\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'hundred'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"concat\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'{x:1, y:%}'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === {x:1, y:100}\")]),t._v(\"\\n\")])])]),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"lex-define-a-lex-matcher\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#lex-define-a-lex-matcher\"}},[t._v(\"#\")]),t._v(\" 🍒 🍓 \"),s(\"code\",[t._v(\"lex\")]),t._v(\": Define a lex matcher.\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".lex(state?: Tin, match?: function): LexMatcher[]\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": \"),s(\"code\",[t._v(\"LexMatcher[]\")]),t._v(\" Ordered list of lex matchers for this lex state.\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"state?: Tin\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Token identifier number\")]),t._v(\" \"),s(\"li\",[s(\"code\",[t._v(\"matcher?: function\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"optional\")])]),t._v(\" : Lex matcher function\\n\"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"(state: LexMatcherState) => LexMatcherResult\")])])])])]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\".lex\")]),t._v(\" method (like the \"),s(\"code\",[t._v(\".rule\")]),t._v(\" and \"),s(\"code\",[t._v(\".token\")]),t._v(\" methods) allows you\\nto change the way that \"),s(\"name-self\"),t._v(\" works. The \"),s(\"code\",[t._v(\".lex\")]),t._v(\" method attaches\\na matcher function to a given lex state. This matcher has the\\nopportunity to examine the current source text position and generate a\\ntoken, or pass lexing over to the standard machinery.\")],1),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"name-self\"),t._v(\" is state based, although most of the normal lexing\\nhappens in the top lex state (LTP).\")],1),t._v(\" \"),s(\"p\",[t._v(\"For more about lex matchers, see the \"),s(\"RouterLink\",{attrs:{to:\"/plugins/\"}},[t._v(\"Plugins\")]),t._v(\" section.\")],1),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍒 Only available on: \"),s(\"code\",[t._v(\"jsonic = Jsonic.make()\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.lex(...)\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-8\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-8\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" tens \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"VL\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" tens\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"VL\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"LTP\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" tens\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"LTP\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Match characters in the top lex state (LTP)\")]),t._v(\"\\ntens\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"lex\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"LTP\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"function\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"tens_matcher\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token parameter\"}},[t._v(\"state\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// % -> 10, %% -> 20, %%% -> 30, etc.\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" marks \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" state\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"src\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"substring\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"state\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"sI\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"match\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token regex\"}},[s(\"span\",{pre:!0,attrs:{class:\"token regex-delimiter\"}},[t._v(\"/\")]),s(\"span\",{pre:!0,attrs:{class:\"token regex-source language-regex\"}},[t._v(\"^%+\")]),s(\"span\",{pre:!0,attrs:{class:\"token regex-delimiter\"}},[t._v(\"/\")])]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"if\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),t._v(\"marks\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" len \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" marks\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"[\")]),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"0\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"]\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"length\\n    state\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"tin \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"VL\")]),t._v(\"\\n    state\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"val \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"10\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"*\")]),t._v(\" len\\n\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// Update lexer position and column\")]),t._v(\"\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"return\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"{\")]),t._v(\"\\n      \"),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"sI\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" state\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"sI \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"+\")]),t._v(\" len\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\",\")]),t._v(\"\\n      \"),s(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[t._v(\"cI\")]),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\":\")]),t._v(\" state\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"cI \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"+\")]),t._v(\" len\\n    \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n  \"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),t._v(\"\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"}\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\n\\n\"),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"tens\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'a:1,b:%%,c:[%%%%]'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === {a:1, b:20, c:[40]}\")]),t._v(\"\\n\")])])]),s(\"p\",[s(\"br\"),s(\"br\")]),t._v(\" \"),s(\"hr\"),t._v(\" \"),s(\"h3\",{attrs:{id:\"token-resolve-a-token-by-name-or-index\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#token-resolve-a-token-by-name-or-index\"}},[t._v(\"#\")]),t._v(\" 🍒 🍓 🍐 \"),s(\"code\",[t._v(\"token\")]),t._v(\": Resolve a token by name or index.\")]),t._v(\" \"),s(\"p\",[s(\"code\",[t._v(\".token(ref: Tin | string): string | Tin\")])]),t._v(\" \"),s(\"p\",[s(\"em\",[t._v(\"Returns\")]),t._v(\": \"),s(\"code\",[t._v(\"string | Tin\")]),t._v(\" Token identifier or token name (opposite of \"),s(\"code\",[t._v(\"ref\")]),t._v(\" type).\")]),t._v(\" \"),s(\"ul\",[s(\"li\",[s(\"code\",[t._v(\"ref: Tin | string\")]),t._v(\" \"),s(\"em\",[s(\"small\",[t._v(\"required\")])]),t._v(\" : Token identifier number or name\")])]),t._v(\" \"),s(\"p\",[t._v(\"The \"),s(\"code\",[t._v(\".token\")]),t._v(\" method lets you get the unique token identification\\nnumber (\"),s(\"code\",[t._v(\"Tin\")]),t._v(\") of a named token in the current \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" instance, or\\nlookup the name of a token by its \"),s(\"code\",[t._v(\"Tin\")]),t._v(\".\")]),t._v(\" \"),s(\"p\",[t._v(\"As lexer states must also be unique, they are generated as\\npseudo-tokens using the same index of tokens. While child \"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\"\\ninstances (generated with \"),s(\"code\",[t._v(\".make\")]),t._v(\") will inherit the index of their\\nparents, in general token identification is usable only for a specific\\n\"),s(\"code\",[t._v(\"Jsonic\")]),t._v(\" instance.\")]),t._v(\" \"),s(\"blockquote\",[s(\"ul\",[s(\"li\",[t._v(\"🍒 Only available on: \"),s(\"code\",[t._v(\"jsonic = Jsonic.make()\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍓 Method: \"),s(\"code\",[t._v(\"jsonic.token(...)\")])]),t._v(\" \"),s(\"li\",[t._v(\"🍐 Property set of token names and Tins: \"),s(\"code\",[t._v(\"jsonic.token.NR\")])])])]),t._v(\" \"),s(\"h4\",{attrs:{id:\"example-9\"}},[s(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-9\"}},[t._v(\"#\")]),t._v(\" ✨ Example\")]),t._v(\" \"),s(\"div\",{staticClass:\"language-js extra-class\"},[s(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[s(\"code\",[s(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[t._v(\"let\")]),t._v(\" jsonic \"),s(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[t._v(\"=\")]),t._v(\" Jsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"make\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\"\\njsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),t._v(\"token\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token constant\"}},[t._v(\"ST\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 11, String token identification number\")]),t._v(\"\\njsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token number\"}},[t._v(\"11\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === '#ST', String token name\")]),t._v(\"\\njsonic\"),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\".\")]),s(\"span\",{pre:!0,attrs:{class:\"token function\"}},[t._v(\"token\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\"(\")]),s(\"span\",{pre:!0,attrs:{class:\"token string\"}},[t._v(\"'#ST'\")]),s(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[t._v(\")\")]),t._v(\" \"),s(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[t._v(\"// === 11, String token name\")]),t._v(\"\\n\")])])])])}),[],!1,null,null,null);s.default=e.exports}}]);"
  },
  {
    "path": "docs/assets/js/26.c7b8345d.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[26],{209:function(t,e,s){\"use strict\";s.r(e);var r=s(6),n=Object(r.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"reference\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#reference\"}},[this._v(\"#\")]),this._v(\" Reference\")]),this._v(\" \"),t(\"p\",[this._v(\"Lorem ipsum\")])])}),[],!1,null,null,null);e.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/27.4e6f90b7.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[27],{210:function(a,t,s){\"use strict\";s.r(t);var e=s(6),r=Object(e.a)({},(function(){var a=this,t=a._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":a.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"options\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#options\"}},[a._v(\"#\")]),a._v(\" Options\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"tag\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#tag\"}},[a._v(\"#\")]),a._v(\" tag\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default \"),t(\"code\",[a._v(\"-\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Suffix to append to the \"),t(\"code\",[a._v(\"Jsonic\")]),a._v(\" instance identifier. Useful if you have\\nto debug multiple instances with different options and plugins.\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token literal-property property\"}},[a._v(\"tag\")]),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\":\")]),t(\"span\",{pre:!0,attrs:{class:\"token string\"}},[a._v(\"'foo'\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\njsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),a._v(\"id \"),t(\"span\",{pre:!0,attrs:{class:\"token comment\"}},[a._v(\"// === 'Jsonic/1614085851902/375149/foo'\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"line\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#line\"}},[a._v(\"#\")]),a._v(\" line\")]),a._v(\" \"),t(\"p\",[a._v(\"Grouping of options for the lexing of lines.\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"line-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#line-lex\"}},[a._v(\"#\")]),a._v(\" line.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-2\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-2\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"line-row\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#line-row\"}},[a._v(\"#\")]),a._v(\" line.row\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default \"),t(\"code\",[a._v(\"'\\\\n'\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-3\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-3\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"line-sep\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#line-sep\"}},[a._v(\"#\")]),a._v(\" line.sep\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")]),a._v(\" (partial RegExp)\")]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"'\\\\r*\\\\n'\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-4\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-4\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"comment\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#comment\"}},[a._v(\"#\")]),a._v(\" comment\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"comment-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#comment-lex\"}},[a._v(\"#\")]),a._v(\" comment.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-5\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-5\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"comment-balance\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#comment-balance\"}},[a._v(\"#\")]),a._v(\" comment.balance\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-6\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-6\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"comment-marker\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#comment-marker\"}},[a._v(\"#\")]),a._v(\" comment.marker\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"{[string]: string}\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default:\"),t(\"code\",[a._v(\"{ '#': true, '//': true, '/*': '*/' }\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-7\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-7\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"space\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#space\"}},[a._v(\"#\")]),a._v(\" space\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"space-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#space-lex\"}},[a._v(\"#\")]),a._v(\" space.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-8\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-8\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"number\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number\"}},[a._v(\"#\")]),a._v(\" number\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"number-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number-lex\"}},[a._v(\"#\")]),a._v(\" number.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-9\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-9\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"number-hex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number-hex\"}},[a._v(\"#\")]),a._v(\" number.hex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-10\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-10\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"number-oct\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number-oct\"}},[a._v(\"#\")]),a._v(\" number.oct\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-11\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-11\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"number-bin\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number-bin\"}},[a._v(\"#\")]),a._v(\" number.bin\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-12\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-12\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"number-digital\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number-digital\"}},[a._v(\"#\")]),a._v(\" number.digital\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"'-1023456789._xoeEaAbBcCdDfF+'\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-13\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-13\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"number-sep\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#number-sep\"}},[a._v(\"#\")]),a._v(\" number.sep\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"'_'\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-14\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-14\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"block\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#block\"}},[a._v(\"#\")]),a._v(\" block\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"block-lex-true\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#block-lex-true\"}},[a._v(\"#\")]),a._v(\" block.lex: true\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-15\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-15\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"block-marker\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#block-marker\"}},[a._v(\"#\")]),a._v(\" block.marker\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"{[string]: string}\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"{ '\\\\'\\\\'\\\\'': '\\\\'\\\\'\\\\'' }\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-16\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-16\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"string\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#string\"}},[a._v(\"#\")]),a._v(\" string\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"string-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#string-lex\"}},[a._v(\"#\")]),a._v(\" string.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-17\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-17\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"string-escape\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#string-escape\"}},[a._v(\"#\")]),a._v(\" string.escape\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"{[string]: string}\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"{ b: '\\\\b', f: '\\\\f', n: '\\\\n', r: '\\\\r', t: '\\\\t', }\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-18\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-18\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"string-multiline\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#string-multiline\"}},[a._v(\"#\")]),a._v(\" string.multiline\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"`\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-19\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-19\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"string-escapedouble\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#string-escapedouble\"}},[a._v(\"#\")]),a._v(\" string.escapedouble\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"false\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-20\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-20\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"text\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#text\"}},[a._v(\"#\")]),a._v(\" text\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"text-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#text-lex\"}},[a._v(\"#\")]),a._v(\" text.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-21\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-21\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"map\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#map\"}},[a._v(\"#\")]),a._v(\" map\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"map-extend\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#map-extend\"}},[a._v(\"#\")]),a._v(\" map.extend\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-22\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-22\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"value\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#value\"}},[a._v(\"#\")]),a._v(\" value\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"value-lex\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#value-lex\"}},[a._v(\"#\")]),a._v(\" value.lex\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-23\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-23\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"value-src\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#value-src\"}},[a._v(\"#\")]),a._v(\" value.src:\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-24\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-24\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"value-src-2\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#value-src-2\"}},[a._v(\"#\")]),a._v(\" value.src:\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"{[string]: string}\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"{ 'null': null, 'true': true, 'false': false, }\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-25\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-25\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"div\",{staticClass:\"language- extra-class\"},[t(\"pre\",[t(\"code\",[a._v(\"},\\n\")])])]),t(\"h2\",{attrs:{id:\"plugin\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#plugin\"}},[a._v(\"#\")]),a._v(\" plugin\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"debug\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#debug\"}},[a._v(\"#\")]),a._v(\" debug\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"debug-get-console\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#debug-get-console\"}},[a._v(\"#\")]),a._v(\" debug.get_console\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"() => console\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"() => console\")]),a._v(\" (System \"),t(\"code\",[a._v(\"console\")]),a._v(\")\")])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-26\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-26\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"p\",[a._v(\":\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"debug-maxlen\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#debug-maxlen\"}},[a._v(\"#\")]),a._v(\" debug.maxlen\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"number\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"33\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-27\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-27\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"debug-print\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#debug-print\"}},[a._v(\"#\")]),a._v(\" debug.print\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"debug-print-config\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#debug-print-config\"}},[a._v(\"#\")]),a._v(\" debug.print.config\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"false\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-28\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-28\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"error\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#error\"}},[a._v(\"#\")]),a._v(\" error\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"hint\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#hint\"}},[a._v(\"#\")]),a._v(\" hint\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"token\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#token\"}},[a._v(\"#\")]),a._v(\" token\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"rule\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#rule\"}},[a._v(\"#\")]),a._v(\" rule\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"rule-start\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#rule-start\"}},[a._v(\"#\")]),a._v(\" rule.start\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"string\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"'val'\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-29\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-29\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"rule-finish\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#rule-finish\"}},[a._v(\"#\")]),a._v(\" rule.finish\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"boolean\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"true\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-30\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-30\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h3\",{attrs:{id:\"rule-maxmul\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#rule-maxmul\"}},[a._v(\"#\")]),a._v(\" rule.maxmul\")]),a._v(\" \"),t(\"ul\",[t(\"li\",[a._v(\"Type: \"),t(\"code\",[a._v(\"number\")])]),a._v(\" \"),t(\"li\",[a._v(\"Default: \"),t(\"code\",[a._v(\"3\")])])]),a._v(\" \"),t(\"p\",[a._v(\"Lorem ipsum\")]),a._v(\" \"),t(\"h4\",{attrs:{id:\"example-31\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#example-31\"}},[a._v(\"#\")]),a._v(\" Example\")]),a._v(\" \"),t(\"div\",{staticClass:\"language-js extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-js\"}},[t(\"code\",[t(\"span\",{pre:!0,attrs:{class:\"token keyword\"}},[a._v(\"let\")]),a._v(\" jsonic \"),t(\"span\",{pre:!0,attrs:{class:\"token operator\"}},[a._v(\"=\")]),a._v(\" Jsonic\"),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\".\")]),t(\"span\",{pre:!0,attrs:{class:\"token function\"}},[a._v(\"make\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"(\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"{\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\"}\")]),t(\"span\",{pre:!0,attrs:{class:\"token punctuation\"}},[a._v(\")\")]),a._v(\"\\n\")])])]),t(\"h2\",{attrs:{id:\"config\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#config\"}},[a._v(\"#\")]),a._v(\" config\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"config-modify\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#config-modify\"}},[a._v(\"#\")]),a._v(\" config.modify\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"parser\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#parser\"}},[a._v(\"#\")]),a._v(\" parser\")]),a._v(\" \"),t(\"h3\",{attrs:{id:\"parser-start\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#parser-start\"}},[a._v(\"#\")]),a._v(\" parser.start\")])])}),[],!1,null,null,null);t.default=r.exports}}]);"
  },
  {
    "path": "docs/assets/js/28.6e0fa7bc.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[28],{214:function(a,t,n){\"use strict\";n.r(t);var s=n(6),e=Object(s.a)({},(function(){var a=this,t=a._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":a.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"syntax\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#syntax\"}},[a._v(\"#\")]),a._v(\" Syntax\")]),a._v(\" \"),t(\"h2\",{attrs:{id:\"cheatsheet\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#cheatsheet\"}},[a._v(\"#\")]),a._v(\" Cheatsheet\")]),a._v(\" \"),t(\"p\",[a._v(\"This document shows the features of the \"),t(\"name-self\"),a._v(\" syntax:\")],1),a._v(\" \"),t(\"div\",{staticClass:\"language-jsonic extra-class\"},[t(\"pre\",{pre:!0,attrs:{class:\"language-text\"}},[t(\"code\",[a._v('// jsonic                              // JSON\\n\\n  // cheat...       # comments\\n  /*\\n   * ...sheet!\\n   */\\n                    # implicit top level    { \\n  a:                # implicit null           \"a\": null,\\n\\n  b: 1              # optional comma           \"b\": 1,\\n  c: 1\\n  c: 2              # last duplicate wins      \"c\": 2,\\n  \\n  d: f: 3           # key chains               \"d\": {\\n  d: g: h: 4        # object merging           \"f\": 3,\\n                                                 \"g\": {\\n                                                   \"h\": 4\\n                                                 }   \\n                                               }\\n                                      \\nTODO\\n                                             }\\n')])])]),t(\"h2\",{attrs:{id:\"railroad-diagrams\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#railroad-diagrams\"}},[a._v(\"#\")]),a._v(\" Railroad Diagrams\")]),a._v(\" \"),t(\"jsonic-railroad\")],1)}),[],!1,null,null,null);t.default=e.exports}}]);"
  },
  {
    "path": "docs/assets/js/29.77b8e3b6.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[29],{211:function(t,r,a){\"use strict\";a.r(r);var s=a(6),i=Object(s.a)({},(function(){var t=this,r=t._self._c;return r(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[r(\"h1\",{attrs:{id:\"tutorials\"}},[r(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#tutorials\"}},[t._v(\"#\")]),t._v(\" Tutorials\")]),t._v(\" \"),r(\"ul\",[r(\"li\",[r(\"a\",{attrs:{href:\"/tutorial/write-a-plugin\"}},[t._v(\"Writing a plugin\")]),t._v(\" \"),r(\"ul\",[r(\"li\",[t._v(\"Short desc\")])])]),t._v(\" \"),r(\"li\",[r(\"a\",{attrs:{href:\"/tutorial/parsing-csv\"}},[t._v(\"Parsing CSV\")]),t._v(\" \"),r(\"ul\",[r(\"li\",[t._v(\"Short desc\")])])])])])}),[],!1,null,null,null);r.default=i.exports}}]);"
  },
  {
    "path": "docs/assets/js/3.6ce1235a.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{164:function(n,s,t){},184:function(n,s,t){\"use strict\";t(164)},217:function(n,s,t){\"use strict\";t.r(s);t(184);var c=t(6),i=Object(c.a)({},(function(){return(0,this._self._c)(\"span\",{staticClass:\"jsn-name-self\"},[this._v(\"Jsonic\")])}),[],!1,null,null,null);s.default=i.exports}}]);"
  },
  {
    "path": "docs/assets/js/30.69b1b865.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[30],{213:function(t,s,n){\"use strict\";n.r(s);var r=n(6),a=Object(r.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"parsing-csv\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#parsing-csv\"}},[this._v(\"#\")]),this._v(\" Parsing CSV\")])])}),[],!1,null,null,null);s.default=a.exports}}]);"
  },
  {
    "path": "docs/assets/js/31.c68f976d.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[31],{215:function(t,n,s){\"use strict\";s.r(n);var e=s(6),o=Object(e.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"p\",[this._v(\"@jsonic/expr\")])])}),[],!1,null,null,null);n.default=o.exports}}]);"
  },
  {
    "path": "docs/assets/js/32.0a08a140.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[32],{212:function(t,s,i){\"use strict\";i.r(s);var n=i(6),r=Object(n.a)({},(function(){var t=this._self._c;return t(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":this.$parent.slotKey}},[t(\"h1\",{attrs:{id:\"write-a-plugin\"}},[t(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#write-a-plugin\"}},[this._v(\"#\")]),this._v(\" Write a plugin\")])])}),[],!1,null,null,null);s.default=r.exports}}]);"
  },
  {
    "path": "docs/assets/js/4.064b2ac3.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{165:function(t,e,n){},185:function(t,e,n){\"use strict\";n(165)},218:function(t,e,n){\"use strict\";n.r(e);var i={functional:!0,props:{type:{type:String,default:\"tip\"},text:String,vertical:{type:String,default:\"top\"}},render:(t,{props:e,slots:n})=>t(\"span\",{class:[\"badge\",e.type],style:{verticalAlign:e.vertical}},e.text||n().default)},p=(n(185),n(6)),l=Object(p.a)(i,void 0,void 0,!1,null,\"15b7b770\",null);e.default=l.exports}}]);"
  },
  {
    "path": "docs/assets/js/5.46815478.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{166:function(t,e,a){},186:function(t,e,a){\"use strict\";a(166)},190:function(t,e,a){\"use strict\";a.r(e);var s={name:\"CodeBlock\",props:{title:{type:String,required:!0},active:{type:Boolean,default:!1}},mounted(){this.$parent&&this.$parent.loadTabs&&this.$parent.loadTabs()}},i=(a(186),a(6)),n=Object(i.a)(s,(function(){return(0,this._self._c)(\"div\",{staticClass:\"theme-code-block\",class:{\"theme-code-block__active\":this.active}},[this._t(\"default\")],2)}),[],!1,null,\"759a7d02\",null);e.default=n.exports}}]);"
  },
  {
    "path": "docs/assets/js/6.f03d59ff.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{167:function(e,t,a){},187:function(e,t,a){\"use strict\";a(167)},191:function(e,t,a){\"use strict\";a.r(t);var o={name:\"CodeGroup\",data:()=>({codeTabs:[],activeCodeTabIndex:-1}),watch:{activeCodeTabIndex(e){this.activateCodeTab(e)}},mounted(){this.loadTabs()},methods:{changeCodeTab(e){this.activeCodeTabIndex=e},loadTabs(){this.codeTabs=(this.$slots.default||[]).filter(e=>Boolean(e.componentOptions)).map((e,t)=>(\"\"===e.componentOptions.propsData.active&&(this.activeCodeTabIndex=t),{title:e.componentOptions.propsData.title,elm:e.elm})),-1===this.activeCodeTabIndex&&this.codeTabs.length>0&&(this.activeCodeTabIndex=0),this.activateCodeTab(0)},activateCodeTab(e){this.codeTabs.forEach(e=>{e.elm&&e.elm.classList.remove(\"theme-code-block__active\")}),this.codeTabs[e].elm&&this.codeTabs[e].elm.classList.add(\"theme-code-block__active\")}}},s=(a(187),a(6)),c=Object(s.a)(o,(function(){var e=this,t=e._self._c;return t(\"ClientOnly\",[t(\"div\",{staticClass:\"theme-code-group\"},[t(\"div\",{staticClass:\"theme-code-group__nav\"},[t(\"ul\",{staticClass:\"theme-code-group__ul\"},e._l(e.codeTabs,(function(a,o){return t(\"li\",{key:a.title,staticClass:\"theme-code-group__li\"},[t(\"button\",{staticClass:\"theme-code-group__nav-tab\",class:{\"theme-code-group__nav-tab-active\":o===e.activeCodeTabIndex},on:{click:function(t){return e.changeCodeTab(o)}}},[e._v(\"\\n            \"+e._s(a.title)+\"\\n          \")])])})),0)]),e._v(\" \"),e._t(\"default\"),e._v(\" \"),e.codeTabs.length<1?t(\"pre\",{staticClass:\"pre-blank\"},[e._v(\"// Make sure to add code blocks to your code group\")]):e._e()],2)])}),[],!1,null,\"deefee04\",null);t.default=c.exports}}]);"
  },
  {
    "path": "docs/assets/js/7.6be3acca.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[7],{216:function(t,a,h){\"use strict\";h.r(a);var s=h(6),r=Object(s.a)({},(function(){var t=this,a=t._self._c;return a(\"div\",[a(\"div\",[a(\"h3\",[t._v(\"jsonic\")]),t._v(\" \"),t._m(0),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"233\",height:\"152\",viewBox:\"0 0 233 152\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 21v20m0 -10h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M40 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M193 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M60 31h33.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M139.25 31h33.75\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"93.75\",y:\"20\",width:\"45.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"116.5\",y:\"35\"}},[t._v(\"map\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M173 31h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M60 61h29.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M143.5 61h29.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"89.5\",y:\"50\",width:\"54\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"116.5\",y:\"65\"}},[t._v(\"list\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M173 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M60 91h25.25\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M147.75 91h25.25\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"85.25\",y:\"80\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"116.5\",y:\"95\"}},[t._v(\"value\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M173 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M173 121h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M60 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M80 121h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"60\",y:\"110\",width:\"20\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"70\",y:\"125\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M80 121h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M90 121h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M100 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M173 121h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"136.5\",y:\"126\"}},[t._v(\"undefined\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M173 121a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M 193 31 h 20 m 0 -10 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"map\")]),t._v(\" \"),t._m(1),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"559\",height:\"99\",viewBox:\"0 0 559 99\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 30v20m0 -10h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M509 40h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M78.5 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"50\",y:\"29\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"64.25\",y:\"44\"}},[t._v(\"{\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M78.5 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M88.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M470.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M88.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M108.5 20h342\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M450.5 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M88.5 40h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M108.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M450.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M108.5 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M118.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M440.5 40h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M118.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M345 40h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M118.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M232.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M118.5 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M128.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M222.5 40h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M128.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M174 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"128.5\",y:\"29\",width:\"45.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"151.25\",y:\"44\"}},[t._v(\"key\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M174 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M184 40h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M194 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M222.5 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"194\",y:\"29\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208.25\",y:\"44\"}},[t._v(\":\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M222.5 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M128.5 40a10 10 0 0 0 -10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M128.5 60h94\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M222.5 60a10 10 0 0 0 10 -10v0a10 10 0 0 0 -10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M232.5 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M242.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M345 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M242.5 40h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M262.5 40h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M325 40h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M242.5 40a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M262.5 60h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M325 60h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"262.5\",y:\"49\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"293.75\",y:\"64\"}},[t._v(\"value\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M325 60a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M345 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M355 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M440.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M355 40h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M375 40h45.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M420.5 40h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M355 40a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M375 60h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M420.5 60h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"375\",y:\"49\",width:\"45.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"397.75\",y:\"64\"}},[t._v(\"sep\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M420.5 60a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M440.5 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M118.5 40a10 10 0 0 0 -10 10v19a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M118.5 79h322\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M440.5 79a10 10 0 0 0 10 -10v-19a10 10 0 0 0 -10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M450.5 40h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M470.5 40h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M480.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M509 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"480.5\",y:\"29\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"494.75\",y:\"44\"}},[t._v(\"}\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M509 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M 519 40 h 20 m 0 -10 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"list\")]),t._v(\" \"),t._m(2),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"425\",height:\"108\",viewBox:\"0 0 425 108\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 30v20m0 -10h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M375 40h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M78.5 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"50\",y:\"29\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"64.25\",y:\"44\"}},[t._v(\"[\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M78.5 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M88.5 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M98.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M326.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M98.5 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M108.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M316.5 40h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M108.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M211 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M108.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M128.5 20h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M191 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M108.5 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M128.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M191 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"128.5\",y:\"29\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"159.75\",y:\"44\"}},[t._v(\"value\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M191 40h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M211 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M316.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M211 40h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M231 40h65.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M296.5 40h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M211 40a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M231 60h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M296.5 60h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M231 60h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M241 60h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M286.5 60h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"241\",y:\"49\",width:\"45.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"263.75\",y:\"64\"}},[t._v(\"sep\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M286.5 60h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M241 60a10 10 0 0 0 -10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M241 80h45.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M286.5 80a10 10 0 0 0 10 -10v0a10 10 0 0 0 -10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M296.5 60a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M316.5 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M108.5 40a10 10 0 0 0 -10 10v28a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M108.5 88h208\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M316.5 88a10 10 0 0 0 10 -10v-28a10 10 0 0 0 -10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M326.5 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M336.5 40h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M346.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M375 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"346.5\",y:\"29\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"360.75\",y:\"44\"}},[t._v(\"]\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M375 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M 385 40 h 20 m 0 -10 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"value\")]),t._v(\" \"),t._m(3),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"416\",height:\"251\",viewBox:\"0 0 416 251\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 30v20m0 -10h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M366 40h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M50 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M70 20h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M132.5 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M50 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M70 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M132.5 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"70\",y:\"29\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"101.25\",y:\"44\"}},[t._v(\"space\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M132.5 40h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M152.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M172.5 40h12.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M230.75 40h12.75\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"185.25\",y:\"29\",width:\"45.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"44\"}},[t._v(\"map\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 40h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M172.5 70h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M235 70h8.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"181\",y:\"59\",width:\"54\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"74\"}},[t._v(\"list\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 70a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M172.5 100h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 100h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"172.5\",y:\"89\",width:\"71\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"104\"}},[t._v(\"string\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 100a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M172.5 130h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 130h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"172.5\",y:\"119\",width:\"71\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"134\"}},[t._v(\"number\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 130a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v100a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M172.5 160h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M235 160h8.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"181\",y:\"149\",width:\"54\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"164\"}},[t._v(\"true\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 160a10 10 0 0 0 10 -10v-100a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v130a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M172.5 190h4.25\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M239.25 190h4.25\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"176.75\",y:\"179\",width:\"62.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"194\"}},[t._v(\"false\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 190a10 10 0 0 0 10 -10v-130a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v160a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M172.5 220h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M235 220h8.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"181\",y:\"209\",width:\"54\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"224\"}},[t._v(\"null\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 220a10 10 0 0 0 10 -10v-160a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M263.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M366 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M283.5 20h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M346 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263.5 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M283.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M346 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"283.5\",y:\"29\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"314.75\",y:\"44\"}},[t._v(\"space\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M346 40h20\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M366 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M 376 40 h 20 m 0 -10 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"key\")]),t._v(\" \"),a(\"p\",[t._v(\"Keys are verbatim source characters including unquoted text, number and literal value representations, but excluding punctuation ({}[]...).\")]),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"416\",height:\"191\",viewBox:\"0 0 416 191\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 30v20m0 -10h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 40h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M366 40h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M50 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M50 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M70 20h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M132.5 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M50 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M70 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M132.5 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"70\",y:\"29\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"101.25\",y:\"44\"}},[t._v(\"space\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M132.5 40h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M152.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M172.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"172.5\",y:\"29\",width:\"71\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"44\"}},[t._v(\"string\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 40h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M172.5 70h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 70h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"172.5\",y:\"59\",width:\"71\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"74\"}},[t._v(\"number\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 70a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M172.5 100h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M235 100h8.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"181\",y:\"89\",width:\"54\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"104\"}},[t._v(\"true\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 100a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M172.5 130h4.25\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M239.25 130h4.25\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"176.75\",y:\"119\",width:\"62.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"134\"}},[t._v(\"false\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 130a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 40a10 10 0 0 1 10 10v100a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M172.5 160h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M235 160h8.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"181\",y:\"149\",width:\"54\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"208\",y:\"164\"}},[t._v(\"null\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M243.5 160a10 10 0 0 0 10 -10v-100a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M263.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M366 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M283.5 20h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M346 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263.5 40h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M283.5 40h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M346 40h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"283.5\",y:\"29\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"314.75\",y:\"44\"}},[t._v(\"space\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M346 40h20\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M366 40h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M 376 40 h 20 m 0 -10 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"sep\")]),t._v(\" \"),a(\"p\",[t._v(\" Commas, spaces, new lines, and nothing, all separate values equivalently. \")]),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"182.5\",height:\"111\",viewBox:\"0 0 182.5 111\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 20v20m10 -20v20m-10 -10h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M40 30h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M142.5 30h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 30h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 30h62.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M122.5 30h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 30a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M60 50h17\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M105.5 50h17\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"77\",y:\"39\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"91.25\",y:\"54\"}},[t._v(\",\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M122.5 50a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 30a10 10 0 0 1 10 10v30a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M60 80h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M122.5 80h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"60\",y:\"69\",width:\"62.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"91.25\",y:\"84\"}},[t._v(\"space\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M122.5 80a10 10 0 0 0 10 -10v-30a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M 142.5 30 h 20 m -10 -10 v 20 m 10 -20 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"space\")]),t._v(\" \"),t._m(4),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"377.5\",height:\"190\",viewBox:\"0 0 377.5 190\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 21v20m10 -20v20m-10 -10h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M40 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M337.5 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M317.5 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M60 31h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M70 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M307.5 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M70 31h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M90 31h30.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M256.75 31h30.75\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M120.75 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M191.75 31h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"120.75\",y:\"20\",width:\"71\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"156.25\",y:\"35\"}},[t._v(\"«0x20»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M191.75 31h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M201.75 31h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M211.75 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M256.75 31h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"234.25\",y:\"36\"}},[t._v(\"space\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 31h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M70 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M90 61h24.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263 61h24.5\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M114.5 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M177 61h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"114.5\",y:\"50\",width:\"62.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"145.75\",y:\"65\"}},[t._v(\"«0xA»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M177 61h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M187 61h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M197 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M263 61h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"230\",y:\"66\"}},[t._v(\"linefeed\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M70 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M90 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 91h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M90 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 91h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"90\",y:\"80\",width:\"62.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"121.25\",y:\"95\"}},[t._v(\"«0xD»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M152.5 91h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M162.5 91h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M172.5 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 91h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"230\",y:\"96\"}},[t._v(\"carriage return\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M70 31a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M90 121h3.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M284 121h3.5\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M93.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M156 121h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"93.5\",y:\"110\",width:\"62.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"124.75\",y:\"125\"}},[t._v(\"«0x9»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M156 121h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M166 121h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M176 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M284 121h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"230\",y:\"126\"}},[t._v(\"horizontal tab\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 121a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M70 31a10 10 0 0 1 10 10v100a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M90 151h59\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M228.5 151h59\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"149\",y:\"140\",width:\"79.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"188.75\",y:\"155\"}},[t._v(\"comment\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M287.5 151a10 10 0 0 0 10 -10v-100a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M307.5 31h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M70 31a10 10 0 0 0 -10 10v119a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M70 170h237.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M307.5 170a10 10 0 0 0 10 -10v-119a10 10 0 0 0 -10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M317.5 31h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M 337.5 31 h 20 m -10 -10 v 20 m 10 -20 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"string\")]),t._v(\" \"),a(\"p\",[t._v(\"Quoted strings (using «\\\"'`») work as in JavaScript, \\nincluding escaping. Triple single quotes operate as backticks, but\\nremove the indent from each line.\")]),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"630.5\",height:\"242\",viewBox:\"0 0 630.5 242\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 21v20m10 -20v20m-10 -10h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M40 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M590.5 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 31h27.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M543 31h27.5\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M87.5 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M422 31h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"87.5\",y:\"20\",width:\"334.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"254.75\",y:\"35\"}},[t._v(\"all-chars ∖ «\\\\t \\\\r\\\\n\\\\b\\\\f\\\\'\\\"`{}[]:,/#»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M422 31h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M432 31h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M442 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M543 31h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"492.5\",y:\"36\"}},[t._v(\"unquoted text\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 31h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 61h86\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M484.5 61h86\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M146 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M214.5 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M146 61h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M166 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M194.5 61h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"166\",y:\"50\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"180.25\",y:\"65\"}},[t._v('\"')])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M194.5 61h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M214.5 61h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M224.5 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M406 61h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"224.5\",y:\"50\",width:\"181.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"315.25\",y:\"65\"}},[t._v(\"escaped-chars ∪ «'»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M406 61h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M416 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M484.5 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M416 61h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M436 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M464.5 61h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"436\",y:\"50\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"450.25\",y:\"65\"}},[t._v('\"')])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M464.5 61h20\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 91h86\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M484.5 91h86\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M146 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M214.5 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M146 91h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M166 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M194.5 91h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"166\",y:\"80\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"180.25\",y:\"95\"}},[t._v(\"'\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M194.5 91h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M214.5 91h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M224.5 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M406 91h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"224.5\",y:\"80\",width:\"181.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"315.25\",y:\"95\"}},[t._v('escaped-chars ∪ «\"»')])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M406 91h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M416 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M484.5 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M416 91h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M436 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M464.5 91h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"436\",y:\"80\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"450.25\",y:\"95\"}},[t._v(\"'\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M464.5 91h20\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 121h93\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M477.5 121h93\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M153 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M221.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M153 121h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M173 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M201.5 121h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"173\",y:\"110\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"187.25\",y:\"125\"}},[t._v(\"`\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M201.5 121h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M221.5 121h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M231.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M399 121h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M231.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M399 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M231.5 121h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M241.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M389 121h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M241.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M389 121h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"241.5\",y:\"110\",width:\"147.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"315.25\",y:\"125\"}},[t._v(\"all-chars ∖ «`»\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M389 121h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M241.5 121a10 10 0 0 0 -10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M241.5 151h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M380.5 151h8.5\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"250\",y:\"140\",width:\"130.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"315.25\",y:\"155\"}},[t._v(\"escaped-chars\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M389 151a10 10 0 0 0 10 -10v-10a10 10 0 0 0 -10 -10\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M399 121h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M409 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M477.5 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M409 121h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M429 121h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M457.5 121h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"429\",y:\"110\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"443.25\",y:\"125\"}},[t._v(\"`\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M457.5 121h20\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 121a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v130a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 181h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M145.5 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M60 181h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M80 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M125.5 181h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"80\",y:\"170\",width:\"45.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"102.75\",y:\"185\"}},[t._v(\"'''\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M125.5 181h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M145.5 181h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M155.5 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M475 181h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M155.5 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M340 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M155.5 181h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M165.5 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M330 181h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M165.5 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M330 181h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"165.5\",y:\"170\",width:\"164.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"247.75\",y:\"185\"}},[t._v(\"all-chars ∖ \\\"'''\\\"\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M330 181h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M165.5 181a10 10 0 0 0 -10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M165.5 211h17\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M313 211h17\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"182.5\",y:\"200\",width:\"130.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"247.75\",y:\"215\"}},[t._v(\"escaped-chars\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M330 211a10 10 0 0 0 10 -10v-10a10 10 0 0 0 -10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M340 181h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M350 181h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"comment\"},[a(\"path\",{attrs:{d:\"M360 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M475 181h0\"}}),t._v(\" \"),a(\"text\",{staticClass:\"comment\",attrs:{x:\"417.5\",y:\"186\"}},[t._v(\"indents removed\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M475 181h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M485 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M485 181h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M505 181h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M550.5 181h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"505\",y:\"170\",width:\"45.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"527.75\",y:\"185\"}},[t._v(\"'''\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M550.5 181h20\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M570.5 181a10 10 0 0 0 10 -10v-130a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M 590.5 31 h 20 m -10 -10 v 20 m 10 -20 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"number\")]),t._v(\" \"),a(\"p\",[t._v(\"Number literals work as in JavaScript. \\nUnderscore can be used to separate digits.\")]),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"857\",height:\"311\",viewBox:\"0 0 857 311\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 38v20m10 -20v20m-10 -10h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M40 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M817 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 48a10 10 0 0 0 10 -10v-8a10 10 0 0 1 10 -10h398.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M118.5 291h678.5a10 10 0 0 0 10 -10v-223a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 48h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M50 48h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M88.5 48h10\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"60\",y:\"37\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"74.25\",y:\"52\"}},[t._v(\"0\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M98.5 48a10 10 0 0 1 10 10v223a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M98.5 20a10 10 0 0 1 10 10v8a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M118.5 48h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M128.5 48h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M138.5 48h95.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M342.75 48h95.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M234.25 48a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M254.25 28h68.5\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M322.75 28a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M234.25 48h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M254.25 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M322.75 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M254.25 48h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M274.25 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M302.75 48h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"274.25\",y:\"37\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"288.5\",y:\"52\"}},[t._v(\"-\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M302.75 48h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M254.25 48a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M274.25 78h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M302.75 78h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"274.25\",y:\"67\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"288.5\",y:\"82\"}},[t._v(\"+\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M302.75 78a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M322.75 48h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M438.5 48a10 10 0 0 1 10 10v29a10 10 0 0 1 -10 10h-300a10 10 0 0 0 -10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M138.5 117h18\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M420.5 117h18\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M156.5 117h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M176.5 117h97.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M302.75 117h97.75\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"274.25\",y:\"106\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"288.5\",y:\"121\"}},[t._v(\"0\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M400.5 117h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M156.5 117a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M176.5 147h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M400.5 147h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"176.5\",y:\"136\",width:\"224\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"288.5\",y:\"151\"}},[t._v(\"digits [1-9][0-9]* ∪ «_»\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M400.5 147a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M438.5 117a10 10 0 0 1 10 10v29a10 10 0 0 1 -10 10h-300a10 10 0 0 0 -10 10v8a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M138.5 194h15\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M423.5 194h15\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M153.5 194a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M173.5 174h230\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M403.5 174a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M153.5 194h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M173.5 194h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M403.5 194h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M173.5 194h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M202 194h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"173.5\",y:\"183\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"187.75\",y:\"198\"}},[t._v(\".\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M202 194h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M212 194h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M222 194h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M403.5 194h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"222\",y:\"183\",width:\"181.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"312.75\",y:\"198\"}},[t._v(\"digits [0-9]* ∪ «_»\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M403.5 194h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M438.5 194a10 10 0 0 1 10 10v0a10 10 0 0 1 -10 10h-300a10 10 0 0 0 -10 10v8a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M138.5 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M438.5 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M138.5 242a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M158.5 222h260\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M418.5 222a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M138.5 242h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M158.5 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M418.5 242h0\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M158.5 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M227 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M158.5 242h20\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M178.5 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M207 242h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"178.5\",y:\"231\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"192.75\",y:\"246\"}},[t._v(\"e\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M207 242h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M158.5 242a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M178.5 272h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M207 272h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"178.5\",y:\"261\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"192.75\",y:\"276\"}},[t._v(\"E\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M207 272a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M227 242h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M237 242h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M418.5 242h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"237\",y:\"231\",width:\"181.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"327.75\",y:\"246\"}},[t._v(\"digits [0-9]* ∪ «_»\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M418.5 242h20\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M438.5 242h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M448.5 242h10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M458.5 242a10 10 0 0 1 10 10v29a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M458.5 20a10 10 0 0 1 10 10v8a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M478.5 48h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M797 48h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M488.5 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M517 48h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"488.5\",y:\"37\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"502.75\",y:\"52\"}},[t._v(\"0\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M517 48h10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M527 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M797 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M527 48h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M547 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M777 48h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M547 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M575.5 48h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"547\",y:\"37\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"561.25\",y:\"52\"}},[t._v(\"x\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M575.5 48h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M585.5 48h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M595.5 48h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M777 48h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"595.5\",y:\"37\",width:\"181.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"686.25\",y:\"52\"}},[t._v(\"digits a-fA-F ∪ «_»\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M777 48h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M527 48a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M547 78h12.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M764.25 78h12.75\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M559.75 78h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M588.25 78h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"559.75\",y:\"67\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"574\",y:\"82\"}},[t._v(\"o\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M588.25 78h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M598.25 78h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M608.25 78h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M764.25 78h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"608.25\",y:\"67\",width:\"156\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"686.25\",y:\"82\"}},[t._v(\"digits 0-7 ∪ «_»\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M777 78a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M527 48a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M547 108h12.75\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M764.25 108h12.75\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M559.75 108h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M588.25 108h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"559.75\",y:\"97\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"574\",y:\"112\"}},[t._v(\"b\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M588.25 108h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M598.25 108h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M608.25 108h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M764.25 108h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"608.25\",y:\"97\",width:\"156\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"686.25\",y:\"112\"}},[t._v(\"digits 0-1 ∪ «_»\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M777 108a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}})])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M807 48h10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M 817 48 h 20 m -10 -10 v 20 m 10 -20 v 20\"}})])])]),a(\"div\",[a(\"h3\",[t._v(\"comment\")]),t._v(\" \"),a(\"p\",[t._v(\"Comments work as in JavaScript. \\nSingle line comments can also be introduced with «#». Balanced comments can nest.\")]),t._v(\" \"),a(\"svg\",{staticClass:\"railroad-diagram\",attrs:{width:\"426.5\",height:\"122\",viewBox:\"0 0 426.5 122\"}},[a(\"g\",{attrs:{transform:\"translate(.5 .5)\"}},[a(\"g\",[a(\"path\",{attrs:{d:\"M20 21v20m10 -20v20m-10 -10h20\"}})]),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M40 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M386.5 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31h20\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 31h18.25\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M348.25 31h18.25\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M78.25 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M115.25 31h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"78.25\",y:\"20\",width:\"37\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"96.75\",y:\"35\"}},[t._v(\"/*\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M115.25 31h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M125.25 31h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M135.25 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M291.25 31h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"135.25\",y:\"20\",width:\"156\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"213.25\",y:\"35\"}},[t._v('all-chars ∖ \"*/\"')])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M291.25 31h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M301.25 31h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M311.25 31h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M348.25 31h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"311.25\",y:\"20\",width:\"37\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"329.75\",y:\"35\"}},[t._v(\"*/\")])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M366.5 31h20\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M366.5 61h0\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M60 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M97 61h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"60\",y:\"50\",width:\"37\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"78.5\",y:\"65\"}},[t._v(\"//\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M97 61h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M107 61h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M117 61h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M366.5 61h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"117\",y:\"50\",width:\"249.5\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"241.75\",y:\"65\"}},[t._v('all-chars ∖ (\"//\" ∪ «\\\\r\\\\n»)')])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M366.5 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M40 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"}}),t._v(\" \"),a(\"g\",[a(\"path\",{attrs:{d:\"M60 91h8.5\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M358 91h8.5\"}}),t._v(\" \"),a(\"g\",{staticClass:\"terminal\"},[a(\"path\",{attrs:{d:\"M68.5 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M97 91h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"68.5\",y:\"80\",width:\"28.5\",height:\"22\",rx:\"10\",ry:\"10\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"82.75\",y:\"95\"}},[t._v(\"#\")])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M97 91h10\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M107 91h10\"}}),t._v(\" \"),a(\"g\",{staticClass:\"non-terminal\"},[a(\"path\",{attrs:{d:\"M117 91h0\"}}),t._v(\" \"),a(\"path\",{attrs:{d:\"M358 91h0\"}}),t._v(\" \"),a(\"rect\",{attrs:{x:\"117\",y:\"80\",width:\"241\",height:\"22\"}}),t._v(\" \"),a(\"text\",{attrs:{x:\"237.5\",y:\"95\"}},[t._v('all-chars ∖ (\"#\" ∪ «\\\\r\\\\n»)')])])]),t._v(\" \"),a(\"path\",{attrs:{d:\"M366.5 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"}})]),t._v(\" \"),a(\"path\",{attrs:{d:\"M 386.5 31 h 20 m -10 -10 v 20 m 10 -20 v 20\"}})])])])])}),[function(){var t=this,a=t._self._c;return a(\"p\",[t._v(\" After parsing, either an object (\"),a(\"i\",[t._v(\"map\")]),t._v(\"), an array\\n(\"),a(\"i\",[t._v(\"list\")]),t._v(\"), or a scalar value is returned. An empty document is\\nconsidered valid and returns \"),a(\"code\",[t._v(\"undefined\")]),t._v(\". While \"),a(\"i\",[t._v(\"map\")]),t._v(\"\\nand \"),a(\"i\",[t._v(\"list\")]),t._v(\" are strictly redundant here as they can be children of\\n\"),a(\"i\",[t._v(\"value\")]),t._v(\", implicit maps (no surrounding braces \"),a(\"code\",[t._v(\"{}\")]),t._v(\")\\nand implicit lists (no surrounding square brackets \"),a(\"code\",[t._v(\"[]\")]),t._v(\")\\nare special cases at the top level.\")])},function(){var t=this._self._c;return t(\"p\",[this._v(\"One or more keys may preceed a value, creating child maps as\\nneeded. Missing values are \"),t(\"code\",[this._v(\"null\")]),this._v(\". Spurious commas are not\\nallowed. Key-value pairs can be separated by \"),t(\"i\",[this._v(\"space\")]),this._v(\" and new lines.\")])},function(){var t=this._self._c;return t(\"p\",[this._v(\" List \"),t(\"i\",[this._v(\"values\")]),this._v(\" are separated by \"),t(\"i\",[this._v(\"space\")]),this._v(\", commas and new\\nlines.  A trailing comma is ignored. A comma without a preceding value\\ninserts an implicit \"),t(\"code\",[this._v(\"null\")]),this._v(\".  \")])},function(){var t=this,a=t._self._c;return a(\"p\",[t._v(\" A \"),a(\"i\",[t._v(\"value\")]),t._v(\" can be surrounded by optional \"),a(\"i\",[t._v(\"space\")]),t._v(\" (To\\nmatch \"),a(\"a\",{attrs:{href:\"http://json.org\"}},[t._v(\"json.org\")]),t._v(\", our non-empty\\n\"),a(\"i\",[t._v(\"space\")]),t._v(\" must be optional).  \")])},function(){var t=this._self._c;return t(\"p\",[this._v(\" There must be at least one space character in the space token\\n(unlike \"),t(\"a\",{attrs:{href:\"http://json.org\"}},[this._v(\"json.org\")]),this._v(\"—thus we make\\n\"),t(\"i\",[this._v(\"space\")]),this._v(\" optional elsewhere as needed). Space can contain one or comments.\")])}],!1,null,null,null);a.default=r.exports}}]);"
  },
  {
    "path": "docs/assets/js/8.0a081a71.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[8],{189:function(t,e,s){\"use strict\";s.r(e);const o=[\"There's nothing here.\",\"How did we get here?\",\"That's a Four-Oh-Four.\",\"Looks like we've got some broken links.\"];var n={methods:{getMsg:()=>o[Math.floor(Math.random()*o.length)]}},h=s(6),i=Object(h.a)(n,(function(){var t=this._self._c;return t(\"div\",{staticClass:\"theme-container\"},[t(\"div\",{staticClass:\"theme-default-content\"},[t(\"h1\",[this._v(\"404\")]),this._v(\" \"),t(\"blockquote\",[this._v(this._s(this.getMsg()))]),this._v(\" \"),t(\"RouterLink\",{attrs:{to:\"/\"}},[this._v(\"\\n      Take me home.\\n    \")])],1)])}),[],!1,null,null,null);e.default=i.exports}}]);"
  },
  {
    "path": "docs/assets/js/9.9b6e31d5.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[9],{192:function(t,a,r){\"use strict\";r.r(a);var s=r(6),e=Object(s.a)({},(function(){var t=this,a=t._self._c;return a(\"ContentSlotsDistributor\",{attrs:{\"slot-key\":t.$parent.slotKey}},[a(\"h1\",{attrs:{id:\"alternatives\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#alternatives\"}},[t._v(\"#\")]),t._v(\" Alternatives\")]),t._v(\" \"),a(\"h2\",{attrs:{id:\"foo\"}},[a(\"a\",{staticClass:\"header-anchor\",attrs:{href:\"#foo\"}},[t._v(\"#\")]),t._v(\" Foo\")]),t._v(\" \"),a(\"p\",[t._v(\"Short desc.\")]),t._v(\" \"),a(\"p\",[t._v(\"Start the \"),a(\"a\",{attrs:{href:\"foo\"}},[t._v(\"foo\")]),t._v(\" link.\")])])}),[],!1,null,null,null);a.default=e.exports}}]);"
  },
  {
    "path": "docs/assets/js/app.0c621e62.js",
    "content": "(window.webpackJsonp=window.webpackJsonp||[]).push([[0],[]]);!function(t){function e(e){for(var r,a,s=e[0],u=e[1],l=e[2],f=0,p=[];f<s.length;f++)a=s[f],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&p.push(o[a][0]),o[a]=0;for(r in u)Object.prototype.hasOwnProperty.call(u,r)&&(t[r]=u[r]);for(c&&c(e);p.length;)p.shift()();return i.push.apply(i,l||[]),n()}function n(){for(var t,e=0;e<i.length;e++){for(var n=i[e],r=!0,s=1;s<n.length;s++){var u=n[s];0!==o[u]&&(r=!1)}r&&(i.splice(e--,1),t=a(a.s=n[0]))}return t}var r={},o={1:0},i=[];function a(e){if(r[e])return r[e].exports;var n=r[e]={i:e,l:!1,exports:{}};return t[e].call(n.exports,n,n.exports,a),n.l=!0,n.exports}a.e=function(t){var e=[],n=o[t];if(0!==n)if(n)e.push(n[2]);else{var r=new Promise((function(e,r){n=o[t]=[e,r]}));e.push(n[2]=r);var i,s=document.createElement(\"script\");s.charset=\"utf-8\",s.timeout=120,a.nc&&s.setAttribute(\"nonce\",a.nc),s.src=function(t){return a.p+\"assets/js/\"+({}[t]||t)+\".\"+{2:\"34930047\",3:\"6ce1235a\",4:\"064b2ac3\",5:\"46815478\",6:\"f03d59ff\",7:\"6be3acca\",8:\"0a081a71\",9:\"9b6e31d5\",10:\"88d9a57b\",11:\"7988a5fa\",12:\"d9f3d941\",13:\"775f91ca\",14:\"f56c2700\",15:\"00058088\",16:\"70a6eea0\",17:\"0d1f36b1\",18:\"0f184e32\",19:\"57ad231b\",20:\"58c8b075\",21:\"0c46ffa9\",22:\"965cbc0d\",23:\"18c270f0\",24:\"5895d76a\",25:\"a347f1d6\",26:\"c7b8345d\",27:\"4e6f90b7\",28:\"6e0fa7bc\",29:\"77b8e3b6\",30:\"69b1b865\",31:\"c68f976d\",32:\"0a08a140\"}[t]+\".js\"}(t);var u=new Error;i=function(e){s.onerror=s.onload=null,clearTimeout(l);var n=o[t];if(0!==n){if(n){var r=e&&(\"load\"===e.type?\"missing\":e.type),i=e&&e.target&&e.target.src;u.message=\"Loading chunk \"+t+\" failed.\\n(\"+r+\": \"+i+\")\",u.name=\"ChunkLoadError\",u.type=r,u.request=i,n[1](u)}o[t]=void 0}};var l=setTimeout((function(){i({type:\"timeout\",target:s})}),12e4);s.onerror=s.onload=i,document.head.appendChild(s)}return Promise.all(e)},a.m=t,a.c=r,a.d=function(t,e,n){a.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},a.r=function(t){\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(t,\"__esModule\",{value:!0})},a.t=function(t,e){if(1&e&&(t=a(t)),8&e)return t;if(4&e&&\"object\"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(a.r(n),Object.defineProperty(n,\"default\",{enumerable:!0,value:t}),2&e&&\"string\"!=typeof t)for(var r in t)a.d(n,r,function(e){return t[e]}.bind(null,r));return n},a.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return a.d(e,\"a\",e),e},a.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},a.p=\"/\",a.oe=function(t){throw console.error(t),t};var s=window.webpackJsonp=window.webpackJsonp||[],u=s.push.bind(s);s.push=e,s=s.slice();for(var l=0;l<s.length;l++)e(s[l]);var c=u;i.push([50,0]),n()}([function(t,e){var n=Array.isArray;t.exports=n},function(t,e,n){var r=n(23),o=\"object\"==typeof self&&self&&self.Object===Object&&self,i=r||o||Function(\"return this\")();t.exports=i},function(t,e,n){var r=n(69),o=n(72);t.exports=function(t,e){var n=o(t,e);return r(n)?n:void 0}},function(t,e){t.exports=function(t){return null!=t&&\"object\"==typeof t}},function(t,e,n){var r=n(5),o=n(54),i=n(55),a=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?\"[object Undefined]\":\"[object Null]\":a&&a in Object(t)?o(t):i(t)}},function(t,e,n){var r=n(1).Symbol;t.exports=r},function(t,e,n){\"use strict\";function r(t,e,n,r,o,i,a,s){var u,l=\"function\"==typeof t?t.options:t;if(e&&(l.render=e,l.staticRenderFns=n,l._compiled=!0),r&&(l.functional=!0),i&&(l._scopeId=\"data-v-\"+i),a?(u=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||\"undefined\"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},l._ssrRegister=u):o&&(u=s?function(){o.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:o),u)if(l.functional){l._injectStyles=u;var c=l.render;l.render=function(t,e){return u.call(e),c(t,e)}}else{var f=l.beforeCreate;l.beforeCreate=f?[].concat(f,u):[u]}return{exports:t,options:l}}n.d(e,\"a\",(function(){return r}))},function(t,e,n){var r=n(59),o=n(60),i=n(61),a=n(62),s=n(63);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}u.prototype.clear=r,u.prototype.delete=o,u.prototype.get=i,u.prototype.has=a,u.prototype.set=s,t.exports=u},function(t,e,n){var r=n(25);t.exports=function(t,e){for(var n=t.length;n--;)if(r(t[n][0],e))return n;return-1}},function(t,e,n){var r=n(2)(Object,\"create\");t.exports=r},function(t,e,n){var r=n(81);t.exports=function(t,e){var n=t.__data__;return r(e)?n[\"string\"==typeof e?\"string\":\"hash\"]:n.map}},function(t,e,n){var r=n(20);t.exports=function(t){if(\"string\"==typeof t||r(t))return t;var e=t+\"\";return\"0\"==e&&1/t==-1/0?\"-0\":e}},function(t,e,n){var r,o;\n/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress\n * @license MIT */void 0===(o=\"function\"==typeof(r=function(){var t,e,n={version:\"0.2.0\"},r=n.settings={minimum:.08,easing:\"ease\",positionUsing:\"\",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role=\"bar\"]',spinnerSelector:'[role=\"spinner\"]',parent:\"body\",template:'<div class=\"bar\" role=\"bar\"><div class=\"peg\"></div></div><div class=\"spinner\" role=\"spinner\"><div class=\"spinner-icon\"></div></div>'};function o(t,e,n){return t<e?e:t>n?n:t}function i(t){return 100*(-1+t)}n.configure=function(t){var e,n;for(e in t)void 0!==(n=t[e])&&t.hasOwnProperty(e)&&(r[e]=n);return this},n.status=null,n.set=function(t){var e=n.isStarted();t=o(t,r.minimum,1),n.status=1===t?null:t;var u=n.render(!e),l=u.querySelector(r.barSelector),c=r.speed,f=r.easing;return u.offsetWidth,a((function(e){\"\"===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(l,function(t,e,n){var o;return(o=\"translate3d\"===r.positionUsing?{transform:\"translate3d(\"+i(t)+\"%,0,0)\"}:\"translate\"===r.positionUsing?{transform:\"translate(\"+i(t)+\"%,0)\"}:{\"margin-left\":i(t)+\"%\"}).transition=\"all \"+e+\"ms \"+n,o}(t,c,f)),1===t?(s(u,{transition:\"none\",opacity:1}),u.offsetWidth,setTimeout((function(){s(u,{transition:\"all \"+c+\"ms linear\",opacity:0}),setTimeout((function(){n.remove(),e()}),c)}),c)):setTimeout(e,c)})),this},n.isStarted=function(){return\"number\"==typeof n.status},n.start=function(){n.status||n.set(0);var t=function(){setTimeout((function(){n.status&&(n.trickle(),t())}),r.trickleSpeed)};return r.trickle&&t(),this},n.done=function(t){return t||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(t){var e=n.status;return e?(\"number\"!=typeof t&&(t=(1-e)*o(Math.random()*e,.1,.95)),e=o(e+t,0,.994),n.set(e)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},t=0,e=0,n.promise=function(r){return r&&\"resolved\"!==r.state()?(0===e&&n.start(),t++,e++,r.always((function(){0==--e?(t=0,n.done()):n.set((t-e)/t)})),this):this},n.render=function(t){if(n.isRendered())return document.getElementById(\"nprogress\");l(document.documentElement,\"nprogress-busy\");var e=document.createElement(\"div\");e.id=\"nprogress\",e.innerHTML=r.template;var o,a=e.querySelector(r.barSelector),u=t?\"-100\":i(n.status||0),c=document.querySelector(r.parent);return s(a,{transition:\"all 0 linear\",transform:\"translate3d(\"+u+\"%,0,0)\"}),r.showSpinner||(o=e.querySelector(r.spinnerSelector))&&p(o),c!=document.body&&l(c,\"nprogress-custom-parent\"),c.appendChild(e),e},n.remove=function(){c(document.documentElement,\"nprogress-busy\"),c(document.querySelector(r.parent),\"nprogress-custom-parent\");var t=document.getElementById(\"nprogress\");t&&p(t)},n.isRendered=function(){return!!document.getElementById(\"nprogress\")},n.getPositioningCSS=function(){var t=document.body.style,e=\"WebkitTransform\"in t?\"Webkit\":\"MozTransform\"in t?\"Moz\":\"msTransform\"in t?\"ms\":\"OTransform\"in t?\"O\":\"\";return e+\"Perspective\"in t?\"translate3d\":e+\"Transform\"in t?\"translate\":\"margin\"};var a=function(){var t=[];function e(){var n=t.shift();n&&n(e)}return function(n){t.push(n),1==t.length&&e()}}(),s=function(){var t=[\"Webkit\",\"O\",\"Moz\",\"ms\"],e={};function n(n){return n=n.replace(/^-ms-/,\"ms-\").replace(/-([\\da-z])/gi,(function(t,e){return e.toUpperCase()})),e[n]||(e[n]=function(e){var n=document.body.style;if(e in n)return e;for(var r,o=t.length,i=e.charAt(0).toUpperCase()+e.slice(1);o--;)if((r=t[o]+i)in n)return r;return e}(n))}function r(t,e,r){e=n(e),t.style[e]=r}return function(t,e){var n,o,i=arguments;if(2==i.length)for(n in e)void 0!==(o=e[n])&&e.hasOwnProperty(n)&&r(t,n,o);else r(t,i[1],i[2])}}();function u(t,e){return(\"string\"==typeof t?t:f(t)).indexOf(\" \"+e+\" \")>=0}function l(t,e){var n=f(t),r=n+e;u(n,e)||(t.className=r.substring(1))}function c(t,e){var n,r=f(t);u(t,e)&&(n=r.replace(\" \"+e+\" \",\" \"),t.className=n.substring(1,n.length-1))}function f(t){return(\" \"+(t.className||\"\")+\" \").replace(/\\s+/gi,\" \")}function p(t){t&&t.parentNode&&t.parentNode.removeChild(t)}return n})?r.call(e,n,e,t):r)||(t.exports=o)},function(t,e,n){var r=n(53),o=n(3),i=Object.prototype,a=i.hasOwnProperty,s=i.propertyIsEnumerable,u=r(function(){return arguments}())?r:function(t){return o(t)&&a.call(t,\"callee\")&&!s.call(t,\"callee\")};t.exports=u},function(t,e,n){var r=n(2)(n(1),\"Map\");t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&(\"object\"==e||\"function\"==e)}},function(t,e,n){var r=n(73),o=n(80),i=n(82),a=n(83),s=n(84);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}u.prototype.clear=r,u.prototype.delete=o,u.prototype.get=i,u.prototype.has=a,u.prototype.set=s,t.exports=u},function(t,e){t.exports=function(t){var e=-1,n=Array(t.size);return t.forEach((function(t){n[++e]=t})),n}},function(t,e){t.exports=function(t){return\"number\"==typeof t&&t>-1&&t%1==0&&t<=9007199254740991}},function(t,e,n){var r=n(0),o=n(20),i=/\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,a=/^\\w*$/;t.exports=function(t,e){if(r(t))return!1;var n=typeof t;return!(\"number\"!=n&&\"symbol\"!=n&&\"boolean\"!=n&&null!=t&&!o(t))||(a.test(t)||!i.test(t)||null!=e&&t in Object(e))}},function(t,e,n){var r=n(4),o=n(3);t.exports=function(t){return\"symbol\"==typeof t||o(t)&&\"[object Symbol]\"==r(t)}},function(t,e){t.exports=function(t){return t}},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,o=t.length;++n<r;)t[o+n]=e[n];return t}},function(t,e){var n=\"object\"==typeof global&&global&&global.Object===Object&&global;t.exports=n},function(t,e,n){var r=n(7),o=n(64),i=n(65),a=n(66),s=n(67),u=n(68);function l(t){var e=this.__data__=new r(t);this.size=e.size}l.prototype.clear=o,l.prototype.delete=i,l.prototype.get=a,l.prototype.has=s,l.prototype.set=u,t.exports=l},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e,n){var r=n(4),o=n(15);t.exports=function(t){if(!o(t))return!1;var e=r(t);return\"[object Function]\"==e||\"[object GeneratorFunction]\"==e||\"[object AsyncFunction]\"==e||\"[object Proxy]\"==e}},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+\"\"}catch(t){}}return\"\"}},function(t,e,n){var r=n(85),o=n(3);t.exports=function t(e,n,i,a,s){return e===n||(null==e||null==n||!o(e)&&!o(n)?e!=e&&n!=n:r(e,n,i,a,t,s))}},function(t,e,n){var r=n(30),o=n(88),i=n(31);t.exports=function(t,e,n,a,s,u){var l=1&n,c=t.length,f=e.length;if(c!=f&&!(l&&f>c))return!1;var p=u.get(t),d=u.get(e);if(p&&d)return p==e&&d==t;var h=-1,v=!0,m=2&n?new r:void 0;for(u.set(t,e),u.set(e,t);++h<c;){var g=t[h],y=e[h];if(a)var b=l?a(y,g,h,e,t,u):a(g,y,h,t,e,u);if(void 0!==b){if(b)continue;v=!1;break}if(m){if(!o(e,(function(t,e){if(!i(m,e)&&(g===t||s(g,t,n,a,u)))return m.push(e)}))){v=!1;break}}else if(g!==y&&!s(g,y,n,a,u)){v=!1;break}}return u.delete(t),u.delete(e),v}},function(t,e,n){var r=n(16),o=n(86),i=n(87);function a(t){var e=-1,n=null==t?0:t.length;for(this.__data__=new r;++e<n;)this.add(t[e])}a.prototype.add=a.prototype.push=o,a.prototype.has=i,t.exports=a},function(t,e){t.exports=function(t,e){return t.has(e)}},function(t,e,n){var r=n(98),o=n(104),i=n(37);t.exports=function(t){return i(t)?r(t):o(t)}},function(t,e,n){(function(t){var r=n(1),o=n(100),i=e&&!e.nodeType&&e,a=i&&\"object\"==typeof t&&t&&!t.nodeType&&t,s=a&&a.exports===i?r.Buffer:void 0,u=(s?s.isBuffer:void 0)||o;t.exports=u}).call(this,n(34)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,\"loaded\",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,\"id\",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e){var n=/^(?:0|[1-9]\\d*)$/;t.exports=function(t,e){var r=typeof t;return!!(e=null==e?9007199254740991:e)&&(\"number\"==r||\"symbol\"!=r&&n.test(t))&&t>-1&&t%1==0&&t<e}},function(t,e,n){var r=n(101),o=n(102),i=n(103),a=i&&i.isTypedArray,s=a?o(a):r;t.exports=s},function(t,e,n){var r=n(26),o=n(18);t.exports=function(t){return null!=t&&o(t.length)&&!r(t)}},function(t,e,n){var r=n(2)(n(1),\"Set\");t.exports=r},function(t,e,n){var r=n(15);t.exports=function(t){return t==t&&!r(t)}},function(t,e){t.exports=function(t,e){return function(n){return null!=n&&(n[t]===e&&(void 0!==e||t in Object(n)))}}},function(t,e,n){var r=n(42),o=n(11);t.exports=function(t,e){for(var n=0,i=(e=r(e,t)).length;null!=t&&n<i;)t=t[o(e[n++])];return n&&n==i?t:void 0}},function(t,e,n){var r=n(0),o=n(19),i=n(114),a=n(117);t.exports=function(t,e){return r(t)?t:o(t,e)?[t]:i(a(t))}},function(t,e,n){},function(t,e,n){},function(t,e,n){var r=n(51),o=n(56),i=n(126),a=n(134),s=n(143),u=n(49),l=i((function(t){var e=u(t);return s(e)&&(e=void 0),a(r(t,1,s,!0),o(e,2))}));t.exports=l},function(t,e,n){\"use strict\";\n/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */var r=/[\"'&<>]/;t.exports=function(t){var e,n=\"\"+t,o=r.exec(n);if(!o)return n;var i=\"\",a=0,s=0;for(a=o.index;a<n.length;a++){switch(n.charCodeAt(a)){case 34:e=\"&quot;\";break;case 38:e=\"&amp;\";break;case 39:e=\"&#39;\";break;case 60:e=\"&lt;\";break;case 62:e=\"&gt;\";break;default:continue}s!==a&&(i+=n.substring(s,a)),s=a+1,i+=e}return s!==a?i+n.substring(s,a):i}},function(t,e){var n=/^\\s+|\\s+$/g,r=/^[-+]0x[0-9a-f]+$/i,o=/^0b[01]+$/i,i=/^0o[0-7]+$/i,a=parseInt,s=\"object\"==typeof global&&global&&global.Object===Object&&global,u=\"object\"==typeof self&&self&&self.Object===Object&&self,l=s||u||Function(\"return this\")(),c=Object.prototype.toString,f=Math.max,p=Math.min,d=function(){return l.Date.now()};function h(t){var e=typeof t;return!!t&&(\"object\"==e||\"function\"==e)}function v(t){if(\"number\"==typeof t)return t;if(function(t){return\"symbol\"==typeof t||function(t){return!!t&&\"object\"==typeof t}(t)&&\"[object Symbol]\"==c.call(t)}(t))return NaN;if(h(t)){var e=\"function\"==typeof t.valueOf?t.valueOf():t;t=h(e)?e+\"\":e}if(\"string\"!=typeof t)return 0===t?t:+t;t=t.replace(n,\"\");var s=o.test(t);return s||i.test(t)?a(t.slice(2),s?2:8):r.test(t)?NaN:+t}t.exports=function(t,e,n){var r,o,i,a,s,u,l=0,c=!1,m=!1,g=!0;if(\"function\"!=typeof t)throw new TypeError(\"Expected a function\");function y(e){var n=r,i=o;return r=o=void 0,l=e,a=t.apply(i,n)}function b(t){return l=t,s=setTimeout(x,e),c?y(t):a}function _(t){var n=t-u;return void 0===u||n>=e||n<0||m&&t-l>=i}function x(){var t=d();if(_(t))return w(t);s=setTimeout(x,function(t){var n=e-(t-u);return m?p(n,i-(t-l)):n}(t))}function w(t){return s=void 0,g&&r?y(t):(r=o=void 0,a)}function k(){var t=d(),n=_(t);if(r=arguments,o=this,u=t,n){if(void 0===s)return b(u);if(m)return s=setTimeout(x,e),y(u)}return void 0===s&&(s=setTimeout(x,e)),a}return e=v(e)||0,h(n)&&(c=!!n.leading,i=(m=\"maxWait\"in n)?f(v(n.maxWait)||0,e):i,g=\"trailing\"in n?!!n.trailing:g),k.cancel=function(){void 0!==s&&clearTimeout(s),l=0,r=u=o=s=void 0},k.flush=function(){return void 0===s?a:w(d())},k}},function(t,e,n){var r=n(41);t.exports=function(t,e,n){var o=null==t?void 0:r(t,e);return void 0===o?n:o}},function(t,e){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},function(t,e,n){t.exports=n(148)},function(t,e,n){var r=n(22),o=n(52);t.exports=function t(e,n,i,a,s){var u=-1,l=e.length;for(i||(i=o),s||(s=[]);++u<l;){var c=e[u];n>0&&i(c)?n>1?t(c,n-1,i,a,s):r(s,c):a||(s[s.length]=c)}return s}},function(t,e,n){var r=n(5),o=n(13),i=n(0),a=r?r.isConcatSpreadable:void 0;t.exports=function(t){return i(t)||o(t)||!!(a&&t&&t[a])}},function(t,e,n){var r=n(4),o=n(3);t.exports=function(t){return o(t)&&\"[object Arguments]\"==r(t)}},function(t,e,n){var r=n(5),o=Object.prototype,i=o.hasOwnProperty,a=o.toString,s=r?r.toStringTag:void 0;t.exports=function(t){var e=i.call(t,s),n=t[s];try{t[s]=void 0;var r=!0}catch(t){}var o=a.call(t);return r&&(e?t[s]=n:delete t[s]),o}},function(t,e){var n=Object.prototype.toString;t.exports=function(t){return n.call(t)}},function(t,e,n){var r=n(57),o=n(113),i=n(21),a=n(0),s=n(123);t.exports=function(t){return\"function\"==typeof t?t:null==t?i:\"object\"==typeof t?a(t)?o(t[0],t[1]):r(t):s(t)}},function(t,e,n){var r=n(58),o=n(112),i=n(40);t.exports=function(t){var e=o(t);return 1==e.length&&e[0][2]?i(e[0][0],e[0][1]):function(n){return n===t||r(n,t,e)}}},function(t,e,n){var r=n(24),o=n(28);t.exports=function(t,e,n,i){var a=n.length,s=a,u=!i;if(null==t)return!s;for(t=Object(t);a--;){var l=n[a];if(u&&l[2]?l[1]!==t[l[0]]:!(l[0]in t))return!1}for(;++a<s;){var c=(l=n[a])[0],f=t[c],p=l[1];if(u&&l[2]){if(void 0===f&&!(c in t))return!1}else{var d=new r;if(i)var h=i(f,p,c,t,e,d);if(!(void 0===h?o(p,f,3,i,d):h))return!1}}return!0}},function(t,e){t.exports=function(){this.__data__=[],this.size=0}},function(t,e,n){var r=n(8),o=Array.prototype.splice;t.exports=function(t){var e=this.__data__,n=r(e,t);return!(n<0)&&(n==e.length-1?e.pop():o.call(e,n,1),--this.size,!0)}},function(t,e,n){var r=n(8);t.exports=function(t){var e=this.__data__,n=r(e,t);return n<0?void 0:e[n][1]}},function(t,e,n){var r=n(8);t.exports=function(t){return r(this.__data__,t)>-1}},function(t,e,n){var r=n(8);t.exports=function(t,e){var n=this.__data__,o=r(n,t);return o<0?(++this.size,n.push([t,e])):n[o][1]=e,this}},function(t,e,n){var r=n(7);t.exports=function(){this.__data__=new r,this.size=0}},function(t,e){t.exports=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}},function(t,e){t.exports=function(t){return this.__data__.get(t)}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e,n){var r=n(7),o=n(14),i=n(16);t.exports=function(t,e){var n=this.__data__;if(n instanceof r){var a=n.__data__;if(!o||a.length<199)return a.push([t,e]),this.size=++n.size,this;n=this.__data__=new i(a)}return n.set(t,e),this.size=n.size,this}},function(t,e,n){var r=n(26),o=n(70),i=n(15),a=n(27),s=/^\\[object .+?Constructor\\]$/,u=Function.prototype,l=Object.prototype,c=u.toString,f=l.hasOwnProperty,p=RegExp(\"^\"+c.call(f).replace(/[\\\\^$.*+?()[\\]{}|]/g,\"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g,\"$1.*?\")+\"$\");t.exports=function(t){return!(!i(t)||o(t))&&(r(t)?p:s).test(a(t))}},function(t,e,n){var r,o=n(71),i=(r=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||\"\"))?\"Symbol(src)_1.\"+r:\"\";t.exports=function(t){return!!i&&i in t}},function(t,e,n){var r=n(1)[\"__core-js_shared__\"];t.exports=r},function(t,e){t.exports=function(t,e){return null==t?void 0:t[e]}},function(t,e,n){var r=n(74),o=n(7),i=n(14);t.exports=function(){this.size=0,this.__data__={hash:new r,map:new(i||o),string:new r}}},function(t,e,n){var r=n(75),o=n(76),i=n(77),a=n(78),s=n(79);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}u.prototype.clear=r,u.prototype.delete=o,u.prototype.get=i,u.prototype.has=a,u.prototype.set=s,t.exports=u},function(t,e,n){var r=n(9);t.exports=function(){this.__data__=r?r(null):{},this.size=0}},function(t,e){t.exports=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}},function(t,e,n){var r=n(9),o=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;if(r){var n=e[t];return\"__lodash_hash_undefined__\"===n?void 0:n}return o.call(e,t)?e[t]:void 0}},function(t,e,n){var r=n(9),o=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;return r?void 0!==e[t]:o.call(e,t)}},function(t,e,n){var r=n(9);t.exports=function(t,e){var n=this.__data__;return this.size+=this.has(t)?0:1,n[t]=r&&void 0===e?\"__lodash_hash_undefined__\":e,this}},function(t,e,n){var r=n(10);t.exports=function(t){var e=r(this,t).delete(t);return this.size-=e?1:0,e}},function(t,e){t.exports=function(t){var e=typeof t;return\"string\"==e||\"number\"==e||\"symbol\"==e||\"boolean\"==e?\"__proto__\"!==t:null===t}},function(t,e,n){var r=n(10);t.exports=function(t){return r(this,t).get(t)}},function(t,e,n){var r=n(10);t.exports=function(t){return r(this,t).has(t)}},function(t,e,n){var r=n(10);t.exports=function(t,e){var n=r(this,t),o=n.size;return n.set(t,e),this.size+=n.size==o?0:1,this}},function(t,e,n){var r=n(24),o=n(29),i=n(89),a=n(92),s=n(108),u=n(0),l=n(33),c=n(36),f=\"[object Object]\",p=Object.prototype.hasOwnProperty;t.exports=function(t,e,n,d,h,v){var m=u(t),g=u(e),y=m?\"[object Array]\":s(t),b=g?\"[object Array]\":s(e),_=(y=\"[object Arguments]\"==y?f:y)==f,x=(b=\"[object Arguments]\"==b?f:b)==f,w=y==b;if(w&&l(t)){if(!l(e))return!1;m=!0,_=!1}if(w&&!_)return v||(v=new r),m||c(t)?o(t,e,n,d,h,v):i(t,e,y,n,d,h,v);if(!(1&n)){var k=_&&p.call(t,\"__wrapped__\"),$=x&&p.call(e,\"__wrapped__\");if(k||$){var C=k?t.value():t,O=$?e.value():e;return v||(v=new r),h(C,O,n,d,v)}}return!!w&&(v||(v=new r),a(t,e,n,d,h,v))}},function(t,e){t.exports=function(t){return this.__data__.set(t,\"__lodash_hash_undefined__\"),this}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length;++n<r;)if(e(t[n],n,t))return!0;return!1}},function(t,e,n){var r=n(5),o=n(90),i=n(25),a=n(29),s=n(91),u=n(17),l=r?r.prototype:void 0,c=l?l.valueOf:void 0;t.exports=function(t,e,n,r,l,f,p){switch(n){case\"[object DataView]\":if(t.byteLength!=e.byteLength||t.byteOffset!=e.byteOffset)return!1;t=t.buffer,e=e.buffer;case\"[object ArrayBuffer]\":return!(t.byteLength!=e.byteLength||!f(new o(t),new o(e)));case\"[object Boolean]\":case\"[object Date]\":case\"[object Number]\":return i(+t,+e);case\"[object Error]\":return t.name==e.name&&t.message==e.message;case\"[object RegExp]\":case\"[object String]\":return t==e+\"\";case\"[object Map]\":var d=s;case\"[object Set]\":var h=1&r;if(d||(d=u),t.size!=e.size&&!h)return!1;var v=p.get(t);if(v)return v==e;r|=2,p.set(t,e);var m=a(d(t),d(e),r,l,f,p);return p.delete(t),m;case\"[object Symbol]\":if(c)return c.call(t)==c.call(e)}return!1}},function(t,e,n){var r=n(1).Uint8Array;t.exports=r},function(t,e){t.exports=function(t){var e=-1,n=Array(t.size);return t.forEach((function(t,r){n[++e]=[r,t]})),n}},function(t,e,n){var r=n(93),o=Object.prototype.hasOwnProperty;t.exports=function(t,e,n,i,a,s){var u=1&n,l=r(t),c=l.length;if(c!=r(e).length&&!u)return!1;for(var f=c;f--;){var p=l[f];if(!(u?p in e:o.call(e,p)))return!1}var d=s.get(t),h=s.get(e);if(d&&h)return d==e&&h==t;var v=!0;s.set(t,e),s.set(e,t);for(var m=u;++f<c;){var g=t[p=l[f]],y=e[p];if(i)var b=u?i(y,g,p,e,t,s):i(g,y,p,t,e,s);if(!(void 0===b?g===y||a(g,y,n,i,s):b)){v=!1;break}m||(m=\"constructor\"==p)}if(v&&!m){var _=t.constructor,x=e.constructor;_==x||!(\"constructor\"in t)||!(\"constructor\"in e)||\"function\"==typeof _&&_ instanceof _&&\"function\"==typeof x&&x instanceof x||(v=!1)}return s.delete(t),s.delete(e),v}},function(t,e,n){var r=n(94),o=n(95),i=n(32);t.exports=function(t){return r(t,i,o)}},function(t,e,n){var r=n(22),o=n(0);t.exports=function(t,e,n){var i=e(t);return o(t)?i:r(i,n(t))}},function(t,e,n){var r=n(96),o=n(97),i=Object.prototype.propertyIsEnumerable,a=Object.getOwnPropertySymbols,s=a?function(t){return null==t?[]:(t=Object(t),r(a(t),(function(e){return i.call(t,e)})))}:o;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length,o=0,i=[];++n<r;){var a=t[n];e(a,n,t)&&(i[o++]=a)}return i}},function(t,e){t.exports=function(){return[]}},function(t,e,n){var r=n(99),o=n(13),i=n(0),a=n(33),s=n(35),u=n(36),l=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=i(t),c=!n&&o(t),f=!n&&!c&&a(t),p=!n&&!c&&!f&&u(t),d=n||c||f||p,h=d?r(t.length,String):[],v=h.length;for(var m in t)!e&&!l.call(t,m)||d&&(\"length\"==m||f&&(\"offset\"==m||\"parent\"==m)||p&&(\"buffer\"==m||\"byteLength\"==m||\"byteOffset\"==m)||s(m,v))||h.push(m);return h}},function(t,e){t.exports=function(t,e){for(var n=-1,r=Array(t);++n<t;)r[n]=e(n);return r}},function(t,e){t.exports=function(){return!1}},function(t,e,n){var r=n(4),o=n(18),i=n(3),a={};a[\"[object Float32Array]\"]=a[\"[object Float64Array]\"]=a[\"[object Int8Array]\"]=a[\"[object Int16Array]\"]=a[\"[object Int32Array]\"]=a[\"[object Uint8Array]\"]=a[\"[object Uint8ClampedArray]\"]=a[\"[object Uint16Array]\"]=a[\"[object Uint32Array]\"]=!0,a[\"[object Arguments]\"]=a[\"[object Array]\"]=a[\"[object ArrayBuffer]\"]=a[\"[object Boolean]\"]=a[\"[object DataView]\"]=a[\"[object Date]\"]=a[\"[object Error]\"]=a[\"[object Function]\"]=a[\"[object Map]\"]=a[\"[object Number]\"]=a[\"[object Object]\"]=a[\"[object RegExp]\"]=a[\"[object Set]\"]=a[\"[object String]\"]=a[\"[object WeakMap]\"]=!1,t.exports=function(t){return i(t)&&o(t.length)&&!!a[r(t)]}},function(t,e){t.exports=function(t){return function(e){return t(e)}}},function(t,e,n){(function(t){var r=n(23),o=e&&!e.nodeType&&e,i=o&&\"object\"==typeof t&&t&&!t.nodeType&&t,a=i&&i.exports===o&&r.process,s=function(){try{var t=i&&i.require&&i.require(\"util\").types;return t||a&&a.binding&&a.binding(\"util\")}catch(t){}}();t.exports=s}).call(this,n(34)(t))},function(t,e,n){var r=n(105),o=n(106),i=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return o(t);var e=[];for(var n in Object(t))i.call(t,n)&&\"constructor\"!=n&&e.push(n);return e}},function(t,e){var n=Object.prototype;t.exports=function(t){var e=t&&t.constructor;return t===(\"function\"==typeof e&&e.prototype||n)}},function(t,e,n){var r=n(107)(Object.keys,Object);t.exports=r},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){var r=n(109),o=n(14),i=n(110),a=n(38),s=n(111),u=n(4),l=n(27),c=l(r),f=l(o),p=l(i),d=l(a),h=l(s),v=u;(r&&\"[object DataView]\"!=v(new r(new ArrayBuffer(1)))||o&&\"[object Map]\"!=v(new o)||i&&\"[object Promise]\"!=v(i.resolve())||a&&\"[object Set]\"!=v(new a)||s&&\"[object WeakMap]\"!=v(new s))&&(v=function(t){var e=u(t),n=\"[object Object]\"==e?t.constructor:void 0,r=n?l(n):\"\";if(r)switch(r){case c:return\"[object DataView]\";case f:return\"[object Map]\";case p:return\"[object Promise]\";case d:return\"[object Set]\";case h:return\"[object WeakMap]\"}return e}),t.exports=v},function(t,e,n){var r=n(2)(n(1),\"DataView\");t.exports=r},function(t,e,n){var r=n(2)(n(1),\"Promise\");t.exports=r},function(t,e,n){var r=n(2)(n(1),\"WeakMap\");t.exports=r},function(t,e,n){var r=n(39),o=n(32);t.exports=function(t){for(var e=o(t),n=e.length;n--;){var i=e[n],a=t[i];e[n]=[i,a,r(a)]}return e}},function(t,e,n){var r=n(28),o=n(48),i=n(120),a=n(19),s=n(39),u=n(40),l=n(11);t.exports=function(t,e){return a(t)&&s(e)?u(l(t),e):function(n){var a=o(n,t);return void 0===a&&a===e?i(n,t):r(e,a,3)}}},function(t,e,n){var r=n(115),o=/[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,i=/\\\\(\\\\)?/g,a=r((function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(\"\"),t.replace(o,(function(t,n,r,o){e.push(r?o.replace(i,\"$1\"):n||t)})),e}));t.exports=a},function(t,e,n){var r=n(116);t.exports=function(t){var e=r(t,(function(t){return 500===n.size&&n.clear(),t})),n=e.cache;return e}},function(t,e,n){var r=n(16);function o(t,e){if(\"function\"!=typeof t||null!=e&&\"function\"!=typeof e)throw new TypeError(\"Expected a function\");var n=function(){var r=arguments,o=e?e.apply(this,r):r[0],i=n.cache;if(i.has(o))return i.get(o);var a=t.apply(this,r);return n.cache=i.set(o,a)||i,a};return n.cache=new(o.Cache||r),n}o.Cache=r,t.exports=o},function(t,e,n){var r=n(118);t.exports=function(t){return null==t?\"\":r(t)}},function(t,e,n){var r=n(5),o=n(119),i=n(0),a=n(20),s=r?r.prototype:void 0,u=s?s.toString:void 0;t.exports=function t(e){if(\"string\"==typeof e)return e;if(i(e))return o(e,t)+\"\";if(a(e))return u?u.call(e):\"\";var n=e+\"\";return\"0\"==n&&1/e==-1/0?\"-0\":n}},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length,o=Array(r);++n<r;)o[n]=e(t[n],n,t);return o}},function(t,e,n){var r=n(121),o=n(122);t.exports=function(t,e){return null!=t&&o(t,e,r)}},function(t,e){t.exports=function(t,e){return null!=t&&e in Object(t)}},function(t,e,n){var r=n(42),o=n(13),i=n(0),a=n(35),s=n(18),u=n(11);t.exports=function(t,e,n){for(var l=-1,c=(e=r(e,t)).length,f=!1;++l<c;){var p=u(e[l]);if(!(f=null!=t&&n(t,p)))break;t=t[p]}return f||++l!=c?f:!!(c=null==t?0:t.length)&&s(c)&&a(p,c)&&(i(t)||o(t))}},function(t,e,n){var r=n(124),o=n(125),i=n(19),a=n(11);t.exports=function(t){return i(t)?r(a(t)):o(t)}},function(t,e){t.exports=function(t){return function(e){return null==e?void 0:e[t]}}},function(t,e,n){var r=n(41);t.exports=function(t){return function(e){return r(e,t)}}},function(t,e,n){var r=n(21),o=n(127),i=n(129);t.exports=function(t,e){return i(o(t,e,r),t+\"\")}},function(t,e,n){var r=n(128),o=Math.max;t.exports=function(t,e,n){return e=o(void 0===e?t.length-1:e,0),function(){for(var i=arguments,a=-1,s=o(i.length-e,0),u=Array(s);++a<s;)u[a]=i[e+a];a=-1;for(var l=Array(e+1);++a<e;)l[a]=i[a];return l[e]=n(u),r(t,this,l)}}},function(t,e){t.exports=function(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}},function(t,e,n){var r=n(130),o=n(133)(r);t.exports=o},function(t,e,n){var r=n(131),o=n(132),i=n(21),a=o?function(t,e){return o(t,\"toString\",{configurable:!0,enumerable:!1,value:r(e),writable:!0})}:i;t.exports=a},function(t,e){t.exports=function(t){return function(){return t}}},function(t,e,n){var r=n(2),o=function(){try{var t=r(Object,\"defineProperty\");return t({},\"\",{}),t}catch(t){}}();t.exports=o},function(t,e){var n=Date.now;t.exports=function(t){var e=0,r=0;return function(){var o=n(),i=16-(o-r);if(r=o,i>0){if(++e>=800)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}},function(t,e,n){var r=n(30),o=n(135),i=n(140),a=n(31),s=n(141),u=n(17);t.exports=function(t,e,n){var l=-1,c=o,f=t.length,p=!0,d=[],h=d;if(n)p=!1,c=i;else if(f>=200){var v=e?null:s(t);if(v)return u(v);p=!1,c=a,h=new r}else h=e?[]:d;t:for(;++l<f;){var m=t[l],g=e?e(m):m;if(m=n||0!==m?m:0,p&&g==g){for(var y=h.length;y--;)if(h[y]===g)continue t;e&&h.push(g),d.push(m)}else c(h,g,n)||(h!==d&&h.push(g),d.push(m))}return d}},function(t,e,n){var r=n(136);t.exports=function(t,e){return!!(null==t?0:t.length)&&r(t,e,0)>-1}},function(t,e,n){var r=n(137),o=n(138),i=n(139);t.exports=function(t,e,n){return e==e?i(t,e,n):r(t,o,n)}},function(t,e){t.exports=function(t,e,n,r){for(var o=t.length,i=n+(r?1:-1);r?i--:++i<o;)if(e(t[i],i,t))return i;return-1}},function(t,e){t.exports=function(t){return t!=t}},function(t,e){t.exports=function(t,e,n){for(var r=n-1,o=t.length;++r<o;)if(t[r]===e)return r;return-1}},function(t,e){t.exports=function(t,e,n){for(var r=-1,o=null==t?0:t.length;++r<o;)if(n(e,t[r]))return!0;return!1}},function(t,e,n){var r=n(38),o=n(142),i=n(17),a=r&&1/i(new r([,-0]))[1]==1/0?function(t){return new r(t)}:o;t.exports=a},function(t,e){t.exports=function(){}},function(t,e,n){var r=n(37),o=n(3);t.exports=function(t){return o(t)&&r(t)}},function(t,e,n){},function(t,e,n){},function(t,e,n){\"use strict\";n(43)},function(t,e,n){\"use strict\";n(44)},function(t,e,n){\"use strict\";n.r(e);\n/*!\n * Vue.js v2.7.14\n * (c) 2014-2022 Evan You\n * Released under the MIT License.\n */\nvar r=Object.freeze({}),o=Array.isArray;function i(t){return null==t}function a(t){return null!=t}function s(t){return!0===t}function u(t){return\"string\"==typeof t||\"number\"==typeof t||\"symbol\"==typeof t||\"boolean\"==typeof t}function l(t){return\"function\"==typeof t}function c(t){return null!==t&&\"object\"==typeof t}var f=Object.prototype.toString;function p(t){return\"[object Object]\"===f.call(t)}function d(t){return\"[object RegExp]\"===f.call(t)}function h(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function v(t){return a(t)&&\"function\"==typeof t.then&&\"function\"==typeof t.catch}function m(t){return null==t?\"\":Array.isArray(t)||p(t)&&t.toString===f?JSON.stringify(t,null,2):String(t)}function g(t){var e=parseFloat(t);return isNaN(e)?t:e}function y(t,e){for(var n=Object.create(null),r=t.split(\",\"),o=0;o<r.length;o++)n[r[o]]=!0;return e?function(t){return n[t.toLowerCase()]}:function(t){return n[t]}}y(\"slot,component\",!0);var b=y(\"key,ref,slot,slot-scope,is\");function _(t,e){var n=t.length;if(n){if(e===t[n-1])return void(t.length=n-1);var r=t.indexOf(e);if(r>-1)return t.splice(r,1)}}var x=Object.prototype.hasOwnProperty;function w(t,e){return x.call(t,e)}function k(t){var e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}var $=/-(\\w)/g,C=k((function(t){return t.replace($,(function(t,e){return e?e.toUpperCase():\"\"}))})),O=k((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),j=/\\B([A-Z])/g,S=k((function(t){return t.replace(j,\"-$1\").toLowerCase()}));var E=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n};function P(t,e){e=e||0;for(var n=t.length-e,r=new Array(n);n--;)r[n]=t[n+e];return r}function A(t,e){for(var n in e)t[n]=e[n];return t}function T(t){for(var e={},n=0;n<t.length;n++)t[n]&&A(e,t[n]);return e}function L(t,e,n){}var R=function(t,e,n){return!1},I=function(t){return t};function M(t,e){if(t===e)return!0;var n=c(t),r=c(e);if(!n||!r)return!n&&!r&&String(t)===String(e);try{var o=Array.isArray(t),i=Array.isArray(e);if(o&&i)return t.length===e.length&&t.every((function(t,n){return M(t,e[n])}));if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(o||i)return!1;var a=Object.keys(t),s=Object.keys(e);return a.length===s.length&&a.every((function(n){return M(t[n],e[n])}))}catch(t){return!1}}function D(t,e){for(var n=0;n<t.length;n++)if(M(t[n],e))return n;return-1}function N(t){var e=!1;return function(){e||(e=!0,t.apply(this,arguments))}}function U(t,e){return t===e?0===t&&1/t!=1/e:t==t||e==e}var F=[\"component\",\"directive\",\"filter\"],z=[\"beforeCreate\",\"created\",\"beforeMount\",\"mounted\",\"beforeUpdate\",\"updated\",\"beforeDestroy\",\"destroyed\",\"activated\",\"deactivated\",\"errorCaptured\",\"serverPrefetch\",\"renderTracked\",\"renderTriggered\"],B={optionMergeStrategies:Object.create(null),silent:!1,productionTip:!1,devtools:!1,performance:!1,errorHandler:null,warnHandler:null,ignoredElements:[],keyCodes:Object.create(null),isReservedTag:R,isReservedAttr:R,isUnknownElement:R,getTagNamespace:L,parsePlatformTagName:I,mustUseProp:R,async:!0,_lifecycleHooks:z},q=/a-zA-Z\\u00B7\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u203F-\\u2040\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD/;function V(t){var e=(t+\"\").charCodeAt(0);return 36===e||95===e}function H(t,e,n,r){Object.defineProperty(t,e,{value:n,enumerable:!!r,writable:!0,configurable:!0})}var W=new RegExp(\"[^\".concat(q.source,\".$_\\\\d]\"));var K=\"__proto__\"in{},J=\"undefined\"!=typeof window,G=J&&window.navigator.userAgent.toLowerCase(),Q=G&&/msie|trident/.test(G),X=G&&G.indexOf(\"msie 9.0\")>0,Y=G&&G.indexOf(\"edge/\")>0;G&&G.indexOf(\"android\");var Z=G&&/iphone|ipad|ipod|ios/.test(G);G&&/chrome\\/\\d+/.test(G),G&&/phantomjs/.test(G);var tt,et=G&&G.match(/firefox\\/(\\d+)/),nt={}.watch,rt=!1;if(J)try{var ot={};Object.defineProperty(ot,\"passive\",{get:function(){rt=!0}}),window.addEventListener(\"test-passive\",null,ot)}catch(t){}var it=function(){return void 0===tt&&(tt=!J&&\"undefined\"!=typeof global&&(global.process&&\"server\"===global.process.env.VUE_ENV)),tt},at=J&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function st(t){return\"function\"==typeof t&&/native code/.test(t.toString())}var ut,lt=\"undefined\"!=typeof Symbol&&st(Symbol)&&\"undefined\"!=typeof Reflect&&st(Reflect.ownKeys);ut=\"undefined\"!=typeof Set&&st(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var ct=null;function ft(t){void 0===t&&(t=null),t||ct&&ct._scope.off(),ct=t,t&&t._scope.on()}var pt=function(){function t(t,e,n,r,o,i,a,s){this.tag=t,this.data=e,this.children=n,this.text=r,this.elm=o,this.ns=void 0,this.context=i,this.fnContext=void 0,this.fnOptions=void 0,this.fnScopeId=void 0,this.key=e&&e.key,this.componentOptions=a,this.componentInstance=void 0,this.parent=void 0,this.raw=!1,this.isStatic=!1,this.isRootInsert=!0,this.isComment=!1,this.isCloned=!1,this.isOnce=!1,this.asyncFactory=s,this.asyncMeta=void 0,this.isAsyncPlaceholder=!1}return Object.defineProperty(t.prototype,\"child\",{get:function(){return this.componentInstance},enumerable:!1,configurable:!0}),t}(),dt=function(t){void 0===t&&(t=\"\");var e=new pt;return e.text=t,e.isComment=!0,e};function ht(t){return new pt(void 0,void 0,void 0,String(t))}function vt(t){var e=new pt(t.tag,t.data,t.children&&t.children.slice(),t.text,t.elm,t.context,t.componentOptions,t.asyncFactory);return e.ns=t.ns,e.isStatic=t.isStatic,e.key=t.key,e.isComment=t.isComment,e.fnContext=t.fnContext,e.fnOptions=t.fnOptions,e.fnScopeId=t.fnScopeId,e.asyncMeta=t.asyncMeta,e.isCloned=!0,e}var mt=0,gt=[],yt=function(){function t(){this._pending=!1,this.id=mt++,this.subs=[]}return t.prototype.addSub=function(t){this.subs.push(t)},t.prototype.removeSub=function(t){this.subs[this.subs.indexOf(t)]=null,this._pending||(this._pending=!0,gt.push(this))},t.prototype.depend=function(e){t.target&&t.target.addDep(this)},t.prototype.notify=function(t){var e=this.subs.filter((function(t){return t}));for(var n=0,r=e.length;n<r;n++){0,e[n].update()}},t}();yt.target=null;var bt=[];function _t(t){bt.push(t),yt.target=t}function xt(){bt.pop(),yt.target=bt[bt.length-1]}var wt=Array.prototype,kt=Object.create(wt);[\"push\",\"pop\",\"shift\",\"unshift\",\"splice\",\"sort\",\"reverse\"].forEach((function(t){var e=wt[t];H(kt,t,(function(){for(var n=[],r=0;r<arguments.length;r++)n[r]=arguments[r];var o,i=e.apply(this,n),a=this.__ob__;switch(t){case\"push\":case\"unshift\":o=n;break;case\"splice\":o=n.slice(2)}return o&&a.observeArray(o),a.dep.notify(),i}))}));var $t=Object.getOwnPropertyNames(kt),Ct={},Ot=!0;function jt(t){Ot=t}var St={notify:L,depend:L,addSub:L,removeSub:L},Et=function(){function t(t,e,n){if(void 0===e&&(e=!1),void 0===n&&(n=!1),this.value=t,this.shallow=e,this.mock=n,this.dep=n?St:new yt,this.vmCount=0,H(t,\"__ob__\",this),o(t)){if(!n)if(K)t.__proto__=kt;else for(var r=0,i=$t.length;r<i;r++){H(t,s=$t[r],kt[s])}e||this.observeArray(t)}else{var a=Object.keys(t);for(r=0;r<a.length;r++){var s;At(t,s=a[r],Ct,void 0,e,n)}}}return t.prototype.observeArray=function(t){for(var e=0,n=t.length;e<n;e++)Pt(t[e],!1,this.mock)},t}();function Pt(t,e,n){return t&&w(t,\"__ob__\")&&t.__ob__ instanceof Et?t.__ob__:!Ot||!n&&it()||!o(t)&&!p(t)||!Object.isExtensible(t)||t.__v_skip||Nt(t)||t instanceof pt?void 0:new Et(t,e,n)}function At(t,e,n,r,i,a){var s=new yt,u=Object.getOwnPropertyDescriptor(t,e);if(!u||!1!==u.configurable){var l=u&&u.get,c=u&&u.set;l&&!c||n!==Ct&&2!==arguments.length||(n=t[e]);var f=!i&&Pt(n,!1,a);return Object.defineProperty(t,e,{enumerable:!0,configurable:!0,get:function(){var e=l?l.call(t):n;return yt.target&&(s.depend(),f&&(f.dep.depend(),o(e)&&Rt(e))),Nt(e)&&!i?e.value:e},set:function(e){var r=l?l.call(t):n;if(U(r,e)){if(c)c.call(t,e);else{if(l)return;if(!i&&Nt(r)&&!Nt(e))return void(r.value=e);n=e}f=!i&&Pt(e,!1,a),s.notify()}}}),s}}function Tt(t,e,n){if(!Dt(t)){var r=t.__ob__;return o(t)&&h(e)?(t.length=Math.max(t.length,e),t.splice(e,1,n),r&&!r.shallow&&r.mock&&Pt(n,!1,!0),n):e in t&&!(e in Object.prototype)?(t[e]=n,n):t._isVue||r&&r.vmCount?n:r?(At(r.value,e,n,void 0,r.shallow,r.mock),r.dep.notify(),n):(t[e]=n,n)}}function Lt(t,e){if(o(t)&&h(e))t.splice(e,1);else{var n=t.__ob__;t._isVue||n&&n.vmCount||Dt(t)||w(t,e)&&(delete t[e],n&&n.dep.notify())}}function Rt(t){for(var e=void 0,n=0,r=t.length;n<r;n++)(e=t[n])&&e.__ob__&&e.__ob__.dep.depend(),o(e)&&Rt(e)}function It(t){return Mt(t,!0),H(t,\"__v_isShallow\",!0),t}function Mt(t,e){if(!Dt(t)){Pt(t,e,it());0}}function Dt(t){return!(!t||!t.__v_isReadonly)}function Nt(t){return!(!t||!0!==t.__v_isRef)}function Ut(t,e,n){Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:function(){var t=e[n];if(Nt(t))return t.value;var r=t&&t.__ob__;return r&&r.dep.depend(),t},set:function(t){var r=e[n];Nt(r)&&!Nt(t)?r.value=t:e[n]=t}})}\"\".concat(\"watcher\",\" callback\"),\"\".concat(\"watcher\",\" getter\"),\"\".concat(\"watcher\",\" cleanup\");var Ft;var zt=function(){function t(t){void 0===t&&(t=!1),this.detached=t,this.active=!0,this.effects=[],this.cleanups=[],this.parent=Ft,!t&&Ft&&(this.index=(Ft.scopes||(Ft.scopes=[])).push(this)-1)}return t.prototype.run=function(t){if(this.active){var e=Ft;try{return Ft=this,t()}finally{Ft=e}}else 0},t.prototype.on=function(){Ft=this},t.prototype.off=function(){Ft=this.parent},t.prototype.stop=function(t){if(this.active){var e=void 0,n=void 0;for(e=0,n=this.effects.length;e<n;e++)this.effects[e].teardown();for(e=0,n=this.cleanups.length;e<n;e++)this.cleanups[e]();if(this.scopes)for(e=0,n=this.scopes.length;e<n;e++)this.scopes[e].stop(!0);if(!this.detached&&this.parent&&!t){var r=this.parent.scopes.pop();r&&r!==this&&(this.parent.scopes[this.index]=r,r.index=this.index)}this.parent=void 0,this.active=!1}},t}();function Bt(t){var e=t._provided,n=t.$parent&&t.$parent._provided;return n===e?t._provided=Object.create(n):e}var qt=k((function(t){var e=\"&\"===t.charAt(0),n=\"~\"===(t=e?t.slice(1):t).charAt(0),r=\"!\"===(t=n?t.slice(1):t).charAt(0);return{name:t=r?t.slice(1):t,once:n,capture:r,passive:e}}));function Vt(t,e){function n(){var t=n.fns;if(!o(t))return Oe(t,null,arguments,e,\"v-on handler\");for(var r=t.slice(),i=0;i<r.length;i++)Oe(r[i],null,arguments,e,\"v-on handler\")}return n.fns=t,n}function Ht(t,e,n,r,o,a){var u,l,c,f;for(u in t)l=t[u],c=e[u],f=qt(u),i(l)||(i(c)?(i(l.fns)&&(l=t[u]=Vt(l,a)),s(f.once)&&(l=t[u]=o(f.name,l,f.capture)),n(f.name,l,f.capture,f.passive,f.params)):l!==c&&(c.fns=l,t[u]=c));for(u in e)i(t[u])&&r((f=qt(u)).name,e[u],f.capture)}function Wt(t,e,n){var r;t instanceof pt&&(t=t.data.hook||(t.data.hook={}));var o=t[e];function u(){n.apply(this,arguments),_(r.fns,u)}i(o)?r=Vt([u]):a(o.fns)&&s(o.merged)?(r=o).fns.push(u):r=Vt([o,u]),r.merged=!0,t[e]=r}function Kt(t,e,n,r,o){if(a(e)){if(w(e,n))return t[n]=e[n],o||delete e[n],!0;if(w(e,r))return t[n]=e[r],o||delete e[r],!0}return!1}function Jt(t){return u(t)?[ht(t)]:o(t)?function t(e,n){var r,l,c,f,p=[];for(r=0;r<e.length;r++)i(l=e[r])||\"boolean\"==typeof l||(c=p.length-1,f=p[c],o(l)?l.length>0&&(Gt((l=t(l,\"\".concat(n||\"\",\"_\").concat(r)))[0])&&Gt(f)&&(p[c]=ht(f.text+l[0].text),l.shift()),p.push.apply(p,l)):u(l)?Gt(f)?p[c]=ht(f.text+l):\"\"!==l&&p.push(ht(l)):Gt(l)&&Gt(f)?p[c]=ht(f.text+l.text):(s(e._isVList)&&a(l.tag)&&i(l.key)&&a(n)&&(l.key=\"__vlist\".concat(n,\"_\").concat(r,\"__\")),p.push(l)));return p}(t):void 0}function Gt(t){return a(t)&&a(t.text)&&!1===t.isComment}function Qt(t,e){var n,r,i,s,u=null;if(o(t)||\"string\"==typeof t)for(u=new Array(t.length),n=0,r=t.length;n<r;n++)u[n]=e(t[n],n);else if(\"number\"==typeof t)for(u=new Array(t),n=0;n<t;n++)u[n]=e(n+1,n);else if(c(t))if(lt&&t[Symbol.iterator]){u=[];for(var l=t[Symbol.iterator](),f=l.next();!f.done;)u.push(e(f.value,u.length)),f=l.next()}else for(i=Object.keys(t),u=new Array(i.length),n=0,r=i.length;n<r;n++)s=i[n],u[n]=e(t[s],s,n);return a(u)||(u=[]),u._isVList=!0,u}function Xt(t,e,n,r){var o,i=this.$scopedSlots[t];i?(n=n||{},r&&(n=A(A({},r),n)),o=i(n)||(l(e)?e():e)):o=this.$slots[t]||(l(e)?e():e);var a=n&&n.slot;return a?this.$createElement(\"template\",{slot:a},o):o}function Yt(t){return Pn(this.$options,\"filters\",t,!0)||I}function Zt(t,e){return o(t)?-1===t.indexOf(e):t!==e}function te(t,e,n,r,o){var i=B.keyCodes[e]||n;return o&&r&&!B.keyCodes[e]?Zt(o,r):i?Zt(i,t):r?S(r)!==e:void 0===t}function ee(t,e,n,r,i){if(n)if(c(n)){o(n)&&(n=T(n));var a=void 0,s=function(o){if(\"class\"===o||\"style\"===o||b(o))a=t;else{var s=t.attrs&&t.attrs.type;a=r||B.mustUseProp(e,s,o)?t.domProps||(t.domProps={}):t.attrs||(t.attrs={})}var u=C(o),l=S(o);u in a||l in a||(a[o]=n[o],i&&((t.on||(t.on={}))[\"update:\".concat(o)]=function(t){n[o]=t}))};for(var u in n)s(u)}else;return t}function ne(t,e){var n=this._staticTrees||(this._staticTrees=[]),r=n[t];return r&&!e||oe(r=n[t]=this.$options.staticRenderFns[t].call(this._renderProxy,this._c,this),\"__static__\".concat(t),!1),r}function re(t,e,n){return oe(t,\"__once__\".concat(e).concat(n?\"_\".concat(n):\"\"),!0),t}function oe(t,e,n){if(o(t))for(var r=0;r<t.length;r++)t[r]&&\"string\"!=typeof t[r]&&ie(t[r],\"\".concat(e,\"_\").concat(r),n);else ie(t,e,n)}function ie(t,e,n){t.isStatic=!0,t.key=e,t.isOnce=n}function ae(t,e){if(e)if(p(e)){var n=t.on=t.on?A({},t.on):{};for(var r in e){var o=n[r],i=e[r];n[r]=o?[].concat(o,i):i}}else;return t}function se(t,e,n,r){e=e||{$stable:!n};for(var i=0;i<t.length;i++){var a=t[i];o(a)?se(a,e,n):a&&(a.proxy&&(a.fn.proxy=!0),e[a.key]=a.fn)}return r&&(e.$key=r),e}function ue(t,e){for(var n=0;n<e.length;n+=2){var r=e[n];\"string\"==typeof r&&r&&(t[e[n]]=e[n+1])}return t}function le(t,e){return\"string\"==typeof t?e+t:t}function ce(t){t._o=re,t._n=g,t._s=m,t._l=Qt,t._t=Xt,t._q=M,t._i=D,t._m=ne,t._f=Yt,t._k=te,t._b=ee,t._v=ht,t._e=dt,t._u=se,t._g=ae,t._d=ue,t._p=le}function fe(t,e){if(!t||!t.length)return{};for(var n={},r=0,o=t.length;r<o;r++){var i=t[r],a=i.data;if(a&&a.attrs&&a.attrs.slot&&delete a.attrs.slot,i.context!==e&&i.fnContext!==e||!a||null==a.slot)(n.default||(n.default=[])).push(i);else{var s=a.slot,u=n[s]||(n[s]=[]);\"template\"===i.tag?u.push.apply(u,i.children||[]):u.push(i)}}for(var l in n)n[l].every(pe)&&delete n[l];return n}function pe(t){return t.isComment&&!t.asyncFactory||\" \"===t.text}function de(t){return t.isComment&&t.asyncFactory}function he(t,e,n,o){var i,a=Object.keys(n).length>0,s=e?!!e.$stable:!a,u=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(s&&o&&o!==r&&u===o.$key&&!a&&!o.$hasNormal)return o;for(var l in i={},e)e[l]&&\"$\"!==l[0]&&(i[l]=ve(t,n,l,e[l]))}else i={};for(var c in n)c in i||(i[c]=me(n,c));return e&&Object.isExtensible(e)&&(e._normalized=i),H(i,\"$stable\",s),H(i,\"$key\",u),H(i,\"$hasNormal\",a),i}function ve(t,e,n,r){var i=function(){var e=ct;ft(t);var n=arguments.length?r.apply(null,arguments):r({}),i=(n=n&&\"object\"==typeof n&&!o(n)?[n]:Jt(n))&&n[0];return ft(e),n&&(!i||1===n.length&&i.isComment&&!de(i))?void 0:n};return r.proxy&&Object.defineProperty(e,n,{get:i,enumerable:!0,configurable:!0}),i}function me(t,e){return function(){return t[e]}}function ge(t){return{get attrs(){if(!t._attrsProxy){var e=t._attrsProxy={};H(e,\"_v_attr_proxy\",!0),ye(e,t.$attrs,r,t,\"$attrs\")}return t._attrsProxy},get listeners(){t._listenersProxy||ye(t._listenersProxy={},t.$listeners,r,t,\"$listeners\");return t._listenersProxy},get slots(){return function(t){t._slotsProxy||_e(t._slotsProxy={},t.$scopedSlots);return t._slotsProxy}(t)},emit:E(t.$emit,t),expose:function(e){e&&Object.keys(e).forEach((function(n){return Ut(t,e,n)}))}}}function ye(t,e,n,r,o){var i=!1;for(var a in e)a in t?e[a]!==n[a]&&(i=!0):(i=!0,be(t,a,r,o));for(var a in t)a in e||(i=!0,delete t[a]);return i}function be(t,e,n,r){Object.defineProperty(t,e,{enumerable:!0,configurable:!0,get:function(){return n[r][e]}})}function _e(t,e){for(var n in e)t[n]=e[n];for(var n in t)n in e||delete t[n]}var xe=null;function we(t,e){return(t.__esModule||lt&&\"Module\"===t[Symbol.toStringTag])&&(t=t.default),c(t)?e.extend(t):t}function ke(t){if(o(t))for(var e=0;e<t.length;e++){var n=t[e];if(a(n)&&(a(n.componentOptions)||de(n)))return n}}function $e(t,e,n,r,f,p){return(o(n)||u(n))&&(f=r,r=n,n=void 0),s(p)&&(f=2),function(t,e,n,r,u){if(a(n)&&a(n.__ob__))return dt();a(n)&&a(n.is)&&(e=n.is);if(!e)return dt();0;o(r)&&l(r[0])&&((n=n||{}).scopedSlots={default:r[0]},r.length=0);2===u?r=Jt(r):1===u&&(r=function(t){for(var e=0;e<t.length;e++)if(o(t[e]))return Array.prototype.concat.apply([],t);return t}(r));var f,p;if(\"string\"==typeof e){var d=void 0;p=t.$vnode&&t.$vnode.ns||B.getTagNamespace(e),f=B.isReservedTag(e)?new pt(B.parsePlatformTagName(e),n,r,void 0,void 0,t):n&&n.pre||!a(d=Pn(t.$options,\"components\",e))?new pt(e,n,r,void 0,void 0,t):_n(d,n,t,r,e)}else f=_n(e,n,t,r);return o(f)?f:a(f)?(a(p)&&function t(e,n,r){e.ns=n,\"foreignObject\"===e.tag&&(n=void 0,r=!0);if(a(e.children))for(var o=0,u=e.children.length;o<u;o++){var l=e.children[o];a(l.tag)&&(i(l.ns)||s(r)&&\"svg\"!==l.tag)&&t(l,n,r)}}(f,p),a(n)&&function(t){c(t.style)&&ze(t.style);c(t.class)&&ze(t.class)}(n),f):dt()}(t,e,n,r,f)}function Ce(t,e,n){_t();try{if(e)for(var r=e;r=r.$parent;){var o=r.$options.errorCaptured;if(o)for(var i=0;i<o.length;i++)try{if(!1===o[i].call(r,t,e,n))return}catch(t){je(t,r,\"errorCaptured hook\")}}je(t,e,n)}finally{xt()}}function Oe(t,e,n,r,o){var i;try{(i=n?t.apply(e,n):t.call(e))&&!i._isVue&&v(i)&&!i._handled&&(i.catch((function(t){return Ce(t,r,o+\" (Promise/async)\")})),i._handled=!0)}catch(t){Ce(t,r,o)}return i}function je(t,e,n){if(B.errorHandler)try{return B.errorHandler.call(null,t,e,n)}catch(e){e!==t&&Se(e,null,\"config.errorHandler\")}Se(t,e,n)}function Se(t,e,n){if(!J||\"undefined\"==typeof console)throw t;console.error(t)}var Ee,Pe=!1,Ae=[],Te=!1;function Le(){Te=!1;var t=Ae.slice(0);Ae.length=0;for(var e=0;e<t.length;e++)t[e]()}if(\"undefined\"!=typeof Promise&&st(Promise)){var Re=Promise.resolve();Ee=function(){Re.then(Le),Z&&setTimeout(L)},Pe=!0}else if(Q||\"undefined\"==typeof MutationObserver||!st(MutationObserver)&&\"[object MutationObserverConstructor]\"!==MutationObserver.toString())Ee=\"undefined\"!=typeof setImmediate&&st(setImmediate)?function(){setImmediate(Le)}:function(){setTimeout(Le,0)};else{var Ie=1,Me=new MutationObserver(Le),De=document.createTextNode(String(Ie));Me.observe(De,{characterData:!0}),Ee=function(){Ie=(Ie+1)%2,De.data=String(Ie)},Pe=!0}function Ne(t,e){var n;if(Ae.push((function(){if(t)try{t.call(e)}catch(t){Ce(t,e,\"nextTick\")}else n&&n(e)})),Te||(Te=!0,Ee()),!t&&\"undefined\"!=typeof Promise)return new Promise((function(t){n=t}))}function Ue(t){return function(e,n){if(void 0===n&&(n=ct),n)return function(t,e,n){var r=t.$options;r[e]=On(r[e],n)}(n,t,e)}}Ue(\"beforeMount\"),Ue(\"mounted\"),Ue(\"beforeUpdate\"),Ue(\"updated\"),Ue(\"beforeDestroy\"),Ue(\"destroyed\"),Ue(\"activated\"),Ue(\"deactivated\"),Ue(\"serverPrefetch\"),Ue(\"renderTracked\"),Ue(\"renderTriggered\"),Ue(\"errorCaptured\");var Fe=new ut;function ze(t){return function t(e,n){var r,i,a=o(e);if(!a&&!c(e)||e.__v_skip||Object.isFrozen(e)||e instanceof pt)return;if(e.__ob__){var s=e.__ob__.dep.id;if(n.has(s))return;n.add(s)}if(a)for(r=e.length;r--;)t(e[r],n);else if(Nt(e))t(e.value,n);else for(i=Object.keys(e),r=i.length;r--;)t(e[i[r]],n)}(t,Fe),Fe.clear(),t}var Be,qe=0,Ve=function(){function t(t,e,n,r,o){var i,a;i=this,void 0===(a=Ft&&!Ft._vm?Ft:t?t._scope:void 0)&&(a=Ft),a&&a.active&&a.effects.push(i),(this.vm=t)&&o&&(t._watcher=this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++qe,this.active=!0,this.post=!1,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new ut,this.newDepIds=new ut,this.expression=\"\",l(e)?this.getter=e:(this.getter=function(t){if(!W.test(t)){var e=t.split(\".\");return function(t){for(var n=0;n<e.length;n++){if(!t)return;t=t[e[n]]}return t}}}(e),this.getter||(this.getter=L)),this.value=this.lazy?void 0:this.get()}return t.prototype.get=function(){var t;_t(this);var e=this.vm;try{t=this.getter.call(e,e)}catch(t){if(!this.user)throw t;Ce(t,e,'getter for watcher \"'.concat(this.expression,'\"'))}finally{this.deep&&ze(t),xt(),this.cleanupDeps()}return t},t.prototype.addDep=function(t){var e=t.id;this.newDepIds.has(e)||(this.newDepIds.add(e),this.newDeps.push(t),this.depIds.has(e)||t.addSub(this))},t.prototype.cleanupDeps=function(){for(var t=this.deps.length;t--;){var e=this.deps[t];this.newDepIds.has(e.id)||e.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},t.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():pn(this)},t.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||c(t)||this.deep){var e=this.value;if(this.value=t,this.user){var n='callback for watcher \"'.concat(this.expression,'\"');Oe(this.cb,this.vm,[t,e],this.vm,n)}else this.cb.call(this.vm,t,e)}}},t.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},t.prototype.depend=function(){for(var t=this.deps.length;t--;)this.deps[t].depend()},t.prototype.teardown=function(){if(this.vm&&!this.vm._isBeingDestroyed&&_(this.vm._scope.effects,this),this.active){for(var t=this.deps.length;t--;)this.deps[t].removeSub(this);this.active=!1,this.onStop&&this.onStop()}},t}();function He(t,e){Be.$on(t,e)}function We(t,e){Be.$off(t,e)}function Ke(t,e){var n=Be;return function r(){var o=e.apply(null,arguments);null!==o&&n.$off(t,r)}}function Je(t,e,n){Be=t,Ht(e,n||{},He,We,Ke,t),Be=void 0}var Ge=null;function Qe(t){var e=Ge;return Ge=t,function(){Ge=e}}function Xe(t){for(;t&&(t=t.$parent);)if(t._inactive)return!0;return!1}function Ye(t,e){if(e){if(t._directInactive=!1,Xe(t))return}else if(t._directInactive)return;if(t._inactive||null===t._inactive){t._inactive=!1;for(var n=0;n<t.$children.length;n++)Ye(t.$children[n]);Ze(t,\"activated\")}}function Ze(t,e,n,r){void 0===r&&(r=!0),_t();var o=ct;r&&ft(t);var i=t.$options[e],a=\"\".concat(e,\" hook\");if(i)for(var s=0,u=i.length;s<u;s++)Oe(i[s],t,n||null,t,a);t._hasHookEvent&&t.$emit(\"hook:\"+e),r&&ft(o),xt()}var tn=[],en=[],nn={},rn=!1,on=!1,an=0;var sn=0,un=Date.now;if(J&&!Q){var ln=window.performance;ln&&\"function\"==typeof ln.now&&un()>document.createEvent(\"Event\").timeStamp&&(un=function(){return ln.now()})}var cn=function(t,e){if(t.post){if(!e.post)return 1}else if(e.post)return-1;return t.id-e.id};function fn(){var t,e;for(sn=un(),on=!0,tn.sort(cn),an=0;an<tn.length;an++)(t=tn[an]).before&&t.before(),e=t.id,nn[e]=null,t.run();var n=en.slice(),r=tn.slice();an=tn.length=en.length=0,nn={},rn=on=!1,function(t){for(var e=0;e<t.length;e++)t[e]._inactive=!0,Ye(t[e],!0)}(n),function(t){var e=t.length;for(;e--;){var n=t[e],r=n.vm;r&&r._watcher===n&&r._isMounted&&!r._isDestroyed&&Ze(r,\"updated\")}}(r),function(){for(var t=0;t<gt.length;t++){var e=gt[t];e.subs=e.subs.filter((function(t){return t})),e._pending=!1}gt.length=0}(),at&&B.devtools&&at.emit(\"flush\")}function pn(t){var e=t.id;if(null==nn[e]&&(t!==yt.target||!t.noRecurse)){if(nn[e]=!0,on){for(var n=tn.length-1;n>an&&tn[n].id>t.id;)n--;tn.splice(n+1,0,t)}else tn.push(t);rn||(rn=!0,Ne(fn))}}function dn(t,e){if(t){for(var n=Object.create(null),r=lt?Reflect.ownKeys(t):Object.keys(t),o=0;o<r.length;o++){var i=r[o];if(\"__ob__\"!==i){var a=t[i].from;if(a in e._provided)n[i]=e._provided[a];else if(\"default\"in t[i]){var s=t[i].default;n[i]=l(s)?s.call(e):s}else 0}}return n}}function hn(t,e,n,i,a){var u,l=this,c=a.options;w(i,\"_uid\")?(u=Object.create(i))._original=i:(u=i,i=i._original);var f=s(c._compiled),p=!f;this.data=t,this.props=e,this.children=n,this.parent=i,this.listeners=t.on||r,this.injections=dn(c.inject,i),this.slots=function(){return l.$slots||he(i,t.scopedSlots,l.$slots=fe(n,i)),l.$slots},Object.defineProperty(this,\"scopedSlots\",{enumerable:!0,get:function(){return he(i,t.scopedSlots,this.slots())}}),f&&(this.$options=c,this.$slots=this.slots(),this.$scopedSlots=he(i,t.scopedSlots,this.$slots)),c._scopeId?this._c=function(t,e,n,r){var a=$e(u,t,e,n,r,p);return a&&!o(a)&&(a.fnScopeId=c._scopeId,a.fnContext=i),a}:this._c=function(t,e,n,r){return $e(u,t,e,n,r,p)}}function vn(t,e,n,r,o){var i=vt(t);return i.fnContext=n,i.fnOptions=r,e.slot&&((i.data||(i.data={})).slot=e.slot),i}function mn(t,e){for(var n in e)t[C(n)]=e[n]}function gn(t){return t.name||t.__name||t._componentTag}ce(hn.prototype);var yn={init:function(t,e){if(t.componentInstance&&!t.componentInstance._isDestroyed&&t.data.keepAlive){var n=t;yn.prepatch(n,n)}else{(t.componentInstance=function(t,e){var n={_isComponent:!0,_parentVnode:t,parent:e},r=t.data.inlineTemplate;a(r)&&(n.render=r.render,n.staticRenderFns=r.staticRenderFns);return new t.componentOptions.Ctor(n)}(t,Ge)).$mount(e?t.elm:void 0,e)}},prepatch:function(t,e){var n=e.componentOptions;!function(t,e,n,o,i){var a=o.data.scopedSlots,s=t.$scopedSlots,u=!!(a&&!a.$stable||s!==r&&!s.$stable||a&&t.$scopedSlots.$key!==a.$key||!a&&t.$scopedSlots.$key),l=!!(i||t.$options._renderChildren||u),c=t.$vnode;t.$options._parentVnode=o,t.$vnode=o,t._vnode&&(t._vnode.parent=o),t.$options._renderChildren=i;var f=o.data.attrs||r;t._attrsProxy&&ye(t._attrsProxy,f,c.data&&c.data.attrs||r,t,\"$attrs\")&&(l=!0),t.$attrs=f,n=n||r;var p=t.$options._parentListeners;if(t._listenersProxy&&ye(t._listenersProxy,n,p||r,t,\"$listeners\"),t.$listeners=t.$options._parentListeners=n,Je(t,n,p),e&&t.$options.props){jt(!1);for(var d=t._props,h=t.$options._propKeys||[],v=0;v<h.length;v++){var m=h[v],g=t.$options.props;d[m]=An(m,g,e,t)}jt(!0),t.$options.propsData=e}l&&(t.$slots=fe(i,o.context),t.$forceUpdate())}(e.componentInstance=t.componentInstance,n.propsData,n.listeners,e,n.children)},insert:function(t){var e,n=t.context,r=t.componentInstance;r._isMounted||(r._isMounted=!0,Ze(r,\"mounted\")),t.data.keepAlive&&(n._isMounted?((e=r)._inactive=!1,en.push(e)):Ye(r,!0))},destroy:function(t){var e=t.componentInstance;e._isDestroyed||(t.data.keepAlive?function t(e,n){if(!(n&&(e._directInactive=!0,Xe(e))||e._inactive)){e._inactive=!0;for(var r=0;r<e.$children.length;r++)t(e.$children[r]);Ze(e,\"deactivated\")}}(e,!0):e.$destroy())}},bn=Object.keys(yn);function _n(t,e,n,u,l){if(!i(t)){var f=n.$options._base;if(c(t)&&(t=f.extend(t)),\"function\"==typeof t){var p;if(i(t.cid)&&void 0===(t=function(t,e){if(s(t.error)&&a(t.errorComp))return t.errorComp;if(a(t.resolved))return t.resolved;var n=xe;if(n&&a(t.owners)&&-1===t.owners.indexOf(n)&&t.owners.push(n),s(t.loading)&&a(t.loadingComp))return t.loadingComp;if(n&&!a(t.owners)){var r=t.owners=[n],o=!0,u=null,l=null;n.$on(\"hook:destroyed\",(function(){return _(r,n)}));var f=function(t){for(var e=0,n=r.length;e<n;e++)r[e].$forceUpdate();t&&(r.length=0,null!==u&&(clearTimeout(u),u=null),null!==l&&(clearTimeout(l),l=null))},p=N((function(n){t.resolved=we(n,e),o?r.length=0:f(!0)})),d=N((function(e){a(t.errorComp)&&(t.error=!0,f(!0))})),h=t(p,d);return c(h)&&(v(h)?i(t.resolved)&&h.then(p,d):v(h.component)&&(h.component.then(p,d),a(h.error)&&(t.errorComp=we(h.error,e)),a(h.loading)&&(t.loadingComp=we(h.loading,e),0===h.delay?t.loading=!0:u=setTimeout((function(){u=null,i(t.resolved)&&i(t.error)&&(t.loading=!0,f(!1))}),h.delay||200)),a(h.timeout)&&(l=setTimeout((function(){l=null,i(t.resolved)&&d(null)}),h.timeout)))),o=!1,t.loading?t.loadingComp:t.resolved}}(p=t,f)))return function(t,e,n,r,o){var i=dt();return i.asyncFactory=t,i.asyncMeta={data:e,context:n,children:r,tag:o},i}(p,e,n,u,l);e=e||{},Hn(t),a(e.model)&&function(t,e){var n=t.model&&t.model.prop||\"value\",r=t.model&&t.model.event||\"input\";(e.attrs||(e.attrs={}))[n]=e.model.value;var i=e.on||(e.on={}),s=i[r],u=e.model.callback;a(s)?(o(s)?-1===s.indexOf(u):s!==u)&&(i[r]=[u].concat(s)):i[r]=u}(t.options,e);var d=function(t,e,n){var r=e.options.props;if(!i(r)){var o={},s=t.attrs,u=t.props;if(a(s)||a(u))for(var l in r){var c=S(l);Kt(o,u,l,c,!0)||Kt(o,s,l,c,!1)}return o}}(e,t);if(s(t.options.functional))return function(t,e,n,i,s){var u=t.options,l={},c=u.props;if(a(c))for(var f in c)l[f]=An(f,c,e||r);else a(n.attrs)&&mn(l,n.attrs),a(n.props)&&mn(l,n.props);var p=new hn(n,l,s,i,t),d=u.render.call(null,p._c,p);if(d instanceof pt)return vn(d,n,p.parent,u,p);if(o(d)){for(var h=Jt(d)||[],v=new Array(h.length),m=0;m<h.length;m++)v[m]=vn(h[m],n,p.parent,u,p);return v}}(t,d,e,n,u);var h=e.on;if(e.on=e.nativeOn,s(t.options.abstract)){var m=e.slot;e={},m&&(e.slot=m)}!function(t){for(var e=t.hook||(t.hook={}),n=0;n<bn.length;n++){var r=bn[n],o=e[r],i=yn[r];o===i||o&&o._merged||(e[r]=o?xn(i,o):i)}}(e);var g=gn(t.options)||l;return new pt(\"vue-component-\".concat(t.cid).concat(g?\"-\".concat(g):\"\"),e,void 0,void 0,void 0,n,{Ctor:t,propsData:d,listeners:h,tag:l,children:u},p)}}}function xn(t,e){var n=function(n,r){t(n,r),e(n,r)};return n._merged=!0,n}var wn=L,kn=B.optionMergeStrategies;function $n(t,e,n){if(void 0===n&&(n=!0),!e)return t;for(var r,o,i,a=lt?Reflect.ownKeys(e):Object.keys(e),s=0;s<a.length;s++)\"__ob__\"!==(r=a[s])&&(o=t[r],i=e[r],n&&w(t,r)?o!==i&&p(o)&&p(i)&&$n(o,i):Tt(t,r,i));return t}function Cn(t,e,n){return n?function(){var r=l(e)?e.call(n,n):e,o=l(t)?t.call(n,n):t;return r?$n(r,o):o}:e?t?function(){return $n(l(e)?e.call(this,this):e,l(t)?t.call(this,this):t)}:e:t}function On(t,e){var n=e?t?t.concat(e):o(e)?e:[e]:t;return n?function(t){for(var e=[],n=0;n<t.length;n++)-1===e.indexOf(t[n])&&e.push(t[n]);return e}(n):n}function jn(t,e,n,r){var o=Object.create(t||null);return e?A(o,e):o}kn.data=function(t,e,n){return n?Cn(t,e,n):e&&\"function\"!=typeof e?t:Cn(t,e)},z.forEach((function(t){kn[t]=On})),F.forEach((function(t){kn[t+\"s\"]=jn})),kn.watch=function(t,e,n,r){if(t===nt&&(t=void 0),e===nt&&(e=void 0),!e)return Object.create(t||null);if(!t)return e;var i={};for(var a in A(i,t),e){var s=i[a],u=e[a];s&&!o(s)&&(s=[s]),i[a]=s?s.concat(u):o(u)?u:[u]}return i},kn.props=kn.methods=kn.inject=kn.computed=function(t,e,n,r){if(!t)return e;var o=Object.create(null);return A(o,t),e&&A(o,e),o},kn.provide=function(t,e){return t?function(){var n=Object.create(null);return $n(n,l(t)?t.call(this):t),e&&$n(n,l(e)?e.call(this):e,!1),n}:e};var Sn=function(t,e){return void 0===e?t:e};function En(t,e,n){if(l(e)&&(e=e.options),function(t,e){var n=t.props;if(n){var r,i,a={};if(o(n))for(r=n.length;r--;)\"string\"==typeof(i=n[r])&&(a[C(i)]={type:null});else if(p(n))for(var s in n)i=n[s],a[C(s)]=p(i)?i:{type:i};else 0;t.props=a}}(e),function(t,e){var n=t.inject;if(n){var r=t.inject={};if(o(n))for(var i=0;i<n.length;i++)r[n[i]]={from:n[i]};else if(p(n))for(var a in n){var s=n[a];r[a]=p(s)?A({from:a},s):{from:s}}else 0}}(e),function(t){var e=t.directives;if(e)for(var n in e){var r=e[n];l(r)&&(e[n]={bind:r,update:r})}}(e),!e._base&&(e.extends&&(t=En(t,e.extends,n)),e.mixins))for(var r=0,i=e.mixins.length;r<i;r++)t=En(t,e.mixins[r],n);var a,s={};for(a in t)u(a);for(a in e)w(t,a)||u(a);function u(r){var o=kn[r]||Sn;s[r]=o(t[r],e[r],n,r)}return s}function Pn(t,e,n,r){if(\"string\"==typeof n){var o=t[e];if(w(o,n))return o[n];var i=C(n);if(w(o,i))return o[i];var a=O(i);return w(o,a)?o[a]:o[n]||o[i]||o[a]}}function An(t,e,n,r){var o=e[t],i=!w(n,t),a=n[t],s=In(Boolean,o.type);if(s>-1)if(i&&!w(o,\"default\"))a=!1;else if(\"\"===a||a===S(t)){var u=In(String,o.type);(u<0||s<u)&&(a=!0)}if(void 0===a){a=function(t,e,n){if(!w(e,\"default\"))return;var r=e.default;0;if(t&&t.$options.propsData&&void 0===t.$options.propsData[n]&&void 0!==t._props[n])return t._props[n];return l(r)&&\"Function\"!==Ln(e.type)?r.call(t):r}(r,o,t);var c=Ot;jt(!0),Pt(a),jt(c)}return a}var Tn=/^\\s*function (\\w+)/;function Ln(t){var e=t&&t.toString().match(Tn);return e?e[1]:\"\"}function Rn(t,e){return Ln(t)===Ln(e)}function In(t,e){if(!o(e))return Rn(e,t)?0:-1;for(var n=0,r=e.length;n<r;n++)if(Rn(e[n],t))return n;return-1}var Mn={enumerable:!0,configurable:!0,get:L,set:L};function Dn(t,e,n){Mn.get=function(){return this[e][n]},Mn.set=function(t){this[e][n]=t},Object.defineProperty(t,n,Mn)}function Nn(t){var e=t.$options;if(e.props&&function(t,e){var n=t.$options.propsData||{},r=t._props=It({}),o=t.$options._propKeys=[];t.$parent&&jt(!1);var i=function(i){o.push(i);var a=An(i,e,n,t);At(r,i,a),i in t||Dn(t,\"_props\",i)};for(var a in e)i(a);jt(!0)}(t,e.props),function(t){var e=t.$options,n=e.setup;if(n){var r=t._setupContext=ge(t);ft(t),_t();var o=Oe(n,null,[t._props||It({}),r],t,\"setup\");if(xt(),ft(),l(o))e.render=o;else if(c(o))if(t._setupState=o,o.__sfc){var i=t._setupProxy={};for(var a in o)\"__sfc\"!==a&&Ut(i,o,a)}else for(var a in o)V(a)||Ut(t,o,a);else 0}}(t),e.methods&&function(t,e){t.$options.props;for(var n in e)t[n]=\"function\"!=typeof e[n]?L:E(e[n],t)}(t,e.methods),e.data)!function(t){var e=t.$options.data;p(e=t._data=l(e)?function(t,e){_t();try{return t.call(e,e)}catch(t){return Ce(t,e,\"data()\"),{}}finally{xt()}}(e,t):e||{})||(e={});var n=Object.keys(e),r=t.$options.props,o=(t.$options.methods,n.length);for(;o--;){var i=n[o];0,r&&w(r,i)||V(i)||Dn(t,\"_data\",i)}var a=Pt(e);a&&a.vmCount++}(t);else{var n=Pt(t._data={});n&&n.vmCount++}e.computed&&function(t,e){var n=t._computedWatchers=Object.create(null),r=it();for(var o in e){var i=e[o],a=l(i)?i:i.get;0,r||(n[o]=new Ve(t,a||L,L,Un)),o in t||Fn(t,o,i)}}(t,e.computed),e.watch&&e.watch!==nt&&function(t,e){for(var n in e){var r=e[n];if(o(r))for(var i=0;i<r.length;i++)qn(t,n,r[i]);else qn(t,n,r)}}(t,e.watch)}var Un={lazy:!0};function Fn(t,e,n){var r=!it();l(n)?(Mn.get=r?zn(e):Bn(n),Mn.set=L):(Mn.get=n.get?r&&!1!==n.cache?zn(e):Bn(n.get):L,Mn.set=n.set||L),Object.defineProperty(t,e,Mn)}function zn(t){return function(){var e=this._computedWatchers&&this._computedWatchers[t];if(e)return e.dirty&&e.evaluate(),yt.target&&e.depend(),e.value}}function Bn(t){return function(){return t.call(this,this)}}function qn(t,e,n,r){return p(n)&&(r=n,n=n.handler),\"string\"==typeof n&&(n=t[n]),t.$watch(e,n,r)}var Vn=0;function Hn(t){var e=t.options;if(t.super){var n=Hn(t.super);if(n!==t.superOptions){t.superOptions=n;var r=function(t){var e,n=t.options,r=t.sealedOptions;for(var o in n)n[o]!==r[o]&&(e||(e={}),e[o]=n[o]);return e}(t);r&&A(t.extendOptions,r),(e=t.options=En(n,t.extendOptions)).name&&(e.components[e.name]=t)}}return e}function Wn(t){this._init(t)}function Kn(t){t.cid=0;var e=1;t.extend=function(t){t=t||{};var n=this,r=n.cid,o=t._Ctor||(t._Ctor={});if(o[r])return o[r];var i=gn(t)||gn(n.options);var a=function(t){this._init(t)};return(a.prototype=Object.create(n.prototype)).constructor=a,a.cid=e++,a.options=En(n.options,t),a.super=n,a.options.props&&function(t){var e=t.options.props;for(var n in e)Dn(t.prototype,\"_props\",n)}(a),a.options.computed&&function(t){var e=t.options.computed;for(var n in e)Fn(t.prototype,n,e[n])}(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,F.forEach((function(t){a[t]=n[t]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=t,a.sealedOptions=A({},a.options),o[r]=a,a}}function Jn(t){return t&&(gn(t.Ctor.options)||t.tag)}function Gn(t,e){return o(t)?t.indexOf(e)>-1:\"string\"==typeof t?t.split(\",\").indexOf(e)>-1:!!d(t)&&t.test(e)}function Qn(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=a.name;s&&!e(s)&&Xn(n,i,r,o)}}}function Xn(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,_(n,e)}Wn.prototype._init=function(t){var e=this;e._uid=Vn++,e._isVue=!0,e.__v_skip=!0,e._scope=new zt(!0),e._scope._vm=!0,t&&t._isComponent?function(t,e){var n=t.$options=Object.create(t.constructor.options),r=e._parentVnode;n.parent=e.parent,n._parentVnode=r;var o=r.componentOptions;n.propsData=o.propsData,n._parentListeners=o.listeners,n._renderChildren=o.children,n._componentTag=o.tag,e.render&&(n.render=e.render,n.staticRenderFns=e.staticRenderFns)}(e,t):e.$options=En(Hn(e.constructor),t||{},e),e._renderProxy=e,e._self=e,function(t){var e=t.$options,n=e.parent;if(n&&!e.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(t)}t.$parent=n,t.$root=n?n.$root:t,t.$children=[],t.$refs={},t._provided=n?n._provided:Object.create(null),t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(e),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&Je(t,e)}(e),function(t){t._vnode=null,t._staticTrees=null;var e=t.$options,n=t.$vnode=e._parentVnode,o=n&&n.context;t.$slots=fe(e._renderChildren,o),t.$scopedSlots=n?he(t.$parent,n.data.scopedSlots,t.$slots):r,t._c=function(e,n,r,o){return $e(t,e,n,r,o,!1)},t.$createElement=function(e,n,r,o){return $e(t,e,n,r,o,!0)};var i=n&&n.data;At(t,\"$attrs\",i&&i.attrs||r,null,!0),At(t,\"$listeners\",e._parentListeners||r,null,!0)}(e),Ze(e,\"beforeCreate\",void 0,!1),function(t){var e=dn(t.$options.inject,t);e&&(jt(!1),Object.keys(e).forEach((function(n){At(t,n,e[n])})),jt(!0))}(e),Nn(e),function(t){var e=t.$options.provide;if(e){var n=l(e)?e.call(t):e;if(!c(n))return;for(var r=Bt(t),o=lt?Reflect.ownKeys(n):Object.keys(n),i=0;i<o.length;i++){var a=o[i];Object.defineProperty(r,a,Object.getOwnPropertyDescriptor(n,a))}}}(e),Ze(e,\"created\"),e.$options.el&&e.$mount(e.$options.el)},function(t){var e={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(t.prototype,\"$data\",e),Object.defineProperty(t.prototype,\"$props\",n),t.prototype.$set=Tt,t.prototype.$delete=Lt,t.prototype.$watch=function(t,e,n){if(p(e))return qn(this,t,e,n);(n=n||{}).user=!0;var r=new Ve(this,t,e,n);if(n.immediate){var o='callback for immediate watcher \"'.concat(r.expression,'\"');_t(),Oe(e,this,[r.value],this,o),xt()}return function(){r.teardown()}}}(Wn),function(t){var e=/^hook:/;t.prototype.$on=function(t,n){var r=this;if(o(t))for(var i=0,a=t.length;i<a;i++)r.$on(t[i],n);else(r._events[t]||(r._events[t]=[])).push(n),e.test(t)&&(r._hasHookEvent=!0);return r},t.prototype.$once=function(t,e){var n=this;function r(){n.$off(t,r),e.apply(n,arguments)}return r.fn=e,n.$on(t,r),n},t.prototype.$off=function(t,e){var n=this;if(!arguments.length)return n._events=Object.create(null),n;if(o(t)){for(var r=0,i=t.length;r<i;r++)n.$off(t[r],e);return n}var a,s=n._events[t];if(!s)return n;if(!e)return n._events[t]=null,n;for(var u=s.length;u--;)if((a=s[u])===e||a.fn===e){s.splice(u,1);break}return n},t.prototype.$emit=function(t){var e=this,n=e._events[t];if(n){n=n.length>1?P(n):n;for(var r=P(arguments,1),o='event handler for \"'.concat(t,'\"'),i=0,a=n.length;i<a;i++)Oe(n[i],e,r,e,o)}return e}}(Wn),function(t){t.prototype._update=function(t,e){var n=this,r=n.$el,o=n._vnode,i=Qe(n);n._vnode=t,n.$el=o?n.__patch__(o,t):n.__patch__(n.$el,t,e,!1),i(),r&&(r.__vue__=null),n.$el&&(n.$el.__vue__=n);for(var a=n;a&&a.$vnode&&a.$parent&&a.$vnode===a.$parent._vnode;)a.$parent.$el=a.$el,a=a.$parent},t.prototype.$forceUpdate=function(){this._watcher&&this._watcher.update()},t.prototype.$destroy=function(){var t=this;if(!t._isBeingDestroyed){Ze(t,\"beforeDestroy\"),t._isBeingDestroyed=!0;var e=t.$parent;!e||e._isBeingDestroyed||t.$options.abstract||_(e.$children,t),t._scope.stop(),t._data.__ob__&&t._data.__ob__.vmCount--,t._isDestroyed=!0,t.__patch__(t._vnode,null),Ze(t,\"destroyed\"),t.$off(),t.$el&&(t.$el.__vue__=null),t.$vnode&&(t.$vnode.parent=null)}}}(Wn),function(t){ce(t.prototype),t.prototype.$nextTick=function(t){return Ne(t,this)},t.prototype._render=function(){var t,e=this,n=e.$options,r=n.render,i=n._parentVnode;i&&e._isMounted&&(e.$scopedSlots=he(e.$parent,i.data.scopedSlots,e.$slots,e.$scopedSlots),e._slotsProxy&&_e(e._slotsProxy,e.$scopedSlots)),e.$vnode=i;try{ft(e),xe=e,t=r.call(e._renderProxy,e.$createElement)}catch(n){Ce(n,e,\"render\"),t=e._vnode}finally{xe=null,ft()}return o(t)&&1===t.length&&(t=t[0]),t instanceof pt||(t=dt()),t.parent=i,t}}(Wn);var Yn=[String,RegExp,Array],Zn={KeepAlive:{name:\"keep-alive\",abstract:!0,props:{include:Yn,exclude:Yn,max:[String,Number]},methods:{cacheVNode:function(){var t=this.cache,e=this.keys,n=this.vnodeToCache,r=this.keyToCache;if(n){var o=n.tag,i=n.componentInstance,a=n.componentOptions;t[r]={name:Jn(a),tag:o,componentInstance:i},e.push(r),this.max&&e.length>parseInt(this.max)&&Xn(t,e[0],e,this._vnode),this.vnodeToCache=null}}},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)Xn(this.cache,t,this.keys)},mounted:function(){var t=this;this.cacheVNode(),this.$watch(\"include\",(function(e){Qn(t,(function(t){return Gn(e,t)}))})),this.$watch(\"exclude\",(function(e){Qn(t,(function(t){return!Gn(e,t)}))}))},updated:function(){this.cacheVNode()},render:function(){var t=this.$slots.default,e=ke(t),n=e&&e.componentOptions;if(n){var r=Jn(n),o=this.include,i=this.exclude;if(o&&(!r||!Gn(o,r))||i&&r&&Gn(i,r))return e;var a=this.cache,s=this.keys,u=null==e.key?n.Ctor.cid+(n.tag?\"::\".concat(n.tag):\"\"):e.key;a[u]?(e.componentInstance=a[u].componentInstance,_(s,u),s.push(u)):(this.vnodeToCache=e,this.keyToCache=u),e.data.keepAlive=!0}return e||t&&t[0]}}};!function(t){var e={get:function(){return B}};Object.defineProperty(t,\"config\",e),t.util={warn:wn,extend:A,mergeOptions:En,defineReactive:At},t.set=Tt,t.delete=Lt,t.nextTick=Ne,t.observable=function(t){return Pt(t),t},t.options=Object.create(null),F.forEach((function(e){t.options[e+\"s\"]=Object.create(null)})),t.options._base=t,A(t.options.components,Zn),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var n=P(arguments,1);return n.unshift(this),l(t.install)?t.install.apply(t,n):l(t)&&t.apply(null,n),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=En(this.options,t),this}}(t),Kn(t),function(t){F.forEach((function(e){t[e]=function(t,n){return n?(\"component\"===e&&p(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),\"directive\"===e&&l(n)&&(n={bind:n,update:n}),this.options[e+\"s\"][t]=n,n):this.options[e+\"s\"][t]}}))}(t)}(Wn),Object.defineProperty(Wn.prototype,\"$isServer\",{get:it}),Object.defineProperty(Wn.prototype,\"$ssrContext\",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Wn,\"FunctionalRenderContext\",{value:hn}),Wn.version=\"2.7.14\";var tr=y(\"style,class\"),er=y(\"input,textarea,option,select,progress\"),nr=y(\"contenteditable,draggable,spellcheck\"),rr=y(\"events,caret,typing,plaintext-only\"),or=y(\"allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible\"),ir=\"http://www.w3.org/1999/xlink\",ar=function(t){return\":\"===t.charAt(5)&&\"xlink\"===t.slice(0,5)},sr=function(t){return ar(t)?t.slice(6,t.length):\"\"},ur=function(t){return null==t||!1===t};function lr(t){for(var e=t.data,n=t,r=t;a(r.componentInstance);)(r=r.componentInstance._vnode)&&r.data&&(e=cr(r.data,e));for(;a(n=n.parent);)n&&n.data&&(e=cr(e,n.data));return function(t,e){if(a(t)||a(e))return fr(t,pr(e));return\"\"}(e.staticClass,e.class)}function cr(t,e){return{staticClass:fr(t.staticClass,e.staticClass),class:a(t.class)?[t.class,e.class]:e.class}}function fr(t,e){return t?e?t+\" \"+e:t:e||\"\"}function pr(t){return Array.isArray(t)?function(t){for(var e,n=\"\",r=0,o=t.length;r<o;r++)a(e=pr(t[r]))&&\"\"!==e&&(n&&(n+=\" \"),n+=e);return n}(t):c(t)?function(t){var e=\"\";for(var n in t)t[n]&&(e&&(e+=\" \"),e+=n);return e}(t):\"string\"==typeof t?t:\"\"}var dr={svg:\"http://www.w3.org/2000/svg\",math:\"http://www.w3.org/1998/Math/MathML\"},hr=y(\"html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,menuitem,summary,content,element,shadow,template,blockquote,iframe,tfoot\"),vr=y(\"svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view\",!0),mr=function(t){return hr(t)||vr(t)};var gr=Object.create(null);var yr=y(\"text,number,password,search,email,tel,url\");var br=Object.freeze({__proto__:null,createElement:function(t,e){var n=document.createElement(t);return\"select\"!==t||e.data&&e.data.attrs&&void 0!==e.data.attrs.multiple&&n.setAttribute(\"multiple\",\"multiple\"),n},createElementNS:function(t,e){return document.createElementNS(dr[t],e)},createTextNode:function(t){return document.createTextNode(t)},createComment:function(t){return document.createComment(t)},insertBefore:function(t,e,n){t.insertBefore(e,n)},removeChild:function(t,e){t.removeChild(e)},appendChild:function(t,e){t.appendChild(e)},parentNode:function(t){return t.parentNode},nextSibling:function(t){return t.nextSibling},tagName:function(t){return t.tagName},setTextContent:function(t,e){t.textContent=e},setStyleScope:function(t,e){t.setAttribute(e,\"\")}}),_r={create:function(t,e){xr(e)},update:function(t,e){t.data.ref!==e.data.ref&&(xr(t,!0),xr(e))},destroy:function(t){xr(t,!0)}};function xr(t,e){var n=t.data.ref;if(a(n)){var r=t.context,i=t.componentInstance||t.elm,s=e?null:i,u=e?void 0:i;if(l(n))Oe(n,r,[s],r,\"template ref function\");else{var c=t.data.refInFor,f=\"string\"==typeof n||\"number\"==typeof n,p=Nt(n),d=r.$refs;if(f||p)if(c){var h=f?d[n]:n.value;e?o(h)&&_(h,i):o(h)?h.includes(i)||h.push(i):f?(d[n]=[i],wr(r,n,d[n])):n.value=[i]}else if(f){if(e&&d[n]!==i)return;d[n]=u,wr(r,n,s)}else if(p){if(e&&n.value!==i)return;n.value=s}else 0}}}function wr(t,e,n){var r=t._setupState;r&&w(r,e)&&(Nt(r[e])?r[e].value=n:r[e]=n)}var kr=new pt(\"\",{},[]),$r=[\"create\",\"activate\",\"update\",\"remove\",\"destroy\"];function Cr(t,e){return t.key===e.key&&t.asyncFactory===e.asyncFactory&&(t.tag===e.tag&&t.isComment===e.isComment&&a(t.data)===a(e.data)&&function(t,e){if(\"input\"!==t.tag)return!0;var n,r=a(n=t.data)&&a(n=n.attrs)&&n.type,o=a(n=e.data)&&a(n=n.attrs)&&n.type;return r===o||yr(r)&&yr(o)}(t,e)||s(t.isAsyncPlaceholder)&&i(e.asyncFactory.error))}function Or(t,e,n){var r,o,i={};for(r=e;r<=n;++r)a(o=t[r].key)&&(i[o]=r);return i}var jr={create:Sr,update:Sr,destroy:function(t){Sr(t,kr)}};function Sr(t,e){(t.data.directives||e.data.directives)&&function(t,e){var n,r,o,i=t===kr,a=e===kr,s=Pr(t.data.directives,t.context),u=Pr(e.data.directives,e.context),l=[],c=[];for(n in u)r=s[n],o=u[n],r?(o.oldValue=r.value,o.oldArg=r.arg,Tr(o,\"update\",e,t),o.def&&o.def.componentUpdated&&c.push(o)):(Tr(o,\"bind\",e,t),o.def&&o.def.inserted&&l.push(o));if(l.length){var f=function(){for(var n=0;n<l.length;n++)Tr(l[n],\"inserted\",e,t)};i?Wt(e,\"insert\",f):f()}c.length&&Wt(e,\"postpatch\",(function(){for(var n=0;n<c.length;n++)Tr(c[n],\"componentUpdated\",e,t)}));if(!i)for(n in s)u[n]||Tr(s[n],\"unbind\",t,t,a)}(t,e)}var Er=Object.create(null);function Pr(t,e){var n,r,o=Object.create(null);if(!t)return o;for(n=0;n<t.length;n++){if((r=t[n]).modifiers||(r.modifiers=Er),o[Ar(r)]=r,e._setupState&&e._setupState.__sfc){var i=r.def||Pn(e,\"_setupState\",\"v-\"+r.name);r.def=\"function\"==typeof i?{bind:i,update:i}:i}r.def=r.def||Pn(e.$options,\"directives\",r.name)}return o}function Ar(t){return t.rawName||\"\".concat(t.name,\".\").concat(Object.keys(t.modifiers||{}).join(\".\"))}function Tr(t,e,n,r,o){var i=t.def&&t.def[e];if(i)try{i(n.elm,t,n,r,o)}catch(r){Ce(r,n.context,\"directive \".concat(t.name,\" \").concat(e,\" hook\"))}}var Lr=[_r,jr];function Rr(t,e){var n=e.componentOptions;if(!(a(n)&&!1===n.Ctor.options.inheritAttrs||i(t.data.attrs)&&i(e.data.attrs))){var r,o,u=e.elm,l=t.data.attrs||{},c=e.data.attrs||{};for(r in(a(c.__ob__)||s(c._v_attr_proxy))&&(c=e.data.attrs=A({},c)),c)o=c[r],l[r]!==o&&Ir(u,r,o,e.data.pre);for(r in(Q||Y)&&c.value!==l.value&&Ir(u,\"value\",c.value),l)i(c[r])&&(ar(r)?u.removeAttributeNS(ir,sr(r)):nr(r)||u.removeAttribute(r))}}function Ir(t,e,n,r){r||t.tagName.indexOf(\"-\")>-1?Mr(t,e,n):or(e)?ur(n)?t.removeAttribute(e):(n=\"allowfullscreen\"===e&&\"EMBED\"===t.tagName?\"true\":e,t.setAttribute(e,n)):nr(e)?t.setAttribute(e,function(t,e){return ur(e)||\"false\"===e?\"false\":\"contenteditable\"===t&&rr(e)?e:\"true\"}(e,n)):ar(e)?ur(n)?t.removeAttributeNS(ir,sr(e)):t.setAttributeNS(ir,e,n):Mr(t,e,n)}function Mr(t,e,n){if(ur(n))t.removeAttribute(e);else{if(Q&&!X&&\"TEXTAREA\"===t.tagName&&\"placeholder\"===e&&\"\"!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener(\"input\",r)};t.addEventListener(\"input\",r),t.__ieph=!0}t.setAttribute(e,n)}}var Dr={create:Rr,update:Rr};function Nr(t,e){var n=e.elm,r=e.data,o=t.data;if(!(i(r.staticClass)&&i(r.class)&&(i(o)||i(o.staticClass)&&i(o.class)))){var s=lr(e),u=n._transitionClasses;a(u)&&(s=fr(s,pr(u))),s!==n._prevClass&&(n.setAttribute(\"class\",s),n._prevClass=s)}}var Ur,Fr={create:Nr,update:Nr};function zr(t,e,n){var r=Ur;return function o(){var i=e.apply(null,arguments);null!==i&&Vr(t,o,n,r)}}var Br=Pe&&!(et&&Number(et[1])<=53);function qr(t,e,n,r){if(Br){var o=sn,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Ur.addEventListener(t,e,rt?{capture:n,passive:r}:n)}function Vr(t,e,n,r){(r||Ur).removeEventListener(t,e._wrapper||e,n)}function Hr(t,e){if(!i(t.data.on)||!i(e.data.on)){var n=e.data.on||{},r=t.data.on||{};Ur=e.elm||t.elm,function(t){if(a(t.__r)){var e=Q?\"change\":\"input\";t[e]=[].concat(t.__r,t[e]||[]),delete t.__r}a(t.__c)&&(t.change=[].concat(t.__c,t.change||[]),delete t.__c)}(n),Ht(n,r,qr,Vr,zr,e.context),Ur=void 0}}var Wr,Kr={create:Hr,update:Hr,destroy:function(t){return Hr(t,kr)}};function Jr(t,e){if(!i(t.data.domProps)||!i(e.data.domProps)){var n,r,o=e.elm,u=t.data.domProps||{},l=e.data.domProps||{};for(n in(a(l.__ob__)||s(l._v_attr_proxy))&&(l=e.data.domProps=A({},l)),u)n in l||(o[n]=\"\");for(n in l){if(r=l[n],\"textContent\"===n||\"innerHTML\"===n){if(e.children&&(e.children.length=0),r===u[n])continue;1===o.childNodes.length&&o.removeChild(o.childNodes[0])}if(\"value\"===n&&\"PROGRESS\"!==o.tagName){o._value=r;var c=i(r)?\"\":String(r);Gr(o,c)&&(o.value=c)}else if(\"innerHTML\"===n&&vr(o.tagName)&&i(o.innerHTML)){(Wr=Wr||document.createElement(\"div\")).innerHTML=\"<svg>\".concat(r,\"</svg>\");for(var f=Wr.firstChild;o.firstChild;)o.removeChild(o.firstChild);for(;f.firstChild;)o.appendChild(f.firstChild)}else if(r!==u[n])try{o[n]=r}catch(t){}}}}function Gr(t,e){return!t.composing&&(\"OPTION\"===t.tagName||function(t,e){var n=!0;try{n=document.activeElement!==t}catch(t){}return n&&t.value!==e}(t,e)||function(t,e){var n=t.value,r=t._vModifiers;if(a(r)){if(r.number)return g(n)!==g(e);if(r.trim)return n.trim()!==e.trim()}return n!==e}(t,e))}var Qr={create:Jr,update:Jr},Xr=k((function(t){var e={},n=/:(.+)/;return t.split(/;(?![^(]*\\))/g).forEach((function(t){if(t){var r=t.split(n);r.length>1&&(e[r[0].trim()]=r[1].trim())}})),e}));function Yr(t){var e=Zr(t.style);return t.staticStyle?A(t.staticStyle,e):e}function Zr(t){return Array.isArray(t)?T(t):\"string\"==typeof t?Xr(t):t}var to,eo=/^--/,no=/\\s*!important$/,ro=function(t,e,n){if(eo.test(e))t.style.setProperty(e,n);else if(no.test(n))t.style.setProperty(S(e),n.replace(no,\"\"),\"important\");else{var r=io(e);if(Array.isArray(n))for(var o=0,i=n.length;o<i;o++)t.style[r]=n[o];else t.style[r]=n}},oo=[\"Webkit\",\"Moz\",\"ms\"],io=k((function(t){if(to=to||document.createElement(\"div\").style,\"filter\"!==(t=C(t))&&t in to)return t;for(var e=t.charAt(0).toUpperCase()+t.slice(1),n=0;n<oo.length;n++){var r=oo[n]+e;if(r in to)return r}}));function ao(t,e){var n=e.data,r=t.data;if(!(i(n.staticStyle)&&i(n.style)&&i(r.staticStyle)&&i(r.style))){var o,s,u=e.elm,l=r.staticStyle,c=r.normalizedStyle||r.style||{},f=l||c,p=Zr(e.data.style)||{};e.data.normalizedStyle=a(p.__ob__)?A({},p):p;var d=function(t,e){var n,r={};if(e)for(var o=t;o.componentInstance;)(o=o.componentInstance._vnode)&&o.data&&(n=Yr(o.data))&&A(r,n);(n=Yr(t.data))&&A(r,n);for(var i=t;i=i.parent;)i.data&&(n=Yr(i.data))&&A(r,n);return r}(e,!0);for(s in f)i(d[s])&&ro(u,s,\"\");for(s in d)(o=d[s])!==f[s]&&ro(u,s,null==o?\"\":o)}}var so={create:ao,update:ao},uo=/\\s+/;function lo(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(\" \")>-1?e.split(uo).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=\" \".concat(t.getAttribute(\"class\")||\"\",\" \");n.indexOf(\" \"+e+\" \")<0&&t.setAttribute(\"class\",(n+e).trim())}}function co(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(\" \")>-1?e.split(uo).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute(\"class\");else{for(var n=\" \".concat(t.getAttribute(\"class\")||\"\",\" \"),r=\" \"+e+\" \";n.indexOf(r)>=0;)n=n.replace(r,\" \");(n=n.trim())?t.setAttribute(\"class\",n):t.removeAttribute(\"class\")}}function fo(t){if(t){if(\"object\"==typeof t){var e={};return!1!==t.css&&A(e,po(t.name||\"v\")),A(e,t),e}return\"string\"==typeof t?po(t):void 0}}var po=k((function(t){return{enterClass:\"\".concat(t,\"-enter\"),enterToClass:\"\".concat(t,\"-enter-to\"),enterActiveClass:\"\".concat(t,\"-enter-active\"),leaveClass:\"\".concat(t,\"-leave\"),leaveToClass:\"\".concat(t,\"-leave-to\"),leaveActiveClass:\"\".concat(t,\"-leave-active\")}})),ho=J&&!X,vo=\"transition\",mo=\"transitionend\",go=\"animation\",yo=\"animationend\";ho&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(vo=\"WebkitTransition\",mo=\"webkitTransitionEnd\"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(go=\"WebkitAnimation\",yo=\"webkitAnimationEnd\"));var bo=J?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function _o(t){bo((function(){bo(t)}))}function xo(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),lo(t,e))}function wo(t,e){t._transitionClasses&&_(t._transitionClasses,e),co(t,e)}function ko(t,e,n){var r=Co(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=\"transition\"===o?mo:yo,u=0,l=function(){t.removeEventListener(s,c),n()},c=function(e){e.target===t&&++u>=a&&l()};setTimeout((function(){u<a&&l()}),i+1),t.addEventListener(s,c)}var $o=/\\b(transform|all)(,|$)/;function Co(t,e){var n,r=window.getComputedStyle(t),o=(r[vo+\"Delay\"]||\"\").split(\", \"),i=(r[vo+\"Duration\"]||\"\").split(\", \"),a=Oo(o,i),s=(r[go+\"Delay\"]||\"\").split(\", \"),u=(r[go+\"Duration\"]||\"\").split(\", \"),l=Oo(s,u),c=0,f=0;return\"transition\"===e?a>0&&(n=\"transition\",c=a,f=i.length):\"animation\"===e?l>0&&(n=\"animation\",c=l,f=u.length):f=(n=(c=Math.max(a,l))>0?a>l?\"transition\":\"animation\":null)?\"transition\"===n?i.length:u.length:0,{type:n,timeout:c,propCount:f,hasTransform:\"transition\"===n&&$o.test(r[vo+\"Property\"])}}function Oo(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max.apply(null,e.map((function(e,n){return jo(e)+jo(t[n])})))}function jo(t){return 1e3*Number(t.slice(0,-1).replace(\",\",\".\"))}function So(t,e){var n=t.elm;a(n._leaveCb)&&(n._leaveCb.cancelled=!0,n._leaveCb());var r=fo(t.data.transition);if(!i(r)&&!a(n._enterCb)&&1===n.nodeType){for(var o=r.css,s=r.type,u=r.enterClass,f=r.enterToClass,p=r.enterActiveClass,d=r.appearClass,h=r.appearToClass,v=r.appearActiveClass,m=r.beforeEnter,y=r.enter,b=r.afterEnter,_=r.enterCancelled,x=r.beforeAppear,w=r.appear,k=r.afterAppear,$=r.appearCancelled,C=r.duration,O=Ge,j=Ge.$vnode;j&&j.parent;)O=j.context,j=j.parent;var S=!O._isMounted||!t.isRootInsert;if(!S||w||\"\"===w){var E=S&&d?d:u,P=S&&v?v:p,A=S&&h?h:f,T=S&&x||m,L=S&&l(w)?w:y,R=S&&k||b,I=S&&$||_,M=g(c(C)?C.enter:C);0;var D=!1!==o&&!X,U=Ao(L),F=n._enterCb=N((function(){D&&(wo(n,A),wo(n,P)),F.cancelled?(D&&wo(n,E),I&&I(n)):R&&R(n),n._enterCb=null}));t.data.show||Wt(t,\"insert\",(function(){var e=n.parentNode,r=e&&e._pending&&e._pending[t.key];r&&r.tag===t.tag&&r.elm._leaveCb&&r.elm._leaveCb(),L&&L(n,F)})),T&&T(n),D&&(xo(n,E),xo(n,P),_o((function(){wo(n,E),F.cancelled||(xo(n,A),U||(Po(M)?setTimeout(F,M):ko(n,s,F)))}))),t.data.show&&(e&&e(),L&&L(n,F)),D||U||F()}}}function Eo(t,e){var n=t.elm;a(n._enterCb)&&(n._enterCb.cancelled=!0,n._enterCb());var r=fo(t.data.transition);if(i(r)||1!==n.nodeType)return e();if(!a(n._leaveCb)){var o=r.css,s=r.type,u=r.leaveClass,l=r.leaveToClass,f=r.leaveActiveClass,p=r.beforeLeave,d=r.leave,h=r.afterLeave,v=r.leaveCancelled,m=r.delayLeave,y=r.duration,b=!1!==o&&!X,_=Ao(d),x=g(c(y)?y.leave:y);0;var w=n._leaveCb=N((function(){n.parentNode&&n.parentNode._pending&&(n.parentNode._pending[t.key]=null),b&&(wo(n,l),wo(n,f)),w.cancelled?(b&&wo(n,u),v&&v(n)):(e(),h&&h(n)),n._leaveCb=null}));m?m(k):k()}function k(){w.cancelled||(!t.data.show&&n.parentNode&&((n.parentNode._pending||(n.parentNode._pending={}))[t.key]=t),p&&p(n),b&&(xo(n,u),xo(n,f),_o((function(){wo(n,u),w.cancelled||(xo(n,l),_||(Po(x)?setTimeout(w,x):ko(n,s,w)))}))),d&&d(n,w),b||_||w())}}function Po(t){return\"number\"==typeof t&&!isNaN(t)}function Ao(t){if(i(t))return!1;var e=t.fns;return a(e)?Ao(Array.isArray(e)?e[0]:e):(t._length||t.length)>1}function To(t,e){!0!==e.data.show&&So(e)}var Lo=function(t){var e,n,r={},l=t.modules,c=t.nodeOps;for(e=0;e<$r.length;++e)for(r[$r[e]]=[],n=0;n<l.length;++n)a(l[n][$r[e]])&&r[$r[e]].push(l[n][$r[e]]);function f(t){var e=c.parentNode(t);a(e)&&c.removeChild(e,t)}function p(t,e,n,o,i,u,l){if(a(t.elm)&&a(u)&&(t=u[l]=vt(t)),t.isRootInsert=!i,!function(t,e,n,o){var i=t.data;if(a(i)){var u=a(t.componentInstance)&&i.keepAlive;if(a(i=i.hook)&&a(i=i.init)&&i(t,!1),a(t.componentInstance))return d(t,e),h(n,t.elm,o),s(u)&&function(t,e,n,o){var i,s=t;for(;s.componentInstance;)if(s=s.componentInstance._vnode,a(i=s.data)&&a(i=i.transition)){for(i=0;i<r.activate.length;++i)r.activate[i](kr,s);e.push(s);break}h(n,t.elm,o)}(t,e,n,o),!0}}(t,e,n,o)){var f=t.data,p=t.children,m=t.tag;a(m)?(t.elm=t.ns?c.createElementNS(t.ns,m):c.createElement(m,t),b(t),v(t,p,e),a(f)&&g(t,e),h(n,t.elm,o)):s(t.isComment)?(t.elm=c.createComment(t.text),h(n,t.elm,o)):(t.elm=c.createTextNode(t.text),h(n,t.elm,o))}}function d(t,e){a(t.data.pendingInsert)&&(e.push.apply(e,t.data.pendingInsert),t.data.pendingInsert=null),t.elm=t.componentInstance.$el,m(t)?(g(t,e),b(t)):(xr(t),e.push(t))}function h(t,e,n){a(t)&&(a(n)?c.parentNode(n)===t&&c.insertBefore(t,e,n):c.appendChild(t,e))}function v(t,e,n){if(o(e)){0;for(var r=0;r<e.length;++r)p(e[r],n,t.elm,null,!0,e,r)}else u(t.text)&&c.appendChild(t.elm,c.createTextNode(String(t.text)))}function m(t){for(;t.componentInstance;)t=t.componentInstance._vnode;return a(t.tag)}function g(t,n){for(var o=0;o<r.create.length;++o)r.create[o](kr,t);a(e=t.data.hook)&&(a(e.create)&&e.create(kr,t),a(e.insert)&&n.push(t))}function b(t){var e;if(a(e=t.fnScopeId))c.setStyleScope(t.elm,e);else for(var n=t;n;)a(e=n.context)&&a(e=e.$options._scopeId)&&c.setStyleScope(t.elm,e),n=n.parent;a(e=Ge)&&e!==t.context&&e!==t.fnContext&&a(e=e.$options._scopeId)&&c.setStyleScope(t.elm,e)}function _(t,e,n,r,o,i){for(;r<=o;++r)p(n[r],i,t,e,!1,n,r)}function x(t){var e,n,o=t.data;if(a(o))for(a(e=o.hook)&&a(e=e.destroy)&&e(t),e=0;e<r.destroy.length;++e)r.destroy[e](t);if(a(e=t.children))for(n=0;n<t.children.length;++n)x(t.children[n])}function w(t,e,n){for(;e<=n;++e){var r=t[e];a(r)&&(a(r.tag)?(k(r),x(r)):f(r.elm))}}function k(t,e){if(a(e)||a(t.data)){var n,o=r.remove.length+1;for(a(e)?e.listeners+=o:e=function(t,e){function n(){0==--n.listeners&&f(t)}return n.listeners=e,n}(t.elm,o),a(n=t.componentInstance)&&a(n=n._vnode)&&a(n.data)&&k(n,e),n=0;n<r.remove.length;++n)r.remove[n](t,e);a(n=t.data.hook)&&a(n=n.remove)?n(t,e):e()}else f(t.elm)}function $(t,e,n,r){for(var o=n;o<r;o++){var i=e[o];if(a(i)&&Cr(t,i))return o}}function C(t,e,n,o,u,l){if(t!==e){a(e.elm)&&a(o)&&(e=o[u]=vt(e));var f=e.elm=t.elm;if(s(t.isAsyncPlaceholder))a(e.asyncFactory.resolved)?S(t.elm,e,n):e.isAsyncPlaceholder=!0;else if(s(e.isStatic)&&s(t.isStatic)&&e.key===t.key&&(s(e.isCloned)||s(e.isOnce)))e.componentInstance=t.componentInstance;else{var d,h=e.data;a(h)&&a(d=h.hook)&&a(d=d.prepatch)&&d(t,e);var v=t.children,g=e.children;if(a(h)&&m(e)){for(d=0;d<r.update.length;++d)r.update[d](t,e);a(d=h.hook)&&a(d=d.update)&&d(t,e)}i(e.text)?a(v)&&a(g)?v!==g&&function(t,e,n,r,o){var s,u,l,f=0,d=0,h=e.length-1,v=e[0],m=e[h],g=n.length-1,y=n[0],b=n[g],x=!o;for(0;f<=h&&d<=g;)i(v)?v=e[++f]:i(m)?m=e[--h]:Cr(v,y)?(C(v,y,r,n,d),v=e[++f],y=n[++d]):Cr(m,b)?(C(m,b,r,n,g),m=e[--h],b=n[--g]):Cr(v,b)?(C(v,b,r,n,g),x&&c.insertBefore(t,v.elm,c.nextSibling(m.elm)),v=e[++f],b=n[--g]):Cr(m,y)?(C(m,y,r,n,d),x&&c.insertBefore(t,m.elm,v.elm),m=e[--h],y=n[++d]):(i(s)&&(s=Or(e,f,h)),i(u=a(y.key)?s[y.key]:$(y,e,f,h))?p(y,r,t,v.elm,!1,n,d):Cr(l=e[u],y)?(C(l,y,r,n,d),e[u]=void 0,x&&c.insertBefore(t,l.elm,v.elm)):p(y,r,t,v.elm,!1,n,d),y=n[++d]);f>h?_(t,i(n[g+1])?null:n[g+1].elm,n,d,g,r):d>g&&w(e,f,h)}(f,v,g,n,l):a(g)?(a(t.text)&&c.setTextContent(f,\"\"),_(f,null,g,0,g.length-1,n)):a(v)?w(v,0,v.length-1):a(t.text)&&c.setTextContent(f,\"\"):t.text!==e.text&&c.setTextContent(f,e.text),a(h)&&a(d=h.hook)&&a(d=d.postpatch)&&d(t,e)}}}function O(t,e,n){if(s(n)&&a(t.parent))t.parent.data.pendingInsert=e;else for(var r=0;r<e.length;++r)e[r].data.hook.insert(e[r])}var j=y(\"attrs,class,staticClass,staticStyle,key\");function S(t,e,n,r){var o,i=e.tag,u=e.data,l=e.children;if(r=r||u&&u.pre,e.elm=t,s(e.isComment)&&a(e.asyncFactory))return e.isAsyncPlaceholder=!0,!0;if(a(u)&&(a(o=u.hook)&&a(o=o.init)&&o(e,!0),a(o=e.componentInstance)))return d(e,n),!0;if(a(i)){if(a(l))if(t.hasChildNodes())if(a(o=u)&&a(o=o.domProps)&&a(o=o.innerHTML)){if(o!==t.innerHTML)return!1}else{for(var c=!0,f=t.firstChild,p=0;p<l.length;p++){if(!f||!S(f,l[p],n,r)){c=!1;break}f=f.nextSibling}if(!c||f)return!1}else v(e,l,n);if(a(u)){var h=!1;for(var m in u)if(!j(m)){h=!0,g(e,n);break}!h&&u.class&&ze(u.class)}}else t.data!==e.text&&(t.data=e.text);return!0}return function(t,e,n,o){if(!i(e)){var u,l=!1,f=[];if(i(t))l=!0,p(e,f);else{var d=a(t.nodeType);if(!d&&Cr(t,e))C(t,e,f,null,null,o);else{if(d){if(1===t.nodeType&&t.hasAttribute(\"data-server-rendered\")&&(t.removeAttribute(\"data-server-rendered\"),n=!0),s(n)&&S(t,e,f))return O(e,f,!0),t;u=t,t=new pt(c.tagName(u).toLowerCase(),{},[],void 0,u)}var h=t.elm,v=c.parentNode(h);if(p(e,f,h._leaveCb?null:v,c.nextSibling(h)),a(e.parent))for(var g=e.parent,y=m(e);g;){for(var b=0;b<r.destroy.length;++b)r.destroy[b](g);if(g.elm=e.elm,y){for(var _=0;_<r.create.length;++_)r.create[_](kr,g);var k=g.data.hook.insert;if(k.merged)for(var $=1;$<k.fns.length;$++)k.fns[$]()}else xr(g);g=g.parent}a(v)?w([t],0,0):a(t.tag)&&x(t)}}return O(e,f,l),e.elm}a(t)&&x(t)}}({nodeOps:br,modules:[Dr,Fr,Kr,Qr,so,J?{create:To,activate:To,remove:function(t,e){!0!==t.data.show?Eo(t,e):e()}}:{}].concat(Lr)});X&&document.addEventListener(\"selectionchange\",(function(){var t=document.activeElement;t&&t.vmodel&&zo(t,\"input\")}));var Ro={inserted:function(t,e,n,r){\"select\"===n.tag?(r.elm&&!r.elm._vOptions?Wt(n,\"postpatch\",(function(){Ro.componentUpdated(t,e,n)})):Io(t,e,n.context),t._vOptions=[].map.call(t.options,No)):(\"textarea\"===n.tag||yr(t.type))&&(t._vModifiers=e.modifiers,e.modifiers.lazy||(t.addEventListener(\"compositionstart\",Uo),t.addEventListener(\"compositionend\",Fo),t.addEventListener(\"change\",Fo),X&&(t.vmodel=!0)))},componentUpdated:function(t,e,n){if(\"select\"===n.tag){Io(t,e,n.context);var r=t._vOptions,o=t._vOptions=[].map.call(t.options,No);if(o.some((function(t,e){return!M(t,r[e])})))(t.multiple?e.value.some((function(t){return Do(t,o)})):e.value!==e.oldValue&&Do(e.value,o))&&zo(t,\"change\")}}};function Io(t,e,n){Mo(t,e,n),(Q||Y)&&setTimeout((function(){Mo(t,e,n)}),0)}function Mo(t,e,n){var r=e.value,o=t.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,u=t.options.length;s<u;s++)if(a=t.options[s],o)i=D(r,No(a))>-1,a.selected!==i&&(a.selected=i);else if(M(No(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function Do(t,e){return e.every((function(e){return!M(e,t)}))}function No(t){return\"_value\"in t?t._value:t.value}function Uo(t){t.target.composing=!0}function Fo(t){t.target.composing&&(t.target.composing=!1,zo(t.target,\"input\"))}function zo(t,e){var n=document.createEvent(\"HTMLEvents\");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function Bo(t){return!t.componentInstance||t.data&&t.data.transition?t:Bo(t.componentInstance._vnode)}var qo={model:Ro,show:{bind:function(t,e,n){var r=e.value,o=(n=Bo(n)).data&&n.data.transition,i=t.__vOriginalDisplay=\"none\"===t.style.display?\"\":t.style.display;r&&o?(n.data.show=!0,So(n,(function(){t.style.display=i}))):t.style.display=r?i:\"none\"},update:function(t,e,n){var r=e.value;!r!=!e.oldValue&&((n=Bo(n)).data&&n.data.transition?(n.data.show=!0,r?So(n,(function(){t.style.display=t.__vOriginalDisplay})):Eo(n,(function(){t.style.display=\"none\"}))):t.style.display=r?t.__vOriginalDisplay:\"none\")},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}}},Vo={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function Ho(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?Ho(ke(e.children)):t}function Wo(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var r in o)e[C(r)]=o[r];return e}function Ko(t,e){if(/\\d-keep-alive$/.test(e.tag))return t(\"keep-alive\",{props:e.componentOptions.propsData})}var Jo=function(t){return t.tag||de(t)},Go=function(t){return\"show\"===t.name},Qo={name:\"transition\",props:Vo,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(Jo)).length){0;var r=this.mode;0;var o=n[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return o;var i=Ho(o);if(!i)return o;if(this._leaving)return Ko(t,o);var a=\"__transition-\".concat(this._uid,\"-\");i.key=null==i.key?i.isComment?a+\"comment\":a+i.tag:u(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var s=(i.data||(i.data={})).transition=Wo(this),l=this._vnode,c=Ho(l);if(i.data.directives&&i.data.directives.some(Go)&&(i.data.show=!0),c&&c.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(i,c)&&!de(c)&&(!c.componentInstance||!c.componentInstance._vnode.isComment)){var f=c.data.transition=A({},s);if(\"out-in\"===r)return this._leaving=!0,Wt(f,\"afterLeave\",(function(){e._leaving=!1,e.$forceUpdate()})),Ko(t,o);if(\"in-out\"===r){if(de(i))return l;var p,d=function(){p()};Wt(s,\"afterEnter\",d),Wt(s,\"enterCancelled\",d),Wt(f,\"delayLeave\",(function(t){p=t}))}}return o}}},Xo=A({tag:String,moveClass:String},Vo);function Yo(t){t.elm._moveCb&&t.elm._moveCb(),t.elm._enterCb&&t.elm._enterCb()}function Zo(t){t.data.newPos=t.elm.getBoundingClientRect()}function ti(t){var e=t.data.pos,n=t.data.newPos,r=e.left-n.left,o=e.top-n.top;if(r||o){t.data.moved=!0;var i=t.elm.style;i.transform=i.WebkitTransform=\"translate(\".concat(r,\"px,\").concat(o,\"px)\"),i.transitionDuration=\"0s\"}}delete Xo.mode;var ei={Transition:Qo,TransitionGroup:{props:Xo,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=Qe(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||\"span\",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=Wo(this),s=0;s<o.length;s++){if((c=o[s]).tag)if(null!=c.key&&0!==String(c.key).indexOf(\"__vlist\"))i.push(c),n[c.key]=c,(c.data||(c.data={})).transition=a;else;}if(r){var u=[],l=[];for(s=0;s<r.length;s++){var c;(c=r[s]).data.transition=a,c.data.pos=c.elm.getBoundingClientRect(),n[c.key]?u.push(c):l.push(c)}this.kept=t(e,null,u),this.removed=l}return t(e,null,i)},updated:function(){var t=this.prevChildren,e=this.moveClass||(this.name||\"v\")+\"-move\";t.length&&this.hasMove(t[0].elm,e)&&(t.forEach(Yo),t.forEach(Zo),t.forEach(ti),this._reflow=document.body.offsetHeight,t.forEach((function(t){if(t.data.moved){var n=t.elm,r=n.style;xo(n,e),r.transform=r.WebkitTransform=r.transitionDuration=\"\",n.addEventListener(mo,n._moveCb=function t(r){r&&r.target!==n||r&&!/transform$/.test(r.propertyName)||(n.removeEventListener(mo,t),n._moveCb=null,wo(n,e))})}})))},methods:{hasMove:function(t,e){if(!ho)return!1;if(this._hasMove)return this._hasMove;var n=t.cloneNode();t._transitionClasses&&t._transitionClasses.forEach((function(t){co(n,t)})),lo(n,e),n.style.display=\"none\",this.$el.appendChild(n);var r=Co(n);return this.$el.removeChild(n),this._hasMove=r.hasTransform}}}};function ni(t,e){for(var n in e)t[n]=e[n];return t}Wn.config.mustUseProp=function(t,e,n){return\"value\"===n&&er(t)&&\"button\"!==e||\"selected\"===n&&\"option\"===t||\"checked\"===n&&\"input\"===t||\"muted\"===n&&\"video\"===t},Wn.config.isReservedTag=mr,Wn.config.isReservedAttr=tr,Wn.config.getTagNamespace=function(t){return vr(t)?\"svg\":\"math\"===t?\"math\":void 0},Wn.config.isUnknownElement=function(t){if(!J)return!0;if(mr(t))return!1;if(t=t.toLowerCase(),null!=gr[t])return gr[t];var e=document.createElement(t);return t.indexOf(\"-\")>-1?gr[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:gr[t]=/HTMLUnknownElement/.test(e.toString())},A(Wn.options.directives,qo),A(Wn.options.components,ei),Wn.prototype.__patch__=J?Lo:L,Wn.prototype.$mount=function(t,e){return function(t,e,n){var r;t.$el=e,t.$options.render||(t.$options.render=dt),Ze(t,\"beforeMount\"),r=function(){t._update(t._render(),n)},new Ve(t,r,L,{before:function(){t._isMounted&&!t._isDestroyed&&Ze(t,\"beforeUpdate\")}},!0),n=!1;var o=t._preWatchers;if(o)for(var i=0;i<o.length;i++)o[i].run();return null==t.$vnode&&(t._isMounted=!0,Ze(t,\"mounted\")),t}(this,t=t&&J?function(t){if(\"string\"==typeof t){var e=document.querySelector(t);return e||document.createElement(\"div\")}return t}(t):void 0,e)},J&&setTimeout((function(){B.devtools&&at&&at.emit(\"init\",Wn)}),0);var ri=/[!'()*]/g,oi=function(t){return\"%\"+t.charCodeAt(0).toString(16)},ii=/%2C/g,ai=function(t){return encodeURIComponent(t).replace(ri,oi).replace(ii,\",\")};function si(t){try{return decodeURIComponent(t)}catch(t){0}return t}var ui=function(t){return null==t||\"object\"==typeof t?t:String(t)};function li(t){var e={};return(t=t.trim().replace(/^(\\?|#|&)/,\"\"))?(t.split(\"&\").forEach((function(t){var n=t.replace(/\\+/g,\" \").split(\"=\"),r=si(n.shift()),o=n.length>0?si(n.join(\"=\")):null;void 0===e[r]?e[r]=o:Array.isArray(e[r])?e[r].push(o):e[r]=[e[r],o]})),e):e}function ci(t){var e=t?Object.keys(t).map((function(e){var n=t[e];if(void 0===n)return\"\";if(null===n)return ai(e);if(Array.isArray(n)){var r=[];return n.forEach((function(t){void 0!==t&&(null===t?r.push(ai(e)):r.push(ai(e)+\"=\"+ai(t)))})),r.join(\"&\")}return ai(e)+\"=\"+ai(n)})).filter((function(t){return t.length>0})).join(\"&\"):null;return e?\"?\"+e:\"\"}var fi=/\\/?$/;function pi(t,e,n,r){var o=r&&r.options.stringifyQuery,i=e.query||{};try{i=di(i)}catch(t){}var a={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||\"/\",hash:e.hash||\"\",query:i,params:e.params||{},fullPath:mi(e,o),matched:t?vi(t):[]};return n&&(a.redirectedFrom=mi(n,o)),Object.freeze(a)}function di(t){if(Array.isArray(t))return t.map(di);if(t&&\"object\"==typeof t){var e={};for(var n in t)e[n]=di(t[n]);return e}return t}var hi=pi(null,{path:\"/\"});function vi(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function mi(t,e){var n=t.path,r=t.query;void 0===r&&(r={});var o=t.hash;return void 0===o&&(o=\"\"),(n||\"/\")+(e||ci)(r)+o}function gi(t,e,n){return e===hi?t===e:!!e&&(t.path&&e.path?t.path.replace(fi,\"\")===e.path.replace(fi,\"\")&&(n||t.hash===e.hash&&yi(t.query,e.query)):!(!t.name||!e.name)&&(t.name===e.name&&(n||t.hash===e.hash&&yi(t.query,e.query)&&yi(t.params,e.params))))}function yi(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var n=Object.keys(t).sort(),r=Object.keys(e).sort();return n.length===r.length&&n.every((function(n,o){var i=t[n];if(r[o]!==n)return!1;var a=e[n];return null==i||null==a?i===a:\"object\"==typeof i&&\"object\"==typeof a?yi(i,a):String(i)===String(a)}))}function bi(t){for(var e=0;e<t.matched.length;e++){var n=t.matched[e];for(var r in n.instances){var o=n.instances[r],i=n.enteredCbs[r];if(o&&i){delete n.enteredCbs[r];for(var a=0;a<i.length;a++)o._isBeingDestroyed||i[a](o)}}}}var _i={name:\"RouterView\",functional:!0,props:{name:{type:String,default:\"default\"}},render:function(t,e){var n=e.props,r=e.children,o=e.parent,i=e.data;i.routerView=!0;for(var a=o.$createElement,s=n.name,u=o.$route,l=o._routerViewCache||(o._routerViewCache={}),c=0,f=!1;o&&o._routerRoot!==o;){var p=o.$vnode?o.$vnode.data:{};p.routerView&&c++,p.keepAlive&&o._directInactive&&o._inactive&&(f=!0),o=o.$parent}if(i.routerViewDepth=c,f){var d=l[s],h=d&&d.component;return h?(d.configProps&&xi(h,i,d.route,d.configProps),a(h,i,r)):a()}var v=u.matched[c],m=v&&v.components[s];if(!v||!m)return l[s]=null,a();l[s]={component:m},i.registerRouteInstance=function(t,e){var n=v.instances[s];(e&&n!==t||!e&&n===t)&&(v.instances[s]=e)},(i.hook||(i.hook={})).prepatch=function(t,e){v.instances[s]=e.componentInstance},i.hook.init=function(t){t.data.keepAlive&&t.componentInstance&&t.componentInstance!==v.instances[s]&&(v.instances[s]=t.componentInstance),bi(u)};var g=v.props&&v.props[s];return g&&(ni(l[s],{route:u,configProps:g}),xi(m,i,u,g)),a(m,i,r)}};function xi(t,e,n,r){var o=e.props=function(t,e){switch(typeof e){case\"undefined\":return;case\"object\":return e;case\"function\":return e(t);case\"boolean\":return e?t.params:void 0;default:0}}(n,r);if(o){o=e.props=ni({},o);var i=e.attrs=e.attrs||{};for(var a in o)t.props&&a in t.props||(i[a]=o[a],delete o[a])}}function wi(t,e,n){var r=t.charAt(0);if(\"/\"===r)return t;if(\"?\"===r||\"#\"===r)return e+t;var o=e.split(\"/\");n&&o[o.length-1]||o.pop();for(var i=t.replace(/^\\//,\"\").split(\"/\"),a=0;a<i.length;a++){var s=i[a];\"..\"===s?o.pop():\".\"!==s&&o.push(s)}return\"\"!==o[0]&&o.unshift(\"\"),o.join(\"/\")}function ki(t){return t.replace(/\\/(?:\\s*\\/)+/g,\"/\")}var $i=Array.isArray||function(t){return\"[object Array]\"==Object.prototype.toString.call(t)},Ci=Ui,Oi=Ai,ji=function(t,e){return Li(Ai(t,e),e)},Si=Li,Ei=Ni,Pi=new RegExp([\"(\\\\\\\\.)\",\"([\\\\/.])?(?:(?:\\\\:(\\\\w+)(?:\\\\(((?:\\\\\\\\.|[^\\\\\\\\()])+)\\\\))?|\\\\(((?:\\\\\\\\.|[^\\\\\\\\()])+)\\\\))([+*?])?|(\\\\*))\"].join(\"|\"),\"g\");function Ai(t,e){for(var n,r=[],o=0,i=0,a=\"\",s=e&&e.delimiter||\"/\";null!=(n=Pi.exec(t));){var u=n[0],l=n[1],c=n.index;if(a+=t.slice(i,c),i=c+u.length,l)a+=l[1];else{var f=t[i],p=n[2],d=n[3],h=n[4],v=n[5],m=n[6],g=n[7];a&&(r.push(a),a=\"\");var y=null!=p&&null!=f&&f!==p,b=\"+\"===m||\"*\"===m,_=\"?\"===m||\"*\"===m,x=n[2]||s,w=h||v;r.push({name:d||o++,prefix:p||\"\",delimiter:x,optional:_,repeat:b,partial:y,asterisk:!!g,pattern:w?Ii(w):g?\".*\":\"[^\"+Ri(x)+\"]+?\"})}}return i<t.length&&(a+=t.substr(i)),a&&r.push(a),r}function Ti(t){return encodeURI(t).replace(/[\\/?#]/g,(function(t){return\"%\"+t.charCodeAt(0).toString(16).toUpperCase()}))}function Li(t,e){for(var n=new Array(t.length),r=0;r<t.length;r++)\"object\"==typeof t[r]&&(n[r]=new RegExp(\"^(?:\"+t[r].pattern+\")$\",Di(e)));return function(e,r){for(var o=\"\",i=e||{},a=(r||{}).pretty?Ti:encodeURIComponent,s=0;s<t.length;s++){var u=t[s];if(\"string\"!=typeof u){var l,c=i[u.name];if(null==c){if(u.optional){u.partial&&(o+=u.prefix);continue}throw new TypeError('Expected \"'+u.name+'\" to be defined')}if($i(c)){if(!u.repeat)throw new TypeError('Expected \"'+u.name+'\" to not repeat, but received `'+JSON.stringify(c)+\"`\");if(0===c.length){if(u.optional)continue;throw new TypeError('Expected \"'+u.name+'\" to not be empty')}for(var f=0;f<c.length;f++){if(l=a(c[f]),!n[s].test(l))throw new TypeError('Expected all \"'+u.name+'\" to match \"'+u.pattern+'\", but received `'+JSON.stringify(l)+\"`\");o+=(0===f?u.prefix:u.delimiter)+l}}else{if(l=u.asterisk?encodeURI(c).replace(/[?#]/g,(function(t){return\"%\"+t.charCodeAt(0).toString(16).toUpperCase()})):a(c),!n[s].test(l))throw new TypeError('Expected \"'+u.name+'\" to match \"'+u.pattern+'\", but received \"'+l+'\"');o+=u.prefix+l}}else o+=u}return o}}function Ri(t){return t.replace(/([.+*?=^!:${}()[\\]|\\/\\\\])/g,\"\\\\$1\")}function Ii(t){return t.replace(/([=!:$\\/()])/g,\"\\\\$1\")}function Mi(t,e){return t.keys=e,t}function Di(t){return t&&t.sensitive?\"\":\"i\"}function Ni(t,e,n){$i(e)||(n=e||n,e=[]);for(var r=(n=n||{}).strict,o=!1!==n.end,i=\"\",a=0;a<t.length;a++){var s=t[a];if(\"string\"==typeof s)i+=Ri(s);else{var u=Ri(s.prefix),l=\"(?:\"+s.pattern+\")\";e.push(s),s.repeat&&(l+=\"(?:\"+u+l+\")*\"),i+=l=s.optional?s.partial?u+\"(\"+l+\")?\":\"(?:\"+u+\"(\"+l+\"))?\":u+\"(\"+l+\")\"}}var c=Ri(n.delimiter||\"/\"),f=i.slice(-c.length)===c;return r||(i=(f?i.slice(0,-c.length):i)+\"(?:\"+c+\"(?=$))?\"),i+=o?\"$\":r&&f?\"\":\"(?=\"+c+\"|$)\",Mi(new RegExp(\"^\"+i,Di(n)),e)}function Ui(t,e,n){return $i(e)||(n=e||n,e=[]),n=n||{},t instanceof RegExp?function(t,e){var n=t.source.match(/\\((?!\\?)/g);if(n)for(var r=0;r<n.length;r++)e.push({name:r,prefix:null,delimiter:null,optional:!1,repeat:!1,partial:!1,asterisk:!1,pattern:null});return Mi(t,e)}(t,e):$i(t)?function(t,e,n){for(var r=[],o=0;o<t.length;o++)r.push(Ui(t[o],e,n).source);return Mi(new RegExp(\"(?:\"+r.join(\"|\")+\")\",Di(n)),e)}(t,e,n):function(t,e,n){return Ni(Ai(t,n),e,n)}(t,e,n)}Ci.parse=Oi,Ci.compile=ji,Ci.tokensToFunction=Si,Ci.tokensToRegExp=Ei;var Fi=Object.create(null);function zi(t,e,n){e=e||{};try{var r=Fi[t]||(Fi[t]=Ci.compile(t));return\"string\"==typeof e.pathMatch&&(e[0]=e.pathMatch),r(e,{pretty:!0})}catch(t){return\"\"}finally{delete e[0]}}function Bi(t,e,n,r){var o=\"string\"==typeof t?{path:t}:t;if(o._normalized)return o;if(o.name){var i=(o=ni({},t)).params;return i&&\"object\"==typeof i&&(o.params=ni({},i)),o}if(!o.path&&o.params&&e){(o=ni({},o))._normalized=!0;var a=ni(ni({},e.params),o.params);if(e.name)o.name=e.name,o.params=a;else if(e.matched.length){var s=e.matched[e.matched.length-1].path;o.path=zi(s,a,e.path)}else 0;return o}var u=function(t){var e=\"\",n=\"\",r=t.indexOf(\"#\");r>=0&&(e=t.slice(r),t=t.slice(0,r));var o=t.indexOf(\"?\");return o>=0&&(n=t.slice(o+1),t=t.slice(0,o)),{path:t,query:n,hash:e}}(o.path||\"\"),l=e&&e.path||\"/\",c=u.path?wi(u.path,l,n||o.append):l,f=function(t,e,n){void 0===e&&(e={});var r,o=n||li;try{r=o(t||\"\")}catch(t){r={}}for(var i in e){var a=e[i];r[i]=Array.isArray(a)?a.map(ui):ui(a)}return r}(u.query,o.query,r&&r.options.parseQuery),p=o.hash||u.hash;return p&&\"#\"!==p.charAt(0)&&(p=\"#\"+p),{_normalized:!0,path:c,query:f,hash:p}}var qi,Vi=function(){},Hi={name:\"RouterLink\",props:{to:{type:[String,Object],required:!0},tag:{type:String,default:\"a\"},custom:Boolean,exact:Boolean,exactPath:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:\"page\"},event:{type:[String,Array],default:\"click\"}},render:function(t){var e=this,n=this.$router,r=this.$route,o=n.resolve(this.to,r,this.append),i=o.location,a=o.route,s=o.href,u={},l=n.options.linkActiveClass,c=n.options.linkExactActiveClass,f=null==l?\"router-link-active\":l,p=null==c?\"router-link-exact-active\":c,d=null==this.activeClass?f:this.activeClass,h=null==this.exactActiveClass?p:this.exactActiveClass,v=a.redirectedFrom?pi(null,Bi(a.redirectedFrom),null,n):a;u[h]=gi(r,v,this.exactPath),u[d]=this.exact||this.exactPath?u[h]:function(t,e){return 0===t.path.replace(fi,\"/\").indexOf(e.path.replace(fi,\"/\"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var n in e)if(!(n in t))return!1;return!0}(t.query,e.query)}(r,v);var m=u[h]?this.ariaCurrentValue:null,g=function(t){Wi(t)&&(e.replace?n.replace(i,Vi):n.push(i,Vi))},y={click:Wi};Array.isArray(this.event)?this.event.forEach((function(t){y[t]=g})):y[this.event]=g;var b={class:u},_=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:s,route:a,navigate:g,isActive:u[d],isExactActive:u[h]});if(_){if(1===_.length)return _[0];if(_.length>1||!_.length)return 0===_.length?t():t(\"span\",{},_)}if(\"a\"===this.tag)b.on=y,b.attrs={href:s,\"aria-current\":m};else{var x=function t(e){var n;if(e)for(var r=0;r<e.length;r++){if(\"a\"===(n=e[r]).tag)return n;if(n.children&&(n=t(n.children)))return n}}(this.$slots.default);if(x){x.isStatic=!1;var w=x.data=ni({},x.data);for(var k in w.on=w.on||{},w.on){var $=w.on[k];k in y&&(w.on[k]=Array.isArray($)?$:[$])}for(var C in y)C in w.on?w.on[C].push(y[C]):w.on[C]=g;var O=x.data.attrs=ni({},x.data.attrs);O.href=s,O[\"aria-current\"]=m}else b.on=y}return t(this.tag,b,this.$slots.default)}};function Wi(t){if(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey||t.defaultPrevented||void 0!==t.button&&0!==t.button)){if(t.currentTarget&&t.currentTarget.getAttribute){var e=t.currentTarget.getAttribute(\"target\");if(/\\b_blank\\b/i.test(e))return}return t.preventDefault&&t.preventDefault(),!0}}var Ki=\"undefined\"!=typeof window;function Ji(t,e,n,r,o){var i=e||[],a=n||Object.create(null),s=r||Object.create(null);t.forEach((function(t){!function t(e,n,r,o,i,a){var s=o.path,u=o.name;0;var l=o.pathToRegexpOptions||{},c=function(t,e,n){n||(t=t.replace(/\\/$/,\"\"));if(\"/\"===t[0])return t;if(null==e)return t;return ki(e.path+\"/\"+t)}(s,i,l.strict);\"boolean\"==typeof o.caseSensitive&&(l.sensitive=o.caseSensitive);var f={path:c,regex:Gi(c,l),components:o.components||{default:o.component},alias:o.alias?\"string\"==typeof o.alias?[o.alias]:o.alias:[],instances:{},enteredCbs:{},name:u,parent:i,matchAs:a,redirect:o.redirect,beforeEnter:o.beforeEnter,meta:o.meta||{},props:null==o.props?{}:o.components?o.props:{default:o.props}};o.children&&o.children.forEach((function(o){var i=a?ki(a+\"/\"+o.path):void 0;t(e,n,r,o,f,i)}));n[f.path]||(e.push(f.path),n[f.path]=f);if(void 0!==o.alias)for(var p=Array.isArray(o.alias)?o.alias:[o.alias],d=0;d<p.length;++d){0;var h={path:p[d],children:o.children};t(e,n,r,h,i,f.path||\"/\")}u&&(r[u]||(r[u]=f))}(i,a,s,t,o)}));for(var u=0,l=i.length;u<l;u++)\"*\"===i[u]&&(i.push(i.splice(u,1)[0]),l--,u--);return{pathList:i,pathMap:a,nameMap:s}}function Gi(t,e){return Ci(t,[],e)}function Qi(t,e){var n=Ji(t),r=n.pathList,o=n.pathMap,i=n.nameMap;function a(t,n,a){var s=Bi(t,n,!1,e),l=s.name;if(l){var c=i[l];if(!c)return u(null,s);var f=c.regex.keys.filter((function(t){return!t.optional})).map((function(t){return t.name}));if(\"object\"!=typeof s.params&&(s.params={}),n&&\"object\"==typeof n.params)for(var p in n.params)!(p in s.params)&&f.indexOf(p)>-1&&(s.params[p]=n.params[p]);return s.path=zi(c.path,s.params),u(c,s,a)}if(s.path){s.params={};for(var d=0;d<r.length;d++){var h=r[d],v=o[h];if(Xi(v.regex,s.path,s.params))return u(v,s,a)}}return u(null,s)}function s(t,n){var r=t.redirect,o=\"function\"==typeof r?r(pi(t,n,null,e)):r;if(\"string\"==typeof o&&(o={path:o}),!o||\"object\"!=typeof o)return u(null,n);var s=o,l=s.name,c=s.path,f=n.query,p=n.hash,d=n.params;if(f=s.hasOwnProperty(\"query\")?s.query:f,p=s.hasOwnProperty(\"hash\")?s.hash:p,d=s.hasOwnProperty(\"params\")?s.params:d,l){i[l];return a({_normalized:!0,name:l,query:f,hash:p,params:d},void 0,n)}if(c){var h=function(t,e){return wi(t,e.parent?e.parent.path:\"/\",!0)}(c,t);return a({_normalized:!0,path:zi(h,d),query:f,hash:p},void 0,n)}return u(null,n)}function u(t,n,r){return t&&t.redirect?s(t,r||n):t&&t.matchAs?function(t,e,n){var r=a({_normalized:!0,path:zi(n,e.params)});if(r){var o=r.matched,i=o[o.length-1];return e.params=r.params,u(i,e)}return u(null,e)}(0,n,t.matchAs):pi(t,n,r,e)}return{match:a,addRoute:function(t,e){var n=\"object\"!=typeof t?i[t]:void 0;Ji([e||t],r,o,i,n),n&&n.alias.length&&Ji(n.alias.map((function(t){return{path:t,children:[e]}})),r,o,i,n)},getRoutes:function(){return r.map((function(t){return o[t]}))},addRoutes:function(t){Ji(t,r,o,i)}}}function Xi(t,e,n){var r=e.match(t);if(!r)return!1;if(!n)return!0;for(var o=1,i=r.length;o<i;++o){var a=t.keys[o-1];a&&(n[a.name||\"pathMatch\"]=\"string\"==typeof r[o]?si(r[o]):r[o])}return!0}var Yi=Ki&&window.performance&&window.performance.now?window.performance:Date;function Zi(){return Yi.now().toFixed(3)}var ta=Zi();function ea(){return ta}function na(t){return ta=t}var ra=Object.create(null);function oa(){\"scrollRestoration\"in window.history&&(window.history.scrollRestoration=\"manual\");var t=window.location.protocol+\"//\"+window.location.host,e=window.location.href.replace(t,\"\"),n=ni({},window.history.state);return n.key=ea(),window.history.replaceState(n,\"\",e),window.addEventListener(\"popstate\",sa),function(){window.removeEventListener(\"popstate\",sa)}}function ia(t,e,n,r){if(t.app){var o=t.options.scrollBehavior;o&&t.app.$nextTick((function(){var i=function(){var t=ea();if(t)return ra[t]}(),a=o.call(t,e,n,r?i:null);a&&(\"function\"==typeof a.then?a.then((function(t){pa(t,i)})).catch((function(t){0})):pa(a,i))}))}}function aa(){var t=ea();t&&(ra[t]={x:window.pageXOffset,y:window.pageYOffset})}function sa(t){aa(),t.state&&t.state.key&&na(t.state.key)}function ua(t){return ca(t.x)||ca(t.y)}function la(t){return{x:ca(t.x)?t.x:window.pageXOffset,y:ca(t.y)?t.y:window.pageYOffset}}function ca(t){return\"number\"==typeof t}var fa=/^#\\d/;function pa(t,e){var n,r=\"object\"==typeof t;if(r&&\"string\"==typeof t.selector){var o=fa.test(t.selector)?document.getElementById(t.selector.slice(1)):document.querySelector(t.selector);if(o){var i=t.offset&&\"object\"==typeof t.offset?t.offset:{};e=function(t,e){var n=document.documentElement.getBoundingClientRect(),r=t.getBoundingClientRect();return{x:r.left-n.left-e.x,y:r.top-n.top-e.y}}(o,i={x:ca((n=i).x)?n.x:0,y:ca(n.y)?n.y:0})}else ua(t)&&(e=la(t))}else r&&ua(t)&&(e=la(t));e&&(\"scrollBehavior\"in document.documentElement.style?window.scrollTo({left:e.x,top:e.y,behavior:t.behavior}):window.scrollTo(e.x,e.y))}var da,ha=Ki&&((-1===(da=window.navigator.userAgent).indexOf(\"Android 2.\")&&-1===da.indexOf(\"Android 4.0\")||-1===da.indexOf(\"Mobile Safari\")||-1!==da.indexOf(\"Chrome\")||-1!==da.indexOf(\"Windows Phone\"))&&window.history&&\"function\"==typeof window.history.pushState);function va(t,e){aa();var n=window.history;try{if(e){var r=ni({},n.state);r.key=ea(),n.replaceState(r,\"\",t)}else n.pushState({key:na(Zi())},\"\",t)}catch(n){window.location[e?\"replace\":\"assign\"](t)}}function ma(t){va(t,!0)}var ga={redirected:2,aborted:4,cancelled:8,duplicated:16};function ya(t,e){return _a(t,e,ga.redirected,'Redirected when going from \"'+t.fullPath+'\" to \"'+function(t){if(\"string\"==typeof t)return t;if(\"path\"in t)return t.path;var e={};return xa.forEach((function(n){n in t&&(e[n]=t[n])})),JSON.stringify(e,null,2)}(e)+'\" via a navigation guard.')}function ba(t,e){return _a(t,e,ga.cancelled,'Navigation cancelled from \"'+t.fullPath+'\" to \"'+e.fullPath+'\" with a new navigation.')}function _a(t,e,n,r){var o=new Error(r);return o._isRouter=!0,o.from=t,o.to=e,o.type=n,o}var xa=[\"params\",\"query\",\"hash\"];function wa(t){return Object.prototype.toString.call(t).indexOf(\"Error\")>-1}function ka(t,e){return wa(t)&&t._isRouter&&(null==e||t.type===e)}function $a(t,e,n){var r=function(o){o>=t.length?n():t[o]?e(t[o],(function(){r(o+1)})):r(o+1)};r(0)}function Ca(t){return function(e,n,r){var o=!1,i=0,a=null;Oa(t,(function(t,e,n,s){if(\"function\"==typeof t&&void 0===t.cid){o=!0,i++;var u,l=Ea((function(e){var o;((o=e).__esModule||Sa&&\"Module\"===o[Symbol.toStringTag])&&(e=e.default),t.resolved=\"function\"==typeof e?e:qi.extend(e),n.components[s]=e,--i<=0&&r()})),c=Ea((function(t){var e=\"Failed to resolve async component \"+s+\": \"+t;a||(a=wa(t)?t:new Error(e),r(a))}));try{u=t(l,c)}catch(t){c(t)}if(u)if(\"function\"==typeof u.then)u.then(l,c);else{var f=u.component;f&&\"function\"==typeof f.then&&f.then(l,c)}}})),o||r()}}function Oa(t,e){return ja(t.map((function(t){return Object.keys(t.components).map((function(n){return e(t.components[n],t.instances[n],t,n)}))})))}function ja(t){return Array.prototype.concat.apply([],t)}var Sa=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.toStringTag;function Ea(t){var e=!1;return function(){for(var n=[],r=arguments.length;r--;)n[r]=arguments[r];if(!e)return e=!0,t.apply(this,n)}}var Pa=function(t,e){this.router=t,this.base=function(t){if(!t)if(Ki){var e=document.querySelector(\"base\");t=(t=e&&e.getAttribute(\"href\")||\"/\").replace(/^https?:\\/\\/[^\\/]+/,\"\")}else t=\"/\";\"/\"!==t.charAt(0)&&(t=\"/\"+t);return t.replace(/\\/$/,\"\")}(e),this.current=hi,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function Aa(t,e,n,r){var o=Oa(t,(function(t,r,o,i){var a=function(t,e){\"function\"!=typeof t&&(t=qi.extend(t));return t.options[e]}(t,e);if(a)return Array.isArray(a)?a.map((function(t){return n(t,r,o,i)})):n(a,r,o,i)}));return ja(r?o.reverse():o)}function Ta(t,e){if(e)return function(){return t.apply(e,arguments)}}Pa.prototype.listen=function(t){this.cb=t},Pa.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},Pa.prototype.onError=function(t){this.errorCbs.push(t)},Pa.prototype.transitionTo=function(t,e,n){var r,o=this;try{r=this.router.match(t,this.current)}catch(t){throw this.errorCbs.forEach((function(e){e(t)})),t}var i=this.current;this.confirmTransition(r,(function(){o.updateRoute(r),e&&e(r),o.ensureURL(),o.router.afterHooks.forEach((function(t){t&&t(r,i)})),o.ready||(o.ready=!0,o.readyCbs.forEach((function(t){t(r)})))}),(function(t){n&&n(t),t&&!o.ready&&(ka(t,ga.redirected)&&i===hi||(o.ready=!0,o.readyErrorCbs.forEach((function(e){e(t)}))))}))},Pa.prototype.confirmTransition=function(t,e,n){var r=this,o=this.current;this.pending=t;var i,a,s=function(t){!ka(t)&&wa(t)&&(r.errorCbs.length?r.errorCbs.forEach((function(e){e(t)})):console.error(t)),n&&n(t)},u=t.matched.length-1,l=o.matched.length-1;if(gi(t,o)&&u===l&&t.matched[u]===o.matched[l])return this.ensureURL(),t.hash&&ia(this.router,o,t,!1),s(((a=_a(i=o,t,ga.duplicated,'Avoided redundant navigation to current location: \"'+i.fullPath+'\".')).name=\"NavigationDuplicated\",a));var c=function(t,e){var n,r=Math.max(t.length,e.length);for(n=0;n<r&&t[n]===e[n];n++);return{updated:e.slice(0,n),activated:e.slice(n),deactivated:t.slice(n)}}(this.current.matched,t.matched),f=c.updated,p=c.deactivated,d=c.activated,h=[].concat(function(t){return Aa(t,\"beforeRouteLeave\",Ta,!0)}(p),this.router.beforeHooks,function(t){return Aa(t,\"beforeRouteUpdate\",Ta)}(f),d.map((function(t){return t.beforeEnter})),Ca(d)),v=function(e,n){if(r.pending!==t)return s(ba(o,t));try{e(t,o,(function(e){!1===e?(r.ensureURL(!0),s(function(t,e){return _a(t,e,ga.aborted,'Navigation aborted from \"'+t.fullPath+'\" to \"'+e.fullPath+'\" via a navigation guard.')}(o,t))):wa(e)?(r.ensureURL(!0),s(e)):\"string\"==typeof e||\"object\"==typeof e&&(\"string\"==typeof e.path||\"string\"==typeof e.name)?(s(ya(o,t)),\"object\"==typeof e&&e.replace?r.replace(e):r.push(e)):n(e)}))}catch(t){s(t)}};$a(h,v,(function(){$a(function(t){return Aa(t,\"beforeRouteEnter\",(function(t,e,n,r){return function(t,e,n){return function(r,o,i){return t(r,o,(function(t){\"function\"==typeof t&&(e.enteredCbs[n]||(e.enteredCbs[n]=[]),e.enteredCbs[n].push(t)),i(t)}))}}(t,n,r)}))}(d).concat(r.router.resolveHooks),v,(function(){if(r.pending!==t)return s(ba(o,t));r.pending=null,e(t),r.router.app&&r.router.app.$nextTick((function(){bi(t)}))}))}))},Pa.prototype.updateRoute=function(t){this.current=t,this.cb&&this.cb(t)},Pa.prototype.setupListeners=function(){},Pa.prototype.teardown=function(){this.listeners.forEach((function(t){t()})),this.listeners=[],this.current=hi,this.pending=null};var La=function(t){function e(e,n){t.call(this,e,n),this._startLocation=Ra(this.base)}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router,n=e.options.scrollBehavior,r=ha&&n;r&&this.listeners.push(oa());var o=function(){var n=t.current,o=Ra(t.base);t.current===hi&&o===t._startLocation||t.transitionTo(o,(function(t){r&&ia(e,t,n,!0)}))};window.addEventListener(\"popstate\",o),this.listeners.push((function(){window.removeEventListener(\"popstate\",o)}))}},e.prototype.go=function(t){window.history.go(t)},e.prototype.push=function(t,e,n){var r=this,o=this.current;this.transitionTo(t,(function(t){va(ki(r.base+t.fullPath)),ia(r.router,t,o,!1),e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this,o=this.current;this.transitionTo(t,(function(t){ma(ki(r.base+t.fullPath)),ia(r.router,t,o,!1),e&&e(t)}),n)},e.prototype.ensureURL=function(t){if(Ra(this.base)!==this.current.fullPath){var e=ki(this.base+this.current.fullPath);t?va(e):ma(e)}},e.prototype.getCurrentLocation=function(){return Ra(this.base)},e}(Pa);function Ra(t){var e=window.location.pathname,n=e.toLowerCase(),r=t.toLowerCase();return!t||n!==r&&0!==n.indexOf(ki(r+\"/\"))||(e=e.slice(t.length)),(e||\"/\")+window.location.search+window.location.hash}var Ia=function(t){function e(e,n,r){t.call(this,e,n),r&&function(t){var e=Ra(t);if(!/^\\/#/.test(e))return window.location.replace(ki(t+\"/#\"+e)),!0}(this.base)||Ma()}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router.options.scrollBehavior,n=ha&&e;n&&this.listeners.push(oa());var r=function(){var e=t.current;Ma()&&t.transitionTo(Da(),(function(r){n&&ia(t.router,r,e,!0),ha||Fa(r.fullPath)}))},o=ha?\"popstate\":\"hashchange\";window.addEventListener(o,r),this.listeners.push((function(){window.removeEventListener(o,r)}))}},e.prototype.push=function(t,e,n){var r=this,o=this.current;this.transitionTo(t,(function(t){Ua(t.fullPath),ia(r.router,t,o,!1),e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this,o=this.current;this.transitionTo(t,(function(t){Fa(t.fullPath),ia(r.router,t,o,!1),e&&e(t)}),n)},e.prototype.go=function(t){window.history.go(t)},e.prototype.ensureURL=function(t){var e=this.current.fullPath;Da()!==e&&(t?Ua(e):Fa(e))},e.prototype.getCurrentLocation=function(){return Da()},e}(Pa);function Ma(){var t=Da();return\"/\"===t.charAt(0)||(Fa(\"/\"+t),!1)}function Da(){var t=window.location.href,e=t.indexOf(\"#\");return e<0?\"\":t=t.slice(e+1)}function Na(t){var e=window.location.href,n=e.indexOf(\"#\");return(n>=0?e.slice(0,n):e)+\"#\"+t}function Ua(t){ha?va(Na(t)):window.location.hash=t}function Fa(t){ha?ma(Na(t)):window.location.replace(Na(t))}var za=function(t){function e(e,n){t.call(this,e,n),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index+1).concat(t),r.index++,e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index).concat(t),e&&e(t)}),n)},e.prototype.go=function(t){var e=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){var t=e.current;e.index=n,e.updateRoute(r),e.router.afterHooks.forEach((function(e){e&&e(r,t)}))}),(function(t){ka(t,ga.duplicated)&&(e.index=n)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:\"/\"},e.prototype.ensureURL=function(){},e}(Pa),Ba=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Qi(t.routes||[],this);var e=t.mode||\"hash\";switch(this.fallback=\"history\"===e&&!ha&&!1!==t.fallback,this.fallback&&(e=\"hash\"),Ki||(e=\"abstract\"),this.mode=e,e){case\"history\":this.history=new La(this,t.base);break;case\"hash\":this.history=new Ia(this,t.base,this.fallback);break;case\"abstract\":this.history=new za(this,t.base);break;default:0}},qa={currentRoute:{configurable:!0}};Ba.prototype.match=function(t,e,n){return this.matcher.match(t,e,n)},qa.currentRoute.get=function(){return this.history&&this.history.current},Ba.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once(\"hook:destroyed\",(function(){var n=e.apps.indexOf(t);n>-1&&e.apps.splice(n,1),e.app===t&&(e.app=e.apps[0]||null),e.app||e.history.teardown()})),!this.app){this.app=t;var n=this.history;if(n instanceof La||n instanceof Ia){var r=function(t){n.setupListeners(),function(t){var r=n.current,o=e.options.scrollBehavior;ha&&o&&\"fullPath\"in t&&ia(e,t,r,!1)}(t)};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},Ba.prototype.beforeEach=function(t){return Ha(this.beforeHooks,t)},Ba.prototype.beforeResolve=function(t){return Ha(this.resolveHooks,t)},Ba.prototype.afterEach=function(t){return Ha(this.afterHooks,t)},Ba.prototype.onReady=function(t,e){this.history.onReady(t,e)},Ba.prototype.onError=function(t){this.history.onError(t)},Ba.prototype.push=function(t,e,n){var r=this;if(!e&&!n&&\"undefined\"!=typeof Promise)return new Promise((function(e,n){r.history.push(t,e,n)}));this.history.push(t,e,n)},Ba.prototype.replace=function(t,e,n){var r=this;if(!e&&!n&&\"undefined\"!=typeof Promise)return new Promise((function(e,n){r.history.replace(t,e,n)}));this.history.replace(t,e,n)},Ba.prototype.go=function(t){this.history.go(t)},Ba.prototype.back=function(){this.go(-1)},Ba.prototype.forward=function(){this.go(1)},Ba.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},Ba.prototype.resolve=function(t,e,n){var r=Bi(t,e=e||this.history.current,n,this),o=this.match(r,e),i=o.redirectedFrom||o.fullPath;return{location:r,route:o,href:function(t,e,n){var r=\"hash\"===n?\"#\"+e:e;return t?ki(t+\"/\"+r):r}(this.history.base,i,this.mode),normalizedTo:r,resolved:o}},Ba.prototype.getRoutes=function(){return this.matcher.getRoutes()},Ba.prototype.addRoute=function(t,e){this.matcher.addRoute(t,e),this.history.current!==hi&&this.history.transitionTo(this.history.getCurrentLocation())},Ba.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==hi&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(Ba.prototype,qa);var Va=Ba;function Ha(t,e){return t.push(e),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}Ba.install=function t(e){if(!t.installed||qi!==e){t.installed=!0,qi=e;var n=function(t){return void 0!==t},r=function(t,e){var r=t.$options._parentVnode;n(r)&&n(r=r.data)&&n(r=r.registerRouteInstance)&&r(t,e)};e.mixin({beforeCreate:function(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,\"_route\",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,r(this,this)},destroyed:function(){r(this)}}),Object.defineProperty(e.prototype,\"$router\",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,\"$route\",{get:function(){return this._routerRoot._route}}),e.component(\"RouterView\",_i),e.component(\"RouterLink\",Hi);var o=e.config.optionMergeStrategies;o.beforeRouteEnter=o.beforeRouteLeave=o.beforeRouteUpdate=o.created}},Ba.version=\"3.6.5\",Ba.isNavigationFailure=ka,Ba.NavigationFailureType=ga,Ba.START_LOCATION=hi,Ki&&window.Vue&&window.Vue.use(Ba);var Wa={NotFound:()=>n.e(8).then(n.bind(null,189)),Layout:()=>Promise.all([n.e(0),n.e(2)]).then(n.bind(null,188))},Ka={\"v-0021b1c6\":()=>n.e(9).then(n.bind(null,192)),\"v-5a489dbc\":()=>n.e(10).then(n.bind(null,193)),\"v-dfd9a036\":()=>n.e(12).then(n.bind(null,194)),\"v-20f3246c\":()=>n.e(11).then(n.bind(null,195)),\"v-029c9c4c\":()=>n.e(15).then(n.bind(null,196)),\"v-69670f28\":()=>n.e(14).then(n.bind(null,197)),\"v-7b49570c\":()=>n.e(13).then(n.bind(null,198)),\"v-23a116c5\":()=>n.e(16).then(n.bind(null,199)),\"v-e6c4f29c\":()=>n.e(18).then(n.bind(null,200)),\"v-030e74f6\":()=>n.e(20).then(n.bind(null,201)),\"v-6c7741a8\":()=>n.e(19).then(n.bind(null,202)),\"v-161c12a4\":()=>n.e(17).then(n.bind(null,203)),\"v-e4ce20e8\":()=>n.e(21).then(n.bind(null,204)),\"v-781440a8\":()=>n.e(24).then(n.bind(null,205)),\"v-b5147228\":()=>n.e(22).then(n.bind(null,206)),\"v-41e6ca06\":()=>n.e(23).then(n.bind(null,207)),\"v-2e731868\":()=>n.e(25).then(n.bind(null,208)),\"v-97f1ad76\":()=>n.e(26).then(n.bind(null,209)),\"v-42ac2168\":()=>n.e(27).then(n.bind(null,210)),\"v-dd310fa2\":()=>n.e(29).then(n.bind(null,211)),\"v-1296e068\":()=>n.e(32).then(n.bind(null,212)),\"v-28042a88\":()=>n.e(30).then(n.bind(null,213)),\"v-077f963a\":()=>n.e(28).then(n.bind(null,214)),\"v-6c9d134c\":()=>n.e(31).then(n.bind(null,215))};function Ja(t){const e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}const Ga=/-(\\w)/g,Qa=Ja(t=>t.replace(Ga,(t,e)=>e?e.toUpperCase():\"\")),Xa=/\\B([A-Z])/g,Ya=Ja(t=>t.replace(Xa,\"-$1\").toLowerCase()),Za=Ja(t=>t.charAt(0).toUpperCase()+t.slice(1));function ts(t,e){if(!e)return;if(t(e))return t(e);return e.includes(\"-\")?t(Za(Qa(e))):t(Za(e))||t(Ya(e))}const es=Object.assign({},Wa,Ka),ns=t=>es[t],rs=t=>Ka[t],os=t=>Wa[t],is=t=>Wn.component(t);function as(t){return ts(rs,t)}function ss(t){return ts(os,t)}function us(t){return ts(ns,t)}function ls(t){return ts(is,t)}function cs(...t){return Promise.all(t.filter(t=>t).map(async t=>{if(!ls(t)&&us(t)){const e=await us(t)();Wn.component(t,e.default)}}))}function fs(t,e){\"undefined\"!=typeof window&&window.__VUEPRESS__&&(window.__VUEPRESS__[t]=e)}var ps=n(45),ds=n.n(ps),hs=n(46),vs=n.n(hs),ms={created(){if(this.siteMeta=this.$site.headTags.filter(([t])=>\"meta\"===t).map(([t,e])=>e),this.$ssrContext){const e=this.getMergedMetaTags();this.$ssrContext.title=this.$title,this.$ssrContext.lang=this.$lang,this.$ssrContext.pageMeta=(t=e)?t.map(t=>{let e=\"<meta\";return Object.keys(t).forEach(n=>{e+=` ${n}=\"${vs()(t[n])}\"`}),e+\">\"}).join(\"\\n    \"):\"\",this.$ssrContext.canonicalLink=ys(this.$canonicalUrl)}var t},mounted(){this.currentMetaTags=[...document.querySelectorAll(\"meta\")],this.updateMeta(),this.updateCanonicalLink()},methods:{updateMeta(){document.title=this.$title,document.documentElement.lang=this.$lang;const t=this.getMergedMetaTags();this.currentMetaTags=bs(t,this.currentMetaTags)},getMergedMetaTags(){const t=this.$page.frontmatter.meta||[];return ds()([{name:\"description\",content:this.$description}],t,this.siteMeta,_s)},updateCanonicalLink(){gs(),this.$canonicalUrl&&document.head.insertAdjacentHTML(\"beforeend\",ys(this.$canonicalUrl))}},watch:{$page(){this.updateMeta(),this.updateCanonicalLink()}},beforeDestroy(){bs(null,this.currentMetaTags),gs()}};function gs(){const t=document.querySelector(\"link[rel='canonical']\");t&&t.remove()}function ys(t=\"\"){return t?`<link href=\"${t}\" rel=\"canonical\" />`:\"\"}function bs(t,e){if(e&&[...e].filter(t=>t.parentNode===document.head).forEach(t=>document.head.removeChild(t)),t)return t.map(t=>{const e=document.createElement(\"meta\");return Object.keys(t).forEach(n=>{e.setAttribute(n,t[n])}),document.head.appendChild(e),e})}function _s(t){for(const e of[\"name\",\"property\",\"itemprop\"])if(t.hasOwnProperty(e))return t[e]+e;return JSON.stringify(t)}var xs=n(47),ws={mounted(){window.addEventListener(\"scroll\",this.onScroll)},methods:{onScroll:n.n(xs)()((function(){this.setActiveHash()}),300),setActiveHash(){const t=[].slice.call(document.querySelectorAll(\".sidebar-link\")),e=[].slice.call(document.querySelectorAll(\".header-anchor\")).filter(e=>t.some(t=>t.hash===e.hash)),n=Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop),r=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),o=window.innerHeight+n;for(let t=0;t<e.length;t++){const i=e[t],a=e[t+1],s=0===t&&0===n||n>=i.parentElement.offsetTop+10&&(!a||n<a.parentElement.offsetTop-10),u=decodeURIComponent(this.$route.hash);if(s&&u!==decodeURIComponent(i.hash)){const n=i;if(o===r)for(let n=t+1;n<e.length;n++)if(u===decodeURIComponent(e[n].hash))return;return this.$vuepress.$set(\"disableScrollBehavior\",!0),void this.$router.replace(decodeURIComponent(n.hash),()=>{this.$nextTick(()=>{this.$vuepress.$set(\"disableScrollBehavior\",!1)})})}}}},beforeDestroy(){window.removeEventListener(\"scroll\",this.onScroll)}},ks=n(12),$s=n.n(ks),Cs=[ms,ws,{mounted(){$s.a.configure({showSpinner:!1}),this.$router.beforeEach((t,e,n)=>{t.path===e.path||Wn.component(t.name)||$s.a.start(),n()}),this.$router.afterEach(()=>{$s.a.done(),this.isSidebarOpen=!1})}}],Os={name:\"GlobalLayout\",computed:{layout(){const t=this.getLayout();return fs(\"layout\",t),Wn.component(t)}},methods:{getLayout(){if(this.$page.path){const t=this.$page.frontmatter.layout;return t&&(this.$vuepress.getLayoutAsyncComponent(t)||this.$vuepress.getVueComponent(t))?t:\"Layout\"}return\"NotFound\"}}},js=n(6),Ss=Object(js.a)(Os,(function(){return(0,this._self._c)(this.layout,{tag:\"component\"})}),[],!1,null,null,null).exports;!function(t,e,n){switch(e){case\"components\":t[e]||(t[e]={}),Object.assign(t[e],n);break;case\"mixins\":t[e]||(t[e]=[]),t[e].push(...n);break;default:throw new Error(\"Unknown option name.\")}}(Ss,\"mixins\",Cs);const Es=[{name:\"v-0021b1c6\",path:\"/guide/alternatives.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-0021b1c6\").then(n)}},{name:\"v-5a489dbc\",path:\"/guide/custom-parsers.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-5a489dbc\").then(n)}},{name:\"v-dfd9a036\",path:\"/guide/\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-dfd9a036\").then(n)}},{path:\"/guide/index.html\",redirect:\"/guide/\"},{name:\"v-20f3246c\",path:\"/guide/getting-started.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-20f3246c\").then(n)}},{name:\"v-029c9c4c\",path:\"/guide/tutorials.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-029c9c4c\").then(n)}},{name:\"v-69670f28\",path:\"/guide/syntax-introduction.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-69670f28\").then(n)}},{name:\"v-7b49570c\",path:\"/guide/install.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-7b49570c\").then(n)}},{name:\"v-23a116c5\",path:\"/\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-23a116c5\").then(n)}},{path:\"/index.html\",redirect:\"/\"},{name:\"v-e6c4f29c\",path:\"/plugin/dynamic.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-e6c4f29c\").then(n)}},{name:\"v-030e74f6\",path:\"/plugin/\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-030e74f6\").then(n)}},{path:\"/plugin/index.html\",redirect:\"/plugin/\"},{name:\"v-6c7741a8\",path:\"/plugin/hoover.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-6c7741a8\").then(n)}},{name:\"v-161c12a4\",path:\"/plugin/csv.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-161c12a4\").then(n)}},{name:\"v-e4ce20e8\",path:\"/plugin/json.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-e4ce20e8\").then(n)}},{name:\"v-781440a8\",path:\"/plugin/native.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-781440a8\").then(n)}},{name:\"v-b5147228\",path:\"/plugin/legacy-stringify.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-b5147228\").then(n)}},{name:\"v-41e6ca06\",path:\"/plugin/multifile.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-41e6ca06\").then(n)}},{name:\"v-2e731868\",path:\"/ref/api.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-2e731868\").then(n)}},{name:\"v-97f1ad76\",path:\"/ref/\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-97f1ad76\").then(n)}},{path:\"/ref/index.html\",redirect:\"/ref/\"},{name:\"v-42ac2168\",path:\"/ref/options.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-42ac2168\").then(n)}},{name:\"v-dd310fa2\",path:\"/tutorial/\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-dd310fa2\").then(n)}},{path:\"/tutorial/index.html\",redirect:\"/tutorial/\"},{name:\"v-1296e068\",path:\"/tutorial/write-a-plugin.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-1296e068\").then(n)}},{name:\"v-28042a88\",path:\"/tutorial/parsing-csv.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-28042a88\").then(n)}},{name:\"v-077f963a\",path:\"/ref/syntax.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-077f963a\").then(n)}},{name:\"v-6c9d134c\",path:\"/tutorial/write-a-parser.html\",component:Ss,beforeEnter:(t,e,n)=>{cs(\"Layout\",\"v-6c9d134c\").then(n)}},{path:\"*\",component:Ss}],Ps={title:\"Jsonic\",description:\"A JSON parser for Node.js that isn't strict.\",base:\"/\",headTags:[[\"link\",{rel:\"stylesheet\",href:\"/railroad-diagrams.css\"}]],pages:[{title:\"Alternatives\",frontmatter:{},regularPath:\"/guide/alternatives.html\",relativePath:\"guide/alternatives.md\",key:\"v-0021b1c6\",path:\"/guide/alternatives.html\",headers:[{level:2,title:\"Foo\",slug:\"foo\"}]},{title:\"Custom parsers\",frontmatter:{},regularPath:\"/guide/custom-parsers.html\",relativePath:\"guide/custom-parsers.md\",key:\"v-5a489dbc\",path:\"/guide/custom-parsers.html\"},{title:\"Guide\",frontmatter:{},regularPath:\"/guide/\",relativePath:\"guide/index.md\",key:\"v-dfd9a036\",path:\"/guide/\"},{title:\"Getting Started\",frontmatter:{},regularPath:\"/guide/getting-started.html\",relativePath:\"guide/getting-started.md\",key:\"v-20f3246c\",path:\"/guide/getting-started.html\",headers:[{level:2,title:\"Parse some easy-going JSON!\",slug:\"parse-some-easy-going-json\"},{level:2,title:\"What does jsonic syntax give you?\",slug:\"what-does-jsonic-syntax-give-you\"},{level:3,title:\"Trailing commas are ignored\",slug:\"trailing-commas-are-ignored\"},{level:3,title:\"Object keys don't need to to be quoted\",slug:\"object-keys-don-t-need-to-to-be-quoted\"},{level:3,title:\"Single quote strings work\",slug:\"single-quote-strings-work\"},{level:3,title:\"Backtick multi-line strings work\",slug:\"backtick-multi-line-strings-work\"},{level:3,title:\"You get all the comments\",slug:\"you-get-all-the-comments\"},{level:3,title:\"Actually, who needs commas when newlines will do\",slug:\"actually-who-needs-commas-when-newlines-will-do\"},{level:3,title:\"And quoting every string is tedious, right?\",slug:\"and-quoting-every-string-is-tedious-right\"},{level:3,title:\"Don't worry about spaces inside text either\",slug:\"don-t-worry-about-spaces-inside-text-either\"},{level:3,title:\"Implicit objects and arrays at the top level\",slug:\"implicit-objects-and-arrays-at-the-top-level\"},{level:3,title:\"All the JavaScript numbers and string escapes\",slug:\"all-the-javascript-numbers-and-string-escapes\"},{level:3,title:\"Block text with indent removal\",slug:\"block-text-with-indent-removal\"},{level:3,title:\"Deep property one liners\",slug:\"deep-property-one-liners\"},{level:3,title:\"Merging of duplicate properties\",slug:\"merging-of-duplicate-properties\"},{level:2,title:\"Developer-friendly error messages\",slug:\"developer-friendly-error-messages\"},{level:2,title:\"Nice, but what does it all cost?\",slug:\"nice-but-what-does-it-all-cost\"},{level:2,title:\"Customizable and extendable\",slug:\"customizable-and-extendable\"}]},{title:\"Tutorials\",frontmatter:{},regularPath:\"/guide/tutorials.html\",relativePath:\"guide/tutorials.md\",key:\"v-029c9c4c\",path:\"/guide/tutorials.html\",headers:[{level:2,title:\"Writing a plugin\",slug:\"writing-a-plugin\"},{level:2,title:\"Parsing CSV into JSON\",slug:\"parsing-csv-into-json\"}]},{title:\"Syntax introduction\",frontmatter:{},regularPath:\"/guide/syntax-introduction.html\",relativePath:\"guide/syntax-introduction.md\",key:\"v-69670f28\",path:\"/guide/syntax-introduction.html\",headers:[{level:2,title:\"Developer experience\",slug:\"developer-experience\"},{level:2,title:\"Walkthrough\",slug:\"walkthrough\"},{level:3,title:\"Comments\",slug:\"comments\"},{level:3,title:\"Keys\",slug:\"keys\"},{level:3,title:\"Commas\",slug:\"commas\"},{level:3,title:\"Strings\",slug:\"strings\"},{level:3,title:\"Numbers\",slug:\"numbers\"},{level:3,title:\"Merges\",slug:\"merges\"},{level:3,title:\"Shortcuts\",slug:\"shortcuts\"},{level:3,title:\"Things that still aren't allowed\",slug:\"things-that-still-aren-t-allowed\"}]},{title:\"Install\",frontmatter:{},regularPath:\"/guide/install.html\",relativePath:\"guide/install.md\",key:\"v-7b49570c\",path:\"/guide/install.html\",headers:[{level:2,title:\"Node.js\",slug:\"node-js\"},{level:2,title:\"ES Module\",slug:\"es-module\"},{level:2,title:\"Typescript\",slug:\"typescript\"},{level:2,title:\"Browser\",slug:\"browser\"},{level:2,title:\"Getting Help\",slug:\"getting-help\"},{level:2,title:\"Next Steps\",slug:\"next-steps\"}]},{title:\"Home\",frontmatter:{home:!0,heroImage:\"/jsonic-logo.svg\",actionText:\"Getting Started →\",actionLink:\"/guide/getting-started.html\"},regularPath:\"/\",relativePath:\"index.md\",key:\"v-23a116c5\",path:\"/\"},{title:\"dynamic\",frontmatter:{},regularPath:\"/plugin/dynamic.html\",relativePath:\"plugin/dynamic.md\",key:\"v-e6c4f29c\",path:\"/plugin/dynamic.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Dynamic value keywords\",slug:\"dynamic-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"Plugins\",frontmatter:{},regularPath:\"/plugin/\",relativePath:\"plugin/index.md\",key:\"v-030e74f6\",path:\"/plugin/\",headers:[{level:2,title:\"Built-in plugins\",slug:\"built-in-plugins\"},{level:3,title:\"native\",slug:\"native\"},{level:3,title:\"csv\",slug:\"csv\"},{level:3,title:\"hoover\",slug:\"hoover\"},{level:3,title:\"json\",slug:\"json\"},{level:3,title:\"dynamic\",slug:\"dynamic\"},{level:3,title:\"multifile\",slug:\"multifile\"},{level:3,title:\"legacy-stringify\",slug:\"legacy-stringify\"},{level:2,title:\"Standard plugins\",slug:\"standard-plugins\"},{level:2,title:\"Community plugins\",slug:\"community-plugins\"}]},{title:\"hoover\",frontmatter:{},regularPath:\"/plugin/hoover.html\",relativePath:\"plugin/hoover.md\",key:\"v-6c7741a8\",path:\"/plugin/hoover.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Hoover value keywords\",slug:\"hoover-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"csv\",frontmatter:{},regularPath:\"/plugin/csv.html\",relativePath:\"plugin/csv.md\",key:\"v-161c12a4\",path:\"/plugin/csv.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Csv value keywords\",slug:\"csv-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"json\",frontmatter:{},regularPath:\"/plugin/json.html\",relativePath:\"plugin/json.md\",key:\"v-e4ce20e8\",path:\"/plugin/json.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Json value keywords\",slug:\"json-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"native\",frontmatter:{},regularPath:\"/plugin/native.html\",relativePath:\"plugin/native.md\",key:\"v-781440a8\",path:\"/plugin/native.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Native value keywords\",slug:\"native-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"legacy-stringify\",frontmatter:{},regularPath:\"/plugin/legacy-stringify.html\",relativePath:\"plugin/legacy-stringify.md\",key:\"v-b5147228\",path:\"/plugin/legacy-stringify.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Legacy-Stringify value keywords\",slug:\"legacy-stringify-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"multifile\",frontmatter:{},regularPath:\"/plugin/multifile.html\",relativePath:\"plugin/multifile.md\",key:\"v-41e6ca06\",path:\"/plugin/multifile.html\",headers:[{level:2,title:\"Usage\",slug:\"usage\"},{level:3,title:\"Quick example\",slug:\"quick-example\"},{level:2,title:\"Syntax\",slug:\"syntax\"},{level:3,title:\"Multifile value keywords\",slug:\"multifile-value-keywords\"},{level:3,title:\"Regular Expressions\",slug:\"regular-expressions\"},{level:3,title:\"ISO Dates\",slug:\"iso-dates\"},{level:2,title:\"Options\",slug:\"options\"},{level:2,title:\"Implementation\",slug:\"implementation\"}]},{title:\"API\",frontmatter:{},regularPath:\"/ref/api.html\",relativePath:\"ref/api.md\",key:\"v-2e731868\",path:\"/ref/api.html\",headers:[{level:2,title:\"Methods\",slug:\"methods\"},{level:3,title:\"🍓 🍐 Jsonic: Just parse already!\",slug:\"jsonic-just-parse-already\"},{level:3,title:\"🍐 id: Unique instance identifier\",slug:\"id-unique-instance-identifier\"},{level:3,title:\"🍓 toString: String description of the Jsonic instance\",slug:\"tostring-string-description-of-the-jsonic-instance\"},{level:3,title:\"🍓 make: Create a new customizable Jsonic instance.\",slug:\"make-create-a-new-customizable-jsonic-instance\"},{level:3,title:\"🍓 🍐 options: Get and set options for a Jsonic instance.\",slug:\"options-get-and-set-options-for-a-jsonic-instance\"},{level:3,title:\"🍒 🍓 use: Register a plugin.\",slug:\"use-register-a-plugin\"},{level:3,title:\"🍒 🍓 rule: Define or modify a parser rule.\",slug:\"rule-define-or-modify-a-parser-rule\"},{level:3,title:\"🍒 🍓 lex: Define a lex matcher.\",slug:\"lex-define-a-lex-matcher\"},{level:3,title:\"🍒 🍓 🍐 token: Resolve a token by name or index.\",slug:\"token-resolve-a-token-by-name-or-index\"}]},{title:\"Reference\",frontmatter:{},regularPath:\"/ref/\",relativePath:\"ref/index.md\",key:\"v-97f1ad76\",path:\"/ref/\"},{title:\"Options\",frontmatter:{sidebarDepth:2},regularPath:\"/ref/options.html\",relativePath:\"ref/options.md\",key:\"v-42ac2168\",path:\"/ref/options.html\",headers:[{level:2,title:\"tag\",slug:\"tag\"},{level:2,title:\"line\",slug:\"line\"},{level:3,title:\"line.lex\",slug:\"line-lex\"},{level:3,title:\"line.row\",slug:\"line-row\"},{level:3,title:\"line.sep\",slug:\"line-sep\"},{level:2,title:\"comment\",slug:\"comment\"},{level:3,title:\"comment.lex\",slug:\"comment-lex\"},{level:3,title:\"comment.balance\",slug:\"comment-balance\"},{level:3,title:\"comment.marker\",slug:\"comment-marker\"},{level:2,title:\"space\",slug:\"space\"},{level:3,title:\"space.lex\",slug:\"space-lex\"},{level:2,title:\"number\",slug:\"number\"},{level:3,title:\"number.lex\",slug:\"number-lex\"},{level:3,title:\"number.hex\",slug:\"number-hex\"},{level:3,title:\"number.oct\",slug:\"number-oct\"},{level:3,title:\"number.bin\",slug:\"number-bin\"},{level:3,title:\"number.digital\",slug:\"number-digital\"},{level:3,title:\"number.sep\",slug:\"number-sep\"},{level:2,title:\"block\",slug:\"block\"},{level:3,title:\"block.lex: true\",slug:\"block-lex-true\"},{level:3,title:\"block.marker\",slug:\"block-marker\"},{level:2,title:\"string\",slug:\"string\"},{level:3,title:\"string.lex\",slug:\"string-lex\"},{level:3,title:\"string.escape\",slug:\"string-escape\"},{level:3,title:\"string.multiline\",slug:\"string-multiline\"},{level:3,title:\"string.escapedouble\",slug:\"string-escapedouble\"},{level:2,title:\"text\",slug:\"text\"},{level:3,title:\"text.lex\",slug:\"text-lex\"},{level:2,title:\"map\",slug:\"map\"},{level:3,title:\"map.extend\",slug:\"map-extend\"},{level:2,title:\"value\",slug:\"value\"},{level:3,title:\"value.lex\",slug:\"value-lex\"},{level:3,title:\"value.src:\",slug:\"value-src\"},{level:3,title:\"value.src:\",slug:\"value-src-2\"},{level:2,title:\"plugin\",slug:\"plugin\"},{level:2,title:\"debug\",slug:\"debug\"},{level:3,title:\"debug.get_console\",slug:\"debug-get-console\"},{level:3,title:\"debug.maxlen\",slug:\"debug-maxlen\"},{level:3,title:\"debug.print\",slug:\"debug-print\"},{level:3,title:\"debug.print.config\",slug:\"debug-print-config\"},{level:2,title:\"error\",slug:\"error\"},{level:2,title:\"hint\",slug:\"hint\"},{level:2,title:\"token\",slug:\"token\"},{level:2,title:\"rule\",slug:\"rule\"},{level:3,title:\"rule.start\",slug:\"rule-start\"},{level:3,title:\"rule.finish\",slug:\"rule-finish\"},{level:3,title:\"rule.maxmul\",slug:\"rule-maxmul\"},{level:2,title:\"config\",slug:\"config\"},{level:3,title:\"config.modify\",slug:\"config-modify\"},{level:2,title:\"parser\",slug:\"parser\"},{level:3,title:\"parser.start\",slug:\"parser-start\"}]},{title:\"Tutorials\",frontmatter:{},regularPath:\"/tutorial/\",relativePath:\"tutorial/index.md\",key:\"v-dd310fa2\",path:\"/tutorial/\"},{title:\"Write a plugin\",frontmatter:{},regularPath:\"/tutorial/write-a-plugin.html\",relativePath:\"tutorial/write-a-plugin.md\",key:\"v-1296e068\",path:\"/tutorial/write-a-plugin.html\"},{title:\"Parsing CSV\",frontmatter:{},regularPath:\"/tutorial/parsing-csv.html\",relativePath:\"tutorial/parsing-csv.md\",key:\"v-28042a88\",path:\"/tutorial/parsing-csv.html\"},{title:\"Syntax\",frontmatter:{},regularPath:\"/ref/syntax.html\",relativePath:\"ref/syntax.md\",key:\"v-077f963a\",path:\"/ref/syntax.html\",headers:[{level:2,title:\"Cheatsheet\",slug:\"cheatsheet\"},{level:2,title:\"Railroad Diagrams\",slug:\"railroad-diagrams\"}]},{frontmatter:{},regularPath:\"/tutorial/write-a-parser.html\",relativePath:\"tutorial/write-a-parser.md\",key:\"v-6c9d134c\",path:\"/tutorial/write-a-parser.html\"}],themeConfig:{nav:[{text:\"Home\",link:\"/\"},{text:\"Guide\",link:\"/guide/\"},{text:\"Reference\",link:\"/ref/\"},{text:\"Plugins\",link:\"/plugin/\"},{text:\"Community\",link:\"/community/\"},{text:\"Github\",link:\"https://github.com/jsonicjs/jsonic\"}],sidebar:{\"/ref/\":[\"api\",\"options\",\"syntax\"],\"/guide/\":[\"install\",\"getting-started\",\"syntax-introduction\",\"alternatives\",\"custom-parsers\",\"tutorials\"],\"/plugin/\":[{title:\"Builtin Plugins\",path:\"#builtin-plugins\",children:[\"native\",\"csv\",\"hoover\",\"json\",\"dynamic\",\"multifile\",\"legacy-stringify\"]},{title:\"Standard Plugins\",path:\"#standard-plugins\",children:[\"foo\"]},{title:\"Community Plugins\",path:\"#community-plugins\",children:[\"bar\"]}]}}};n(144);Wn.component(\"JsonicRailroad\",()=>n.e(7).then(n.bind(null,216))),Wn.component(\"NameSelf\",()=>Promise.all([n.e(0),n.e(3)]).then(n.bind(null,217))),Wn.component(\"Badge\",()=>Promise.all([n.e(0),n.e(4)]).then(n.bind(null,218))),Wn.component(\"CodeBlock\",()=>Promise.all([n.e(0),n.e(5)]).then(n.bind(null,190))),Wn.component(\"CodeGroup\",()=>Promise.all([n.e(0),n.e(6)]).then(n.bind(null,191)));n(145);var As=[{},({Vue:t})=>{t.mixin({computed:{$dataBlock(){return this.$options.__data__block__}}})},{},{}],Ts=[];class Ls extends class{constructor(){this.store=new Wn({data:{state:{}}})}$get(t){return this.store.state[t]}$set(t,e){Wn.set(this.store.state,t,e)}$emit(...t){this.store.$emit(...t)}$on(...t){this.store.$on(...t)}}{}Object.assign(Ls.prototype,{getPageAsyncComponent:as,getLayoutAsyncComponent:ss,getAsyncComponent:us,getVueComponent:ls});var Rs={install(t){const e=new Ls;t.$vuepress=e,t.prototype.$vuepress=e}};function Is(t,e){const n=e.toLowerCase();return t.options.routes.some(t=>t.path.toLowerCase()===n)}var Ms={props:{pageKey:String,slotKey:{type:String,default:\"default\"}},render(t){const e=this.pageKey||this.$parent.$page.key;return fs(\"pageKey\",e),Wn.component(e)||Wn.component(e,as(e)),Wn.component(e)?t(e):t(\"\")}},Ds={functional:!0,props:{slotKey:String,required:!0},render:(t,{props:e,slots:n})=>t(\"div\",{class:[\"content__\"+e.slotKey]},n()[e.slotKey])},Ns={computed:{openInNewWindowTitle(){return this.$themeLocaleConfig.openNewWindowText||\"(opens new window)\"}}},Us=(n(146),n(147),Object(js.a)(Ns,(function(){var t=this._self._c;return t(\"span\",[t(\"svg\",{staticClass:\"icon outbound\",attrs:{xmlns:\"http://www.w3.org/2000/svg\",\"aria-hidden\":\"true\",focusable:\"false\",x:\"0px\",y:\"0px\",viewBox:\"0 0 100 100\",width:\"15\",height:\"15\"}},[t(\"path\",{attrs:{fill:\"currentColor\",d:\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"}}),this._v(\" \"),t(\"polygon\",{attrs:{fill:\"currentColor\",points:\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"}})]),this._v(\" \"),t(\"span\",{staticClass:\"sr-only\"},[this._v(this._s(this.openInNewWindowTitle))])])}),[],!1,null,null,null).exports),Fs={functional:!0,render(t,{parent:e,children:n}){if(e._isMounted)return n;e.$once(\"hook:mounted\",()=>{e.$forceUpdate()})}};Wn.config.productionTip=!1,Wn.use(Va),Wn.use(Rs),Wn.mixin(function(t,e,n=Wn){!function(t){t.locales&&Object.keys(t.locales).forEach(e=>{t.locales[e].path=e});Object.freeze(t)}(e),n.$vuepress.$set(\"siteData\",e);const r=new(t(n.$vuepress.$get(\"siteData\"))),o=Object.getOwnPropertyDescriptors(Object.getPrototypeOf(r)),i={};return Object.keys(o).reduce((t,e)=>(e.startsWith(\"$\")&&(t[e]=o[e].get),t),i),{computed:i}}(t=>class{setPage(t){this.__page=t}get $site(){return t}get $themeConfig(){return this.$site.themeConfig}get $frontmatter(){return this.$page.frontmatter}get $localeConfig(){const{locales:t={}}=this.$site;let e,n;for(const r in t)\"/\"===r?n=t[r]:0===this.$page.path.indexOf(r)&&(e=t[r]);return e||n||{}}get $siteTitle(){return this.$localeConfig.title||this.$site.title||\"\"}get $canonicalUrl(){const{canonicalUrl:t}=this.$page.frontmatter;return\"string\"==typeof t&&t}get $title(){const t=this.$page,{metaTitle:e}=this.$page.frontmatter;if(\"string\"==typeof e)return e;const n=this.$siteTitle,r=t.frontmatter.home?null:t.frontmatter.title||t.title;return n?r?r+\" | \"+n:n:r||\"VuePress\"}get $description(){const t=function(t){if(t){const e=t.filter(t=>\"description\"===t.name)[0];if(e)return e.content}}(this.$page.frontmatter.meta);return t||(this.$page.frontmatter.description||this.$localeConfig.description||this.$site.description||\"\")}get $lang(){return this.$page.frontmatter.lang||this.$localeConfig.lang||\"en-US\"}get $localePath(){return this.$localeConfig.path||\"/\"}get $themeLocaleConfig(){return(this.$site.themeConfig.locales||{})[this.$localePath]||{}}get $page(){return this.__page?this.__page:function(t,e){for(let n=0;n<t.length;n++){const r=t[n];if(r.path.toLowerCase()===e.toLowerCase())return r}return{path:\"\",frontmatter:{}}}(this.$site.pages,this.$route.path)}},Ps)),Wn.component(\"Content\",Ms),Wn.component(\"ContentSlotsDistributor\",Ds),Wn.component(\"OutboundLink\",Us),Wn.component(\"ClientOnly\",Fs),Wn.component(\"Layout\",ss(\"Layout\")),Wn.component(\"NotFound\",ss(\"NotFound\")),Wn.prototype.$withBase=function(t){const e=this.$site.base;return\"/\"===t.charAt(0)?e+t.slice(1):t},window.__VUEPRESS__={version:\"1.9.8\",hash:\"9af4755\"},async function(t){const e=\"undefined\"!=typeof window&&window.__VUEPRESS_ROUTER_BASE__?window.__VUEPRESS_ROUTER_BASE__:Ps.routerBase||Ps.base,n=new Va({base:e,mode:\"history\",fallback:!1,routes:Es,scrollBehavior:(t,e,n)=>n||(t.hash?!Wn.$vuepress.$get(\"disableScrollBehavior\")&&{selector:decodeURIComponent(t.hash)}:{x:0,y:0})});!function(t){t.beforeEach((e,n,r)=>{if(Is(t,e.path))r();else if(/(\\/|\\.html)$/.test(e.path))if(/\\/$/.test(e.path)){const n=e.path.replace(/\\/$/,\"\")+\".html\";Is(t,n)?r(n):r()}else r();else{const n=e.path+\"/\",o=e.path+\".html\";Is(t,o)?r(o):Is(t,n)?r(n):r()}})}(n);const r={};try{await Promise.all(As.filter(t=>\"function\"==typeof t).map(e=>e({Vue:Wn,options:r,router:n,siteData:Ps,isServer:t})))}catch(t){console.error(t)}return{app:new Wn(Object.assign(r,{router:n,render:t=>t(\"div\",{attrs:{id:\"app\"}},[t(\"RouterView\",{ref:\"layout\"}),t(\"div\",{class:\"global-ui\"},Ts.map(e=>t(e)))])})),router:n}}(!1).then(({app:t,router:e})=>{e.onReady(()=>{t.$mount(\"#app\")})})}]);"
  },
  {
    "path": "docs/guide/alternatives.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Alternatives | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/9.9b6e31d5.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" class=\"sidebar-link\">Install</a></li><li><a href=\"/guide/getting-started.html\" class=\"sidebar-link\">Getting Started</a></li><li><a href=\"/guide/syntax-introduction.html\" class=\"sidebar-link\">Syntax introduction</a></li><li><a href=\"/guide/alternatives.html\" aria-current=\"page\" class=\"active sidebar-link\">Alternatives</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/guide/alternatives.html#foo\" class=\"sidebar-link\">Foo</a></li></ul></li><li><a href=\"/guide/custom-parsers.html\" class=\"sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" class=\"sidebar-link\">Tutorials</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"alternatives\"><a href=\"#alternatives\" class=\"header-anchor\">#</a> Alternatives</h1> <h2 id=\"foo\"><a href=\"#foo\" class=\"header-anchor\">#</a> Foo</h2> <p>Short desc.</p> <p>Start the <a href=\"foo\">foo</a> link.</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/guide/syntax-introduction.html\" class=\"prev\">\n        Syntax introduction\n      </a></span> <span class=\"next\"><a href=\"/guide/custom-parsers.html\">\n        Custom parsers\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/9.9b6e31d5.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/guide/custom-parsers.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Custom parsers | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/10.88d9a57b.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" class=\"sidebar-link\">Install</a></li><li><a href=\"/guide/getting-started.html\" class=\"sidebar-link\">Getting Started</a></li><li><a href=\"/guide/syntax-introduction.html\" class=\"sidebar-link\">Syntax introduction</a></li><li><a href=\"/guide/alternatives.html\" class=\"sidebar-link\">Alternatives</a></li><li><a href=\"/guide/custom-parsers.html\" aria-current=\"page\" class=\"active sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" class=\"sidebar-link\">Tutorials</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"custom-parsers\"><a href=\"#custom-parsers\" class=\"header-anchor\">#</a> Custom parsers</h1></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/guide/alternatives.html\" class=\"prev\">\n        Alternatives\n      </a></span> <span class=\"next\"><a href=\"/guide/tutorials.html\">\n        Tutorials\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/10.88d9a57b.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/guide/getting-started.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Getting Started | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/11.7988a5fa.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" class=\"sidebar-link\">Install</a></li><li><a href=\"/guide/getting-started.html\" aria-current=\"page\" class=\"active sidebar-link\">Getting Started</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/guide/getting-started.html#parse-some-easy-going-json\" class=\"sidebar-link\">Parse some easy-going JSON!</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/getting-started.html#what-does-jsonic-syntax-give-you\" class=\"sidebar-link\">What does jsonic syntax give you?</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/getting-started.html#developer-friendly-error-messages\" class=\"sidebar-link\">Developer-friendly error messages</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/getting-started.html#nice-but-what-does-it-all-cost\" class=\"sidebar-link\">Nice, but what does it all cost?</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/getting-started.html#customizable-and-extendable\" class=\"sidebar-link\">Customizable and extendable</a></li></ul></li><li><a href=\"/guide/syntax-introduction.html\" class=\"sidebar-link\">Syntax introduction</a></li><li><a href=\"/guide/alternatives.html\" class=\"sidebar-link\">Alternatives</a></li><li><a href=\"/guide/custom-parsers.html\" class=\"sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" class=\"sidebar-link\">Tutorials</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"getting-started\"><a href=\"#getting-started\" class=\"header-anchor\">#</a> Getting Started</h1> <p>Make sure you have <span class=\"jsn-name-self\">Jsonic</span> <a href=\"install\">installed</a>. The installation\ninstructions cover your various options and environments in more\ndetail.</p> <p>Then load into your source code:</p> <h6 id=\"use-require-if-you-re-writing-for-node-hjs\"><a href=\"#use-require-if-you-re-writing-for-node-hjs\" class=\"header-anchor\">#</a> use <code>require</code> if you're writing for Node.hjs:</h6> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> Jsonic <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h6 id=\"use-import-if-you-re-writing-for-a-browser\"><a href=\"#use-import-if-you-re-writing-for-a-browser\" class=\"header-anchor\">#</a> use <code>import</code> if you're writing for a browser:</h6> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Jsonic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\n</code></pre></div><h2 id=\"parse-some-easy-going-json\"><a href=\"#parse-some-easy-going-json\" class=\"header-anchor\">#</a> Parse some easy-going JSON!</h2> <p>The <code>Jsonic</code> variable is a function that takes in a string and returns\nan object:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> data <span class=\"token operator\">=</span> <span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span> <span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">)</span> <span class=\"token comment\">// prints {&quot;a&quot;:1}</span>\n</code></pre></div><p>You pass in your jsonic source as a string. The string is parsed and\nconverted into a JavaScript variable.</p> <h2 id=\"what-does-jsonic-syntax-give-you\"><a href=\"#what-does-jsonic-syntax-give-you\" class=\"header-anchor\">#</a> What does <em>jsonic</em> syntax give you?</h2> <p>Standard JSON syntax <em>almost</em> provides a good developer experience,\nbut not quite. We need a few extra things:</p> <h3 id=\"trailing-commas-are-ignored\"><a href=\"#trailing-commas-are-ignored\" class=\"header-anchor\">#</a> Trailing commas are ignored</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'[ 1, 2, ]'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === [1, 2]</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'{ &quot;a&quot;:3, &quot;b&quot;:4, }'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;:3, &quot;b&quot;:4}</span>\n</code></pre></div><p>Just like a modern JavaScript. This one you can't live without.</p> <h3 id=\"object-keys-don-t-need-to-to-be-quoted\"><a href=\"#object-keys-don-t-need-to-to-be-quoted\" class=\"header-anchor\">#</a> Object keys don't need to to be quoted</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'{a:1}'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;:1}</span>\n</code></pre></div><p>Now object keys are more readable.</p> <h3 id=\"single-quote-strings-work\"><a href=\"#single-quote-strings-work\" class=\"header-anchor\">#</a> Single quote strings work</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'&quot;a&quot;'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === 'a'</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">&quot;'a'&quot;</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === 'a'</span>\n</code></pre></div><p>Just like normal JavaScript.</p> <h3 id=\"backtick-multi-line-strings-work\"><a href=\"#backtick-multi-line-strings-work\" class=\"header-anchor\">#</a> Backtick multi-line strings work</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === {&quot;a&quot;:1, &quot;b&quot;:2}</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">{\n  a: 1,\n  b: 2,\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Again, just like JavaScript.</p> <h3 id=\"you-get-all-the-comments\"><a href=\"#you-get-all-the-comments\" class=\"header-anchor\">#</a> You get all the comments</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === {&quot;a&quot;:1, &quot;b&quot;:2}</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">{\n  a: 1,\n  // A comment\n  # Also a comment\n  /*\n   * Still a comment\n   */\n  b: 2,\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Now you can comment your configuration files properly.</p> <h3 id=\"actually-who-needs-commas-when-newlines-will-do\"><a href=\"#actually-who-needs-commas-when-newlines-will-do\" class=\"header-anchor\">#</a> Actually, who needs commas when newlines will do</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === { &quot;a&quot;: 1, &quot;b&quot;: [ 2, 3 ] }</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">{\n  a: 1\n  b: [\n    2\n    3\n  ]\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Why should YAML have all the fun?</p> <h3 id=\"and-quoting-every-string-is-tedious-right\"><a href=\"#and-quoting-every-string-is-tedious-right\" class=\"header-anchor\">#</a> And quoting every string is tedious, right?</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === {&quot;a&quot;:&quot;foo&quot;, &quot;b&quot;:&quot;bar&quot;}</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'{a:foo, b:bar}'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>When a value isn't a number, <code>true</code>, <code>false</code>, or <code>null</code>, it has to be a string.</p> <h3 id=\"don-t-worry-about-spaces-inside-text-either\"><a href=\"#don-t-worry-about-spaces-inside-text-either\" class=\"header-anchor\">#</a> Don't worry about spaces inside text either</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === { &quot;a&quot;: &quot;foo bar&quot; }</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'{a: foo bar }'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Surrounding space is trimmed as you would expect.</p> <h3 id=\"implicit-objects-and-arrays-at-the-top-level\"><a href=\"#implicit-objects-and-arrays-at-the-top-level\" class=\"header-anchor\">#</a> Implicit objects and arrays at the top level</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === {&quot;a&quot;:1, &quot;b&quot;:2}</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1,b:2'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// === [&quot;a&quot;, &quot;b&quot;]</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a,b'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>This is very convenient for quickly defining small objects.</p> <h3 id=\"all-the-javascript-numbers-and-string-escapes\"><a href=\"#all-the-javascript-numbers-and-string-escapes\" class=\"header-anchor\">#</a> All the JavaScript numbers and string escapes</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === [20.0, 20, 20, 20, 20]</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'20.0, 2e1, 0x14, 0o24, 0b10100'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// === ['a', 'a', 'a']</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'&quot;\\x61&quot;, &quot;\\u0061&quot;, &quot;\\u{000061}&quot;'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Parity with JavaScript string handling is important to <span class=\"jsn-name-self\">Jsonic</span>.</p> <h3 id=\"block-text-with-indent-removal\"><a href=\"#block-text-with-indent-removal\" class=\"header-anchor\">#</a> Block text with indent removal</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === 'ship!\\ndish!\\nball!'</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n  '''\n  ship!\n  dish!\n  ball!\n  '''\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>A great idea from <a href=\"https://hjson.github.io/\" target=\"_blank\" rel=\"noopener noreferrer\">HJson<span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a>, so we had to have it!</p> <h3 id=\"deep-property-one-liners\"><a href=\"#deep-property-one-liners\" class=\"header-anchor\">#</a> Deep property one liners</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === { &quot;a&quot;: { &quot;b&quot;: { &quot;c&quot;: 1 } } }</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a: b: c: 1'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Also a great idea from the <a href=\"https://cuelang.org/\" target=\"_blank\" rel=\"noopener noreferrer\">Cue configuration language<span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a>.</p> <h3 id=\"merging-of-duplicate-properties\"><a href=\"#merging-of-duplicate-properties\" class=\"header-anchor\">#</a> Merging of duplicate properties</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// === {&quot;a&quot;: {&quot;b&quot;:1, &quot;c&quot;:2, &quot;d&quot;: 3}}</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n  a: {b: 1}\n  a: {c: 2}\n  a: d: 3\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>This is super useful when writing system specifications.</p> <h2 id=\"developer-friendly-error-messages\"><a href=\"#developer-friendly-error-messages\" class=\"header-anchor\">#</a> Developer-friendly error messages</h2> <p>Let's give <span class=\"jsn-name-self\">Jsonic</span> a syntax error:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">{\n  a: 1\n  ]\n}</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Here's what the error looks like:</p> <div class=\"language-sh extra-class\"><pre class=\"language-sh\"><code>JsonicError <span class=\"token punctuation\">[</span>SyntaxError<span class=\"token punctuation\">]</span>: <span class=\"token punctuation\">[</span>jsonic/unexpected<span class=\"token punctuation\">]</span>: unexpected character<span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span>: <span class=\"token string\">&quot;]&quot;</span>\n  --<span class=\"token operator\">&gt;</span> <span class=\"token operator\">&lt;</span>no-file<span class=\"token operator\">&gt;</span>:2:2\n  <span class=\"token number\">0</span> <span class=\"token operator\">|</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token number\">1</span> <span class=\"token operator\">|</span>   a: <span class=\"token number\">1</span>\n  <span class=\"token number\">2</span> <span class=\"token operator\">|</span>   <span class=\"token punctuation\">]</span>\n        ^ unexpected character<span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span>: <span class=\"token string\">&quot;]&quot;</span>\n  <span class=\"token number\">3</span> <span class=\"token operator\">|</span> <span class=\"token punctuation\">}</span>\n  <span class=\"token number\">4</span> <span class=\"token operator\">|</span> \n  The character<span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span> <span class=\"token string\">&quot;]&quot;</span> were not expected at this point as they <span class=\"token keyword\">do</span> not\n  match the expected syntax, even under the relaxed jsonic rules. If it\n  is not obviously wrong, the actual syntax error may be elsewhere. Try\n  commenting out larger areas around this point <span class=\"token keyword\">until</span> you get no errors,\n  <span class=\"token keyword\">then</span> remove the comments <span class=\"token keyword\">in</span> small sections <span class=\"token keyword\">until</span> you <span class=\"token function\">find</span> the\n  offending syntax. NOTE: Also check <span class=\"token keyword\">if</span> any plugins you are using\n  <span class=\"token function\">expect</span> different syntax <span class=\"token keyword\">in</span> this case.\n  https://jsonic.richardrodger.com\n  --internal: <span class=\"token assign-left variable\">rule</span><span class=\"token operator\">=</span>pair~close<span class=\"token punctuation\">;</span> <span class=\"token assign-left variable\">token</span><span class=\"token operator\">=</span><span class=\"token comment\">#CS; plugins=--</span>\n\nat Object.<span class=\"token operator\">&lt;</span>anonymous<span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">(</span>~/jsonic/test/syntax-error.js:2:1<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">..</span>.\n  code: <span class=\"token string\">'unexpected'</span>,\n  details: <span class=\"token punctuation\">{</span> state: <span class=\"token string\">'close'</span> <span class=\"token punctuation\">}</span>,\n  meta: <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>,\n  fileName: undefined,\n  lineNumber: <span class=\"token number\">2</span>,\n  columnNumber: <span class=\"token number\">2</span>\n<span class=\"token punctuation\">}</span>\n\n</code></pre></div><h2 id=\"nice-but-what-does-it-all-cost\"><a href=\"#nice-but-what-does-it-all-cost\" class=\"header-anchor\">#</a> Nice, but what does it all cost?</h2> <p>Jsonic is not going to be as fast at the builtin in JSON\nparser. Jsonic is for inline data in source code, settings, options,\nand configuration. Even if you have tens of thousands of line of\nconfiguration, Jsonic is <a href=\"/ref/performance\">plenty fast enough</a>.</p> <p>Fine, but does it scale? Yes, Jsonic is <code>O(n)</code>. Larger and larger\nsource text won't make the world implode.</p> <p>Memory usage? It doesn't leak.</p> <p>What about the call stack? All good. <span class=\"jsn-name-self\">Jsonic</span> doesn't use\nrecursive functions (it's iterative) so can handle data of any depth,\nat any depth in the call stack of your system.</p> <p>Every <span class=\"jsn-name-self\">Jsonic</span> release is measured for complexity, memory, and\nstack safety.</p> <p>Bugs that break this commitment get the highest priority.</p> <h2 id=\"customizable-and-extendable\"><a href=\"#customizable-and-extendable\" class=\"header-anchor\">#</a> Customizable and extendable</h2> <p>All of the JSON enhancements can be <a href=\"/guide/customize\">customized</a>.</p> <p>The top level <code>Jsonic</code> function cannot be customized. First, create a\nnew instance with:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> myjsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Then you can set some options. let's turn off comments:</p> <div class=\"language- extra-class\"><pre class=\"language-text\"><code>myjsonic.options({comment:{lex:false}})\n\nmyjsonic('a:1,#b:2') // === {&quot;a&quot;:1, &quot;#b&quot;:2}\nJsonic('a:1,#b:2') // === {&quot;a&quot;:1}\n</code></pre></div><p>Maybe you're just against <code>#</code> style comments, but fine with <code>//</code>?  OK,\nlet's get rid of just <code>#</code> comments:</p> <div class=\"language- extra-class\"><pre class=\"language-text\"><code>let nohash = Jsonic.make({comment:{marker:{'#':false}})\n\nnohash('a:1,#b:2') // === {&quot;a&quot;:1, &quot;#b&quot;:2}\nnohash('a:1,//b:2') // === {&quot;a&quot;:1}\n</code></pre></div><p>You can pass custom options directly to <code>Jsonic.make</code> when creating a\nnew instance.`</p> <p>All the options are explained in the <a href=\"/ref/options\">options reference</a>.</p> <p>​<span class=\"jsn-name-self\">Jsonic</span> can be extended with plugins. A few are included\nin the standard package to get you started.</p> <p>Let's parse some CSV:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> Csv <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/csv'</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">let</span> csv <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Csv<span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// === [{&quot;a&quot;: 1, &quot;b&quot;: 2}, {&quot;a&quot;: 3, &quot;b&quot;: 4}]</span>\n<span class=\"token function\">csv</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\na, b\n1, 2\n3, 4\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>The available plugins are listed in the <a href=\"/plugin/\">plugins section</a>,\nalong with tutorials to help you write your own plugins.</p> <h1 id=\"where-to-go-next\"><a href=\"#where-to-go-next\" class=\"header-anchor\">#</a> Where to go next</h1> <p>If you want to write custom parsers with <span class=\"jsn-name-self\">Jsonic</span>, start by\nfollowing the <a href=\"/guide/tutorials\">tutorials</a>.</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/guide/install.html\" class=\"prev\">\n        Install\n      </a></span> <span class=\"next\"><a href=\"/guide/syntax-introduction.html\">\n        Syntax introduction\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/11.7988a5fa.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/guide/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Guide | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/12.d9f3d941.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" class=\"sidebar-link\">Install</a></li><li><a href=\"/guide/getting-started.html\" class=\"sidebar-link\">Getting Started</a></li><li><a href=\"/guide/syntax-introduction.html\" class=\"sidebar-link\">Syntax introduction</a></li><li><a href=\"/guide/alternatives.html\" class=\"sidebar-link\">Alternatives</a></li><li><a href=\"/guide/custom-parsers.html\" class=\"sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" class=\"sidebar-link\">Tutorials</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"guide\"><a href=\"#guide\" class=\"header-anchor\">#</a> Guide</h1> <p>Lorem ipsum</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/12.d9f3d941.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/guide/install.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Install | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/13.775f91ca.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" aria-current=\"page\" class=\"active sidebar-link\">Install</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/guide/install.html#node-js\" class=\"sidebar-link\">Node.js</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/install.html#es-module\" class=\"sidebar-link\">ES Module</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/install.html#typescript\" class=\"sidebar-link\">Typescript</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/install.html#browser\" class=\"sidebar-link\">Browser</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/install.html#getting-help\" class=\"sidebar-link\">Getting Help</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/install.html#next-steps\" class=\"sidebar-link\">Next Steps</a></li></ul></li><li><a href=\"/guide/getting-started.html\" class=\"sidebar-link\">Getting Started</a></li><li><a href=\"/guide/syntax-introduction.html\" class=\"sidebar-link\">Syntax introduction</a></li><li><a href=\"/guide/alternatives.html\" class=\"sidebar-link\">Alternatives</a></li><li><a href=\"/guide/custom-parsers.html\" class=\"sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" class=\"sidebar-link\">Tutorials</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"install\"><a href=\"#install\" class=\"header-anchor\">#</a> Install</h1> <p>You can use <span class=\"jsn-name-self\">Jsonic</span> in the browser and on the server.</p> <p>You'll need to install the package into your project first:</p> <div class=\"language-sh extra-class\"><pre class=\"language-sh\"><code>$ <span class=\"token function\">npm</span> <span class=\"token function\">install</span> jsonic \n</code></pre></div><p>To validate your install, run the following code:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code>console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>This should output some strict JSON: <code>{&quot;a&quot;:1}</code>.</p> <p>​<span class=\"jsn-name-self\">Jsonic</span> also provides a command line utility\nthat converts jsonic arguments into traditional JSON. If you install\n<span class=\"jsn-name-self\">Jsonic</span> globally, then you can run this utility directly:</p> <div class=\"language-sh extra-class\"><pre class=\"language-sh\"><code>$ <span class=\"token function\">npm</span> <span class=\"token function\">install</span> <span class=\"token parameter variable\">-g</span> jsonic\n$ jsonic a:1\n<span class=\"token punctuation\">{</span><span class=\"token string\">&quot;a&quot;</span>:1<span class=\"token punctuation\">}</span>\n</code></pre></div><p>If you plan on writing custom plugins, this utility is very handy for\ndebugging (try <code>jsonic -d</code> to get a debug log).</p> <h2 id=\"node-js\"><a href=\"#node-js\" class=\"header-anchor\">#</a> Node.js</h2> <p>The recommended way to load Jsonic is:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> Jsonic <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// And then just use it directly!</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>However, if you are upgrading from <span class=\"jsn-name-self\">Jsonic</span> 0.x or 1.x, then the\nold way will still work (and is <em>not</em> deprecated—this is a perfectly valid traditional Node.js idiom!):</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">const</span> Jsonic <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Nothing broken!</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"es-module\"><a href=\"#es-module\" class=\"header-anchor\">#</a> ES Module</h2> <p>If you are using Node version 14 or higher and want to load <span class=\"jsn-name-self\">Jsonic</span> into your\nown modules, then load the jsonic.js file directly, like so:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">import</span> Jsonic <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>This will also work:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Jsonic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>Note that <span class=\"jsn-name-self\">Jsonic</span> is a CommonJS module, so this required Node\nversion 14 or higher.</p> <p>In other scenarios (such as browser usage), your packaging solution\n(webpack, rollup, etc.) will expose <span class=\"jsn-name-self\">Jsonic</span> to you as an ES Module.</p> <h2 id=\"typescript\"><a href=\"#typescript\" class=\"header-anchor\">#</a> Typescript</h2> <p>​<span class=\"jsn-name-self\">Jsonic</span> is written in TypeScript, and the\npackage includes type information. Import and go!</p> <div class=\"language-ts extra-class\"><pre class=\"language-ts\"><code><span class=\"token keyword\">import</span> Jsonic <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\n<span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>This will also work:</p> <div class=\"language-ts extra-class\"><pre class=\"language-ts\"><code><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Jsonic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\n<span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>The package also exports all of the types needed for plugin development:</p> <div class=\"language-ts extra-class\"><pre class=\"language-ts\"><code><span class=\"token comment\">// Some of the exported types (there are more...)</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Jsonic<span class=\"token punctuation\">,</span> Plugin<span class=\"token punctuation\">,</span> Rule<span class=\"token punctuation\">,</span> Context <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\n</code></pre></div><h2 id=\"browser\"><a href=\"#browser\" class=\"header-anchor\">#</a> Browser</h2> <p>If you use a packager (such as webpack, rollup, parcel, ...), then\nimport <span class=\"jsn-name-self\">Jsonic</span> and the packager will look after things for you.</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Jsonic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic'</span>\n</code></pre></div><p>You can also load <span class=\"jsn-name-self\">Jsonic</span> directly using the <code>script</code> tag. The\npackage includes a standalone minified version <code>jsonic.min.js</code>.</p> <div class=\"language-html extra-class\"><pre class=\"language-html\"><code><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">&quot;</span>jsonic.min.js<span class=\"token punctuation\">&quot;</span></span><span class=\"token punctuation\">&gt;</span></span><span class=\"token script\"></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">&gt;</span></span>\n</code></pre></div><p>This will make the global variable <code>Jsonic</code> available for direct use.</p> <h2 id=\"getting-help\"><a href=\"#getting-help\" class=\"header-anchor\">#</a> Getting Help</h2> <p>If you need help installing <span class=\"jsn-name-self\">Jsonic</span>, please post a question on\nthe <a href=\"https://github.com/rjrodger/jsonic/discussions/26\" target=\"_blank\" rel=\"noopener noreferrer\">Installation<span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a>\ndiscussion board.</p> <h2 id=\"next-steps\"><a href=\"#next-steps\" class=\"header-anchor\">#</a> Next Steps</h2> <p>Follow the <a href=\"getting-started\">Getting Started</a> guide to learn the extended\n<span class=\"jsn-name-self\">Jsonic</span> syntax.</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><!----> <span class=\"next\"><a href=\"/guide/getting-started.html\">\n        Getting Started\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/13.775f91ca.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/guide/syntax-introduction.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Syntax introduction | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/14.f56c2700.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" class=\"sidebar-link\">Install</a></li><li><a href=\"/guide/getting-started.html\" class=\"sidebar-link\">Getting Started</a></li><li><a href=\"/guide/syntax-introduction.html\" aria-current=\"page\" class=\"active sidebar-link\">Syntax introduction</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/guide/syntax-introduction.html#developer-experience\" class=\"sidebar-link\">Developer experience</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/syntax-introduction.html#walkthrough\" class=\"sidebar-link\">Walkthrough</a></li></ul></li><li><a href=\"/guide/alternatives.html\" class=\"sidebar-link\">Alternatives</a></li><li><a href=\"/guide/custom-parsers.html\" class=\"sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" class=\"sidebar-link\">Tutorials</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"syntax-introduction\"><a href=\"#syntax-introduction\" class=\"header-anchor\">#</a> Syntax introduction</h1> <p>The syntax that <span class=\"jsn-name-self\">Jsonic</span> uses is configurable. There is a standard\nversion that you will learn about first in this guide.</p> <p>​<span class=\"jsn-name-self\">Jsonic</span> syntax is a super set of traditional\n<em>JSON</em>. Every valid <em>JSON</em> document is also a valid <span class=\"jsn-name-self\">Jsonic</span>\ndocument.</p> <h2 id=\"developer-experience\"><a href=\"#developer-experience\" class=\"header-anchor\">#</a> Developer experience</h2> <p>The developer experience when using <em>JSON</em> is OK but not great. When\n<em>JSON</em> is used for editable documents, say for configuration, things\nget tricky. The lack of comments make life more difficult than it\nneeds to be. The ceremony of quoting all strings is tedious. And\ndealing with multi-line strings is really nasty.</p> <p>There are many extensions to <em>JSON</em> that solve these problems (and\nothers) in various ways.  All the alternatives that I know about are\nlisted on the <a href=\"/guide/alternatives\">alternatives</a> page.</p> <p>I have taken as many of the good ideas as possible, and combined them\ninto a configurable <em>JSON</em> parser. The basic guiding is: if it isn't\nambiguous, it's cool.</p> <p>​<span class=\"jsn-name-self\">Jsonic</span> is also something else&amp;emdash;an\nextensible parser. If you want to add your own little Domain Specific\nLanguages (DSL) into your <em>JSON</em> documents, <span class=\"jsn-name-self\">Jsonic</span> is built\nexactly for that purpose! See\nthe <a href=\"/guide/custom-parsers\">custom parsers</a> section for a guide to\nwriting your own <em>JSON</em>-based DSLs with <span class=\"jsn-name-self\">Jsonic</span></p> <h2 id=\"walkthrough\"><a href=\"#walkthrough\" class=\"header-anchor\">#</a> Walkthrough</h2> <p>Let's first informally describe the syntax extensions that\n<span class=\"jsn-name-self\">Jsonic</span> provides. For a slightly more formal description, See\nthe <a href=\"/ref/#railroad-diagrams\">railroad diagrams</a> section.</p> <p>​<span class=\"jsn-name-self\">Jsonic</span> is an extension\nof <a href=\"https://json.org\" target=\"_blank\" rel=\"noopener noreferrer\">JSON<span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a>, so all the usual rules of <em>JSON</em> syntax\nstill work.</p> <h3 id=\"comments\"><a href=\"#comments\" class=\"header-anchor\">#</a> Comments</h3> <p>Single line comments can be introduced by <code>#</code> or <code>//</code> and run to the\nend of the current line:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>{\n  &quot;a&quot;: 1, # a comment\n  &quot;b&quot;: 2, // also a comment\n}\n</code></pre></div><p>You also get multi-line comments with:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>{\n  &quot;a&quot;: 1,\n  /* \n   * A Multiline comment\n   */\n  &quot;b&quot;: 2,\n  &quot;foo&quot;: foo,\n}\n\n</code></pre></div><p>Sometimes you need to comment out a section that already has a\nmulti-line comment within it. This can be annoying with traditional\nmulti-line syntax, as the multi-line comment ends with the first end\nmarker (<code>*/</code>) seen.  <span class=\"jsn-name-self\">Jsonic</span> allows multi-line comments to nest\nso you don't have to worry about this anymore:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>{\n  &quot;a&quot;: 1,\n  /* \n   * /* a multi-line comment\n   *  * inside a multi-line comment.\n   *  */\n   */\n  &quot;b&quot;: 2,\n}\n\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(Even the syntax highlighter struggles with this one!)</small></p> <h3 id=\"keys\"><a href=\"#keys\" class=\"header-anchor\">#</a> Keys</h3> <p>You don't have to quote keys. This deals with the most tedious part of\nediting pure <em>JSON</em> by hand. We go a little easier than pure\n<code>JavaScript</code> too—you only have to quote keys if they contain\nspaces or punctuation.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n{               {\n  a: 1,           &quot;a&quot;: 1,\n  1: 2,           &quot;1&quot;: 2,\n  1a: 3,          &quot;1a&quot;: 3,\n  &quot;1 a&quot;: 4,       &quot;1 a&quot;: 4,\n  &quot;{}&quot;: 5,         &quot;{}&quot;: 5,\n  true: 6         &quot;true&quot;: 6\n}               }\n</code></pre></div><h3 id=\"commas\"><a href=\"#commas\" class=\"header-anchor\">#</a> Commas</h3> <p>This is another essential convenience. You can having trailing commas,\nwhich makes cut-and-paste editing much easier.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n{               {\n  a: 1,           &quot;a&quot;: 1,\n  1: 2,           &quot;1&quot;: 2\n}               }\n</code></pre></div><p>Actually, you don't need commas at all. Spaces, tabs, new lines and <em>nothing</em> also\nseparate elements for you:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n{               {\n  a: 1           &quot;a&quot;: 1,\n  1: 2           &quot;1&quot;: 2,\n  b: [3 4]       &quot;b&quot;: [3, 4]\n  c: [[5][6]]    &quot;c&quot;: [[5], [6]] \n}               }\n</code></pre></div><p>Repeated commas do have a special meaning. Any comma without a\n<strong>preceding value</strong> generates a <code>null</code> value (<em>JSON</em> only has <code>null</code>, not\n<code>undefined</code>):</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n[a,]           [&quot;a&quot;]\n[,a]           [null, &quot;a&quot;]\n[,a,]          [null, &quot;a&quot;]\n[,a,,]         [null, &quot;a&quot;, null]\n[,,,]          [null, null, null]\n[,,]           [null, null]\n[,]            [null]\n</code></pre></div><p>You also get <code>null</code> when a property value is missing:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n{a:,b:}         {&quot;a&quot;:null, &quot;b&quot;:null}\n</code></pre></div><h3 id=\"strings\"><a href=\"#strings\" class=\"header-anchor\">#</a> Strings</h3> <p>Single and double quoted strings work the same way as in JavaScript:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n&quot;a&quot;             &quot;a&quot;\n'b'             &quot;b&quot;\n&quot;c'c&quot;           &quot;c'c&quot;\n'd&quot;d'           &quot;d\\&quot;d&quot;\n'e\\te'          &quot;e\\te&quot;\n</code></pre></div><p>You also get backticks, which are multi-line:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n`a             &quot;a\\nb&quot;\nb`           \n</code></pre></div><p>And a convenience syntax for indented blocks of text:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n  '''           &quot;red\\ngreen\\nblue&quot;\n  red                          \n  green\n  blue\n  '''\n</code></pre></div><h3 id=\"numbers\"><a href=\"#numbers\" class=\"header-anchor\">#</a> Numbers</h3> <p>You get all the JavaScript number formats:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n20              20\n20.0            20\n2e1             20\n0x14            20\n0o24            20\n0b10100         20\n</code></pre></div><p>And underscore as way to make large numbers easier to read:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n2_000_000       2000000\n</code></pre></div><p>The special JavaScript number value literals (such as <code>Infinity</code>) are\nnot supported, but you can have them (and other things, like\n<code>undefined</code>) if you use the <a href=\"/plugin/native\">native</a> plugin.</p> <h3 id=\"merges\"><a href=\"#merges\" class=\"header-anchor\">#</a> Merges</h3> <p>Duplicate keys merge their values when they are objects or arrays,\notherwise the last value wins.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic                  // JSON\n{a:1, a:2}                 {&quot;a&quot;:2}\n{a:{b:1}, a:{c:2}}         {&quot;a&quot;:{&quot;b&quot;:1, &quot;c&quot;:2}}\n{a:[1,2], a:[3]}           {&quot;a&quot;:[3, 2]}\n</code></pre></div><h3 id=\"shortcuts\"><a href=\"#shortcuts\" class=\"header-anchor\">#</a> Shortcuts</h3> <p>At the top level, you can skip braces and square brackets:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\na:1,b:2         {&quot;a&quot;:1, &quot;b&quot;:2}\n1,2             [1, 2]\n</code></pre></div><p>You can use repeated key-colon pairs to set a single deep property:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\na:b:1           {&quot;a&quot;: {&quot;b&quot;:1}}\na:b:[2]         {&quot;a&quot;: {&quot;b&quot;: [2]}}\na:b:2, a:c:3    {&quot;a&quot;: {&quot;b&quot;: 2, &quot;c&quot;:3}}\n</code></pre></div><p>Open objects and arrays close themselves:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n{a:{b:{c:[1     {&quot;a&quot;:{&quot;b&quot;:{&quot;c&quot;:[1]}}}\n</code></pre></div><h3 id=\"things-that-still-aren-t-allowed\"><a href=\"#things-that-still-aren-t-allowed\" class=\"header-anchor\">#</a> Things that still aren't allowed</h3> <p>You still need to be able to detect actual syntax errors, like misused\npunctuation in property keys or array elements:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>a{b:1           // nope!\na}b:1           // nope!\na[b:1           // nope!\na]b:1           // nope!\n[{]             // nope!\n[}]             // nope!\n</code></pre></div><p>Punctuation cannot occur in property values either as it terminates\nthe value (which is what you want).</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic       // JSON\n{a:}            {&quot;a&quot;: null}\n{a:{}           {&quot;a&quot;: {}}\n{a:]}           // nope!\n{a:[}           // nope!\n</code></pre></div></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/guide/getting-started.html\" class=\"prev\">\n        Getting Started\n      </a></span> <span class=\"next\"><a href=\"/guide/alternatives.html\">\n        Alternatives\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/14.f56c2700.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/guide/tutorials.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Tutorials | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/15.00058088.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link router-link-active\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/guide/install.html\" class=\"sidebar-link\">Install</a></li><li><a href=\"/guide/getting-started.html\" class=\"sidebar-link\">Getting Started</a></li><li><a href=\"/guide/syntax-introduction.html\" class=\"sidebar-link\">Syntax introduction</a></li><li><a href=\"/guide/alternatives.html\" class=\"sidebar-link\">Alternatives</a></li><li><a href=\"/guide/custom-parsers.html\" class=\"sidebar-link\">Custom parsers</a></li><li><a href=\"/guide/tutorials.html\" aria-current=\"page\" class=\"active sidebar-link\">Tutorials</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/guide/tutorials.html#writing-a-plugin\" class=\"sidebar-link\">Writing a plugin</a></li><li class=\"sidebar-sub-header\"><a href=\"/guide/tutorials.html#parsing-csv-into-json\" class=\"sidebar-link\">Parsing CSV into JSON</a></li></ul></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"tutorials\"><a href=\"#tutorials\" class=\"header-anchor\">#</a> Tutorials</h1> <h2 id=\"writing-a-plugin\"><a href=\"#writing-a-plugin\" class=\"header-anchor\">#</a> Writing a plugin</h2> <p>Short desc.</p> <p>Start the <a href=\"/tutorial/write-a-plugin\">writing a plugin</a> tutorial.</p> <h2 id=\"parsing-csv-into-json\"><a href=\"#parsing-csv-into-json\" class=\"header-anchor\">#</a> Parsing CSV into JSON</h2> <p>Short desc.</p> <p>Start the <a href=\"/tutorial/parsing-csv\">parsing CSV</a> tutorial.</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/guide/custom-parsers.html\" class=\"prev\">\n        Custom parsers\n      </a></span> <!----></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/15.00058088.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/16.70a6eea0.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container no-sidebar\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" aria-current=\"page\" class=\"home-link router-link-exact-active router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <!----> </aside> <main aria-labelledby=\"main-title\" class=\"home\"><header class=\"hero\"><img src=\"/jsonic-logo.svg\" alt=\"hero\"> <h1 id=\"main-title\">\n      Jsonic\n    </h1> <p class=\"description\">\n      A JSON parser for Node.js that isn't strict.\n    </p> <p class=\"action\"><a href=\"/guide/getting-started.html\" class=\"nav-link action-button\">\n  Getting Started →\n</a></p></header> <!----> <div class=\"theme-default-content custom content__default\"><p>Jsonic items</p></div> <div class=\"footer content__footer\"></div></main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/16.70a6eea0.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/jsonic.js",
    "content": "\"use strict\";\n/* Copyright (c) 2013-2021 Richard Rodger, MIT License */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.make = exports.util = exports.RuleSpec = exports.Rule = exports.Parser = exports.Lexer = exports.JsonicError = exports.Jsonic = void 0;\nconst S = {\n    object: 'object',\n    string: 'string',\n    function: 'function',\n    unexpected: 'unexpected',\n    map: 'map',\n    list: 'list',\n    elem: 'elem',\n    pair: 'pair',\n    val: 'val',\n    node: 'node',\n};\nfunction make_standard_options() {\n    let opts = {\n        // String escape chars.\n        // Denoting char (follows escape char) => actual char.\n        escape: {\n            b: '\\b',\n            f: '\\f',\n            n: '\\n',\n            r: '\\r',\n            t: '\\t',\n        },\n        // Special chars\n        char: {\n            // Increments row (aka line) counter.\n            row: '\\n',\n            // Invalid code point.\n            bad_unicode: String.fromCharCode('0x0000'),\n        },\n        // Comment markers.\n        // <mark-char>: true -> single line comments\n        // <mark-start>: <mark-end> -> multiline comments\n        comment: {\n            '#': true,\n            '//': true,\n            '/*': '*/'\n        },\n        // Control balanced markers.\n        balance: {\n            // Balance multiline comments.\n            comment: true,\n        },\n        // Control number formats.\n        number: {\n            // Recognize numbers in the Lexer.\n            lex: true,\n            // Recognize hex numbers (eg. 10 === 0x0a).\n            hex: true,\n            // Recognize octal numbers (eg. 10 === 0o12).\n            oct: true,\n            // Recognize ninary numbers (eg. 10 === 0b1010).\n            bin: true,\n            // All possible number chars. |+-|0|xob|0-9a-fA-F|.e|+-|0-9a-fA-F|\n            digital: '-1023456789._xoeEaAbBcCdDfF+',\n            // Allow embedded separator. `null` to disable.\n            sep: '_',\n        },\n        // String formats.\n        string: {\n            // Multiline quote chars.\n            multiline: '`',\n            block: {\n                '\\'\\'\\'': '\\'\\'\\''\n            },\n            // CSV-style double quote escape.\n            escapedouble: false,\n        },\n        // Text formats.\n        text: {\n            // Text includes internal whitespace.\n            hoover: true,\n            // Consume to end of line.\n            endofline: false,\n        },\n        // TODO: rename to map for consistency\n        // Object formats.\n        object: {\n            // TODO: allow: true - allow duplicates, else error\n            // Later duplicates extend earlier ones, rather than replacing them.\n            extend: true,\n        },\n        // Keyword values.\n        value: {\n            'null': null,\n            'true': true,\n            'false': false,\n        },\n        // Parsing modes.\n        mode: {},\n        // Plugin custom options, (namespace by plugin name).\n        plugin: {},\n        debug: {\n            // Default console for logging\n            get_console: () => console,\n            // Max length of parse value to print\n            maxlen: 33,\n        },\n        // Error messages.\n        error: {\n            unknown: 'unknown error: $code',\n            unexpected: 'unexpected character(s): $src',\n            invalid_unicode: 'invalid unicode escape: $src',\n            invalid_ascii: 'invalid ascii escape: $src',\n            unprintable: 'unprintable character: $src',\n            unterminated: 'unterminated string: $src'\n        },\n        // Error hints: {error-code: hint-text}. \n        hint: make_hint,\n        token: {\n            // Single char tokens.\n            '#OB': { c: '{' },\n            '#CB': { c: '}' },\n            '#OS': { c: '[' },\n            '#CS': { c: ']' },\n            '#CL': { c: ':' },\n            '#CA': { c: ',' },\n            // Multi-char tokens (start chars).\n            '#SP': ' \\t',\n            '#LN': '\\n\\r',\n            '#CM': true,\n            '#NR': '-0123456789',\n            '#ST': '\"\\'`',\n            // General char tokens.\n            '#TX': true,\n            '#VL': true,\n            // Non-char tokens.\n            '#BD': true,\n            '#ZZ': true,\n            '#UK': true,\n            '#AA': true,\n            // Token sets\n            // NOTE: comma-sep strings to avoid util.deep array override logic\n            '#IGNORE': { s: '#SP,#LN,#CM' },\n        },\n        // Lexing options.\n        lex: {\n            core: {\n                // The token for line endings.\n                LN: '#LN',\n            }\n        },\n        // Parser rule options.\n        rule: {\n            // Multiplier to increase the maximum number of rule occurences.\n            maxmul: 3,\n        },\n    };\n    return opts;\n}\n// Jsonic errors with nice formatting.\nclass JsonicError extends SyntaxError {\n    constructor(code, details, token, rule, ctx) {\n        details = util.deep({}, details);\n        let errctx = util.deep({}, {\n            rI: ctx.rI,\n            opts: ctx.opts,\n            config: ctx.config,\n            meta: ctx.meta,\n            src: () => ctx.src(),\n            root: () => ctx.root(),\n            plugins: () => ctx.plugins(),\n            node: ctx.node,\n            t0: ctx.t0,\n            t1: ctx.t1,\n            tI: ctx.tI,\n            // TODO: fix cycle\n            // rs: ctx.rs,\n            next: () => ctx.next(),\n            log: ctx.log,\n            use: ctx.use\n        });\n        let desc = JsonicError.make_desc(code, details, token, rule, errctx);\n        super(desc.message);\n        Object.assign(this, desc);\n        if (this.stack) {\n            this.stack =\n                this.stack.split('\\n')\n                    .filter(s => !s.includes('jsonic/jsonic'))\n                    .map(s => s.replace(/    at /, 'at '))\n                    .join('\\n');\n        }\n    }\n    static make_desc(code, details, token, rule, ctx) {\n        token = { ...token };\n        let opts = ctx.opts;\n        let meta = ctx.meta;\n        let errtxt = util.errinject((opts.error[code] || opts.error.unknown), code, details, token, rule, ctx);\n        if (S.function === typeof (opts.hint)) {\n            // Only expand the hints on demand. Allow for plugin-defined hints.\n            opts.hint = { ...opts.hint(), ...opts.hint };\n        }\n        let message = [\n            ('\\x1b[31m[jsonic/' + code + ']:\\x1b[0m ' +\n                ((meta && meta.mode) ? '\\x1b[35m[mode:' + meta.mode + ']:\\x1b[0m ' : '') +\n                errtxt),\n            '  \\x1b[34m-->\\x1b[0m ' + (meta.fileName || '<no-file>') + ':' + token.row + ':' + token.col,\n            util.extract(ctx.src(), errtxt, token),\n            util.errinject((opts.hint[code] || opts.hint.unknown).split('\\n')\n                .map((s, i) => (0 === i ? ' ' : '  ') + s).join('\\n'), code, details, token, rule, ctx),\n            '  \\x1b[2mhttps://jsonic.richardrodger.com\\x1b[0m',\n            '  \\x1b[2m--internal: rule=' + rule.name + '~' + RuleState[rule.state] +\n                '; token=' + ctx.config.token[token.pin] +\n                '; plugins=' + ctx.plugins().map((p) => p.name).join(',') + '--\\x1b[0m\\n'\n        ].join('\\n');\n        let desc = {\n            internal: {\n                token,\n                ctx,\n            }\n        };\n        desc = {\n            ...Object.create(desc),\n            message,\n            code,\n            details,\n            meta,\n            fileName: meta.fileName,\n            lineNumber: token.row,\n            columnNumber: token.col,\n        };\n        return desc;\n    }\n    toJSON() {\n        return {\n            ...this,\n            __error: true,\n            name: this.name,\n            message: this.message,\n            stack: this.stack,\n        };\n    }\n}\nexports.JsonicError = JsonicError;\nclass Lexer {\n    constructor(config) {\n        this.match = {};\n        this.match[util.token('@LTP', config)] = []; // TOP\n        this.match[util.token('@LTX', config)] = []; // TEXT\n        this.match[util.token('@LCS', config)] = []; // CONSUME\n        this.match[util.token('@LML', config)] = []; // MULTILINE\n        this.end = {\n            pin: util.token('#ZZ', config),\n            loc: 0,\n            len: 0,\n            row: 0,\n            col: 0,\n            val: undefined,\n            src: undefined,\n        };\n    }\n    // Create the lexing function.\n    start(ctx) {\n        const opts = ctx.opts;\n        const config = ctx.config;\n        let tpin = (name) => util.token(name, config);\n        let tn = (pin) => util.token(pin, config);\n        let F = ctx.F;\n        let LTP = tpin('@LTP');\n        let LTX = tpin('@LTX');\n        let LCS = tpin('@LCS');\n        let LML = tpin('@LML');\n        let ZZ = tpin('#ZZ');\n        let SP = tpin('#SP');\n        let CM = tpin('#CM');\n        let NR = tpin('#NR');\n        let ST = tpin('#ST');\n        let TX = tpin('#TX');\n        let VL = tpin('#VL');\n        tpin('#LN');\n        // NOTE: always returns this object!\n        let token = {\n            pin: ZZ,\n            loc: 0,\n            row: 0,\n            col: 0,\n            len: 0,\n            val: undefined,\n            src: undefined,\n        };\n        // Main indexes.\n        let sI = 0; // Source text index.\n        let rI = 0; // Source row index.\n        let cI = 0; // Source column index.\n        let src = ctx.src();\n        let srclen = src.length;\n        // TS2722 impedes this definition unless Context is\n        // refined to (Context & { log: any })\n        let lexlog = (null != ctx && null != ctx.log) ?\n            ((...rest) => ctx.log('lex', ...rest)) :\n            undefined;\n        let self = this;\n        function bad(code, pI, badsrc, use) {\n            return self.bad(ctx, lexlog, code, token, sI, pI, rI, cI, badsrc, badsrc, use);\n        }\n        // Check for custom matchers.\n        // NOTE: deliberately grabs local state (token,sI,rI,cI,...)\n        function matchers(state, rule) {\n            let matchers = self.match[state];\n            if (null != matchers) {\n                token.loc = sI; // TODO: move to top of while for all rules?\n                for (let matcher of matchers) {\n                    let match = matcher(sI, src, token, ctx, rule, bad);\n                    // Adjust lex location if there was a match.\n                    if (match) {\n                        sI = match.sI;\n                        rI = match.rD ? rI + match.rD : rI;\n                        cI = match.cD ? cI + match.cD : cI;\n                        lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                        return token;\n                    }\n                }\n            }\n        }\n        // Lex next Token.\n        let lex = function lex(rule) {\n            token.len = 0;\n            token.val = undefined;\n            token.src = undefined;\n            token.row = rI;\n            let state = LTP;\n            let state_param = null;\n            let enders = '';\n            let pI = 0; // Current lex position (only update sI at end of rule).\n            let s = []; // Parsed string chars and substrings.\n            let cc = -1; // Char code.\n            next_char: while (sI < srclen) {\n                let c0 = src[sI];\n                let c0c = src.charCodeAt(sI);\n                if (LTP === state) {\n                    if (matchers(LTP, rule)) {\n                        return token;\n                    }\n                    // Space chars.\n                    if (config.start.SP.includes(c0c)) {\n                        token.pin = SP;\n                        token.loc = sI;\n                        token.col = cI++;\n                        pI = sI + 1;\n                        while (config.multi.SP.includes(src[pI]))\n                            cI++, pI++;\n                        token.len = pI - sI;\n                        token.val = src.substring(sI, pI);\n                        token.src = token.val;\n                        sI = pI;\n                        lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                        return token;\n                    }\n                    // Newline chars.\n                    if (config.start.LN.includes(c0c)) {\n                        token.pin = config.lex.core.LN;\n                        token.loc = sI;\n                        token.col = cI;\n                        pI = sI;\n                        cI = 0;\n                        while (config.multi.LN.includes(src[pI])) {\n                            // Count rows.\n                            rI += (opts.char.row === src[pI] ? 1 : 0);\n                            pI++;\n                        }\n                        token.len = pI - sI;\n                        token.val = src.substring(sI, pI);\n                        token.src = token.val;\n                        sI = pI;\n                        lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                        return token;\n                    }\n                    // Single char tokens.\n                    if (null != config.single[c0c]) {\n                        token.pin = config.single[c0c];\n                        token.loc = sI;\n                        token.col = cI++;\n                        token.len = 1;\n                        token.src = c0;\n                        sI++;\n                        lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                        return token;\n                    }\n                    // Number chars.\n                    if (config.start.NR.includes(c0c) && opts.number.lex) {\n                        token.pin = NR;\n                        token.loc = sI;\n                        token.col = cI;\n                        pI = sI;\n                        while (opts.number.digital.includes(src[++pI]))\n                            ;\n                        let numstr = src.substring(sI, pI);\n                        if (null == src[pI] || config.value_ender.includes(src[pI])) {\n                            token.len = pI - sI;\n                            let base_char = src[sI + 1];\n                            // Leading 0s are text unless hex|oct|bin val: if at least two\n                            // digits and does not start with 0x|o|b, then text.\n                            if (1 < token.len && '0' === src[sI] && // Maybe a 0x|o|b number?\n                                opts.number.hex && 'x' !== base_char && // But...\n                                opts.number.oct && 'o' !== base_char && //  it is...\n                                opts.number.bin && 'b' !== base_char && //    not.\n                                true) {\n                                // Not a number.\n                                token.val = undefined;\n                                pI--;\n                            }\n                            // Attempt to parse natively as a number, using +(string).\n                            else {\n                                token.val = +numstr;\n                                // Allow number format 1000_000_000 === 1e9.\n                                if (null != config.number.sep_re && isNaN(token.val)) {\n                                    token.val = +(numstr.replace(config.number.sep_re, ''));\n                                }\n                                // Not a number, just a random collection of digital chars.\n                                if (isNaN(token.val)) {\n                                    token.val = undefined;\n                                    pI--;\n                                }\n                            }\n                        }\n                        // It was a number\n                        if (null != token.val) {\n                            // Ensure verbatim src (eg. src=\"1e6\" -> val=1000000).\n                            token.src = numstr;\n                            cI += token.len;\n                            sI = pI;\n                            lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                            return token;\n                        }\n                        // NOTE: else drop through to default, as this must be literal text\n                        // prefixed with digits.\n                    }\n                    // Block chars.\n                    if (config.start_bm.includes(c0c)) {\n                        let marker = src.substring(sI, sI + config.bmk_maxlen);\n                        for (let bm of config.bmk) {\n                            if (marker.startsWith(bm)) {\n                                token.pin = ST;\n                                token.loc = sI;\n                                token.col = cI;\n                                state = LML;\n                                state_param = [bm, opts.string.block[bm], null, true];\n                                continue next_char;\n                            }\n                        }\n                    }\n                    // String chars.\n                    if (config.start.ST.includes(c0c)) {\n                        token.pin = ST;\n                        token.loc = sI;\n                        token.col = cI++;\n                        let qc = c0.charCodeAt(0);\n                        let multiline = opts.string.multiline.includes(c0);\n                        s = [];\n                        cc = -1;\n                        // TODO: \\xNN \\u{...}\n                        for (pI = sI + 1; pI < srclen; pI++) {\n                            cI++;\n                            cc = src.charCodeAt(pI);\n                            // Quote char.\n                            if (qc === cc) {\n                                if (opts.string.escapedouble && qc === src.charCodeAt(pI + 1)) {\n                                    s.push(src[pI]);\n                                    pI++;\n                                }\n                                else {\n                                    pI++;\n                                    break; // String finished.\n                                }\n                            }\n                            // TODO: use opt.char.escape (string) -> config.char.escape (code)\n                            // Escape char. \n                            else if (92 === cc) {\n                                let ec = src.charCodeAt(++pI);\n                                cI++;\n                                let es = config.escape[ec];\n                                if (null != es) {\n                                    s.push(es);\n                                }\n                                // ASCII escape \\x**\n                                else if (120 === ec) {\n                                    pI++;\n                                    let us = String.fromCharCode(('0x' + src.substring(pI, pI + 2)));\n                                    if (opts.char.bad_unicode === us) {\n                                        return bad('invalid_ascii', pI, src.substring(pI - 2, pI + 2));\n                                    }\n                                    s.push(us);\n                                    pI += 1; // Loop increments pI.\n                                    cI += 2;\n                                }\n                                // Unicode escape \\u****.\n                                // TODO: support \\u{*****}\n                                else if (117 === ec) {\n                                    pI++;\n                                    let us = String.fromCharCode(('0x' + src.substring(pI, pI + 4)));\n                                    if (opts.char.bad_unicode === us) {\n                                        return bad('invalid_unicode', pI, src.substring(pI - 2, pI + 4));\n                                    }\n                                    s.push(us);\n                                    pI += 3; // Loop increments pI.\n                                    cI += 4;\n                                }\n                                else {\n                                    s.push(src[pI]);\n                                }\n                            }\n                            // Unprintable chars.\n                            else if (cc < 32) {\n                                if (multiline && config.start.LN.includes(cc)) {\n                                    s.push(src[pI]);\n                                }\n                                else {\n                                    return bad('unprintable', pI, 'char-code=' + src[pI].charCodeAt(0));\n                                }\n                            }\n                            // Main body of string.\n                            else {\n                                let bI = pI;\n                                do {\n                                    cc = src.charCodeAt(++pI);\n                                    cI++;\n                                } while (32 <= cc && qc !== cc && 92 !== cc);\n                                cI--;\n                                s.push(src.substring(bI, pI));\n                                pI--;\n                            }\n                        }\n                        if (qc !== cc) {\n                            cI = sI;\n                            return bad('unterminated', pI - 1, s.join(''));\n                        }\n                        token.val = s.join('');\n                        token.src = src.substring(sI, pI);\n                        token.len = pI - sI;\n                        sI = pI;\n                        lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                        return token;\n                    }\n                    // Comment chars.\n                    if (config.start_cm.includes(c0c)) {\n                        let is_line_comment = config.cm_single.includes(c0);\n                        // Also check for comment markers as single comment char could be\n                        // a comment marker prefix (eg. # and ###, / and //, /*).\n                        let marker = src.substring(sI, sI + config.cmk_maxlen);\n                        for (let cm of config.cmk) {\n                            if (marker.startsWith(cm)) {\n                                // Multi-line comment.\n                                if (true !== opts.comment[cm]) {\n                                    token.pin = CM;\n                                    token.loc = sI;\n                                    token.col = cI;\n                                    token.val = ''; // intialize for LCS.\n                                    state = LML;\n                                    state_param = [cm, opts.comment[cm], 'comment'];\n                                    continue next_char;\n                                }\n                                else {\n                                    is_line_comment = true;\n                                }\n                                break;\n                            }\n                        }\n                        if (is_line_comment) {\n                            token.pin = CM;\n                            token.loc = sI;\n                            token.col = cI;\n                            token.val = ''; // intialize for LCS.\n                            state = LCS;\n                            enders = config.multi.LN;\n                            continue next_char;\n                        }\n                    }\n                    // NOTE: default section. Cases above can bail to here if lookaheads\n                    // fail to match (eg. NR).\n                    // No explicit token recognized. That leaves:\n                    // - keyword literal values (from opts.value)\n                    // - text values (everything up to a text_ender char (eg. newline))\n                    token.loc = sI;\n                    token.col = cI;\n                    pI = sI;\n                    // Literal values must be terminated, otherwise they are just\n                    // accidental prefixes to literal text\n                    // (e.g truex -> \"truex\" not `true` \"x\")\n                    do {\n                        cI++;\n                        pI++;\n                    } while (null != src[pI] && !config.value_ender.includes(src[pI]));\n                    let txt = src.substring(sI, pI);\n                    // A keyword literal value? (eg. true, false, null)\n                    let val = opts.value[txt];\n                    if (undefined !== val) {\n                        token.pin = VL;\n                        token.val = val;\n                        token.src = txt;\n                        token.len = pI - sI;\n                        sI = pI;\n                        lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                        return token;\n                    }\n                    state = LTX;\n                    continue next_char;\n                }\n                else if (LTX === state) {\n                    if (matchers(LTX, rule)) {\n                        return token;\n                    }\n                    let text_enders = opts.text.hoover ? config.hoover_ender : config.text_ender;\n                    // TODO: construct a RegExp to do this\n                    while (null != src[pI] &&\n                        (!text_enders.includes(src[pI]) ||\n                            (config.cmk0.includes(src[pI]) &&\n                                !config.cmk1.includes(src[pI + 1])))) {\n                        cI++;\n                        pI++;\n                    }\n                    token.len = pI - sI;\n                    token.pin = TX;\n                    token.val = src.substring(sI, pI);\n                    token.src = token.val;\n                    // Hoovering (ie. greedily consume non-token chars including internal space)\n                    // If hoovering, separate space at end from text\n                    if (opts.text.hoover &&\n                        config.multi.SP.includes(token.val[token.val.length - 1])) {\n                        // Find last non-space char\n                        let tI = token.val.length - 2;\n                        while (0 < tI && config.multi.SP.includes(token.val[tI]))\n                            tI--;\n                        token.val = token.val.substring(0, tI + 1);\n                        token.src = token.val;\n                        // Adjust column counter backwards by end space length\n                        cI -= (token.len - tI - 1);\n                        token.len = token.val.length;\n                        // Ensures end space will be seen as the next token \n                        sI += token.len;\n                    }\n                    else {\n                        sI = pI;\n                    }\n                    lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                    return token;\n                }\n                // Lexer State: CONSUME => all chars up to first ender\n                else if (LCS === state) {\n                    if (matchers(LCS, rule)) {\n                        return token;\n                    }\n                    pI = sI;\n                    while (pI < srclen && !enders.includes(src[pI]))\n                        pI++, cI++;\n                    token.val += src.substring(sI, pI);\n                    token.src = token.val;\n                    token.len = token.val.length;\n                    sI = pI;\n                    state = LTP;\n                    lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                    return token;\n                }\n                // Lexer State: MULTILINE => all chars up to last close marker, or end\n                else if (LML === state) {\n                    if (matchers(LML, rule)) {\n                        return token;\n                    }\n                    pI = sI;\n                    // Balance open and close markers (eg. if opts.balance.comment=true).\n                    let depth = 1;\n                    let open = state_param[0];\n                    let close = state_param[1];\n                    let balance = opts.balance[state_param[2]];\n                    let has_indent = !!state_param[3];\n                    let indent_str = '';\n                    let openlen = open.length;\n                    let closelen = close.length;\n                    if (has_indent) {\n                        let uI = sI - 1;\n                        while (-1 < uI && config.multi.SP.includes(src[uI--]))\n                            ;\n                        indent_str = config.multi.SP[0].repeat(sI - uI - 2);\n                    }\n                    // Assume starts with open string\n                    pI += open.length;\n                    while (pI < srclen && 0 < depth) {\n                        // Close first so that open === close case works\n                        if (close[0] === src[pI] &&\n                            close === src.substring(pI, pI + closelen)) {\n                            pI += closelen;\n                            cI += closelen;\n                            depth--;\n                        }\n                        else if (balance && open[0] === src[pI] &&\n                            open === src.substring(pI, pI + openlen)) {\n                            pI += openlen;\n                            cI += closelen;\n                            depth++;\n                        }\n                        else {\n                            // Count rows.\n                            if (opts.char.row === src[pI]) {\n                                rI++;\n                                cI = 0;\n                            }\n                            else {\n                                cI++;\n                            }\n                            pI++;\n                        }\n                    }\n                    token.val = src.substring(sI, pI);\n                    token.src = token.val;\n                    token.len = token.val.length;\n                    // Assume indent means block\n                    if (has_indent) {\n                        token.val =\n                            token.val.substring(openlen, token.val.length - closelen);\n                        token.val =\n                            token.val.replace(new RegExp(opts.char.row + indent_str, 'g'), '\\n');\n                        token.val = token.val.substring(1, token.val.length - 1);\n                    }\n                    sI = pI;\n                    state = LTP;\n                    lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n                    return token;\n                }\n            }\n            // LN001: keeps returning ZZ past end of input\n            token.pin = ZZ;\n            token.loc = srclen;\n            token.col = cI;\n            lexlog && lexlog(tn(token.pin), F(token.src), { ...token });\n            return token;\n        };\n        lex.src = src;\n        return lex;\n    }\n    // Describe state when lexing goes wrong using the signal token \"#BD\" (bad token!).\n    bad(ctx, log, why, token, sI, pI, rI, cI, val, src, use) {\n        token.why = why;\n        token.pin = util.token('#BD', ctx.config);\n        token.loc = pI;\n        token.row = rI;\n        token.col = cI;\n        token.len = pI - sI + 1;\n        token.val = val;\n        token.src = src;\n        token.use = use;\n        log && log(util.token(token.pin, ctx.config), ctx.F(token.src), { ...token }, 'error', why);\n        return token;\n    }\n    // Register a custom lexing matcher to be attempted first for given lex state.\n    // See _plugin_ folder for examples.\n    lex(state, matcher) {\n        if (null == state && null == matcher) {\n            return this.match;\n        }\n        else if (null == this.match[state]) {\n            throw new Error('jsonic: unknown lex state:' + state);\n        }\n        if (null != matcher) {\n            this.match[state].push(matcher);\n        }\n        return this.match[state];\n    }\n    // Clone the Lexer, and in particular the registered matchers.\n    clone(config) {\n        let lexer = new Lexer(config);\n        util.deep(lexer.match, this.match);\n        return lexer;\n    }\n}\nexports.Lexer = Lexer;\nvar RuleState;\n(function (RuleState) {\n    RuleState[RuleState[\"open\"] = 0] = \"open\";\n    RuleState[RuleState[\"close\"] = 1] = \"close\";\n})(RuleState || (RuleState = {}));\nclass Rule {\n    //val: any\n    //key: any\n    constructor(spec, ctx, node) {\n        this.id = ctx.rI++;\n        this.name = spec.name;\n        this.spec = spec;\n        this.node = node;\n        this.state = RuleState.open;\n        this.child = norule;\n        this.open = [];\n        this.close = [];\n        this.n = {};\n    }\n    process(ctx) {\n        let rule = norule;\n        if (RuleState.open === this.state) {\n            rule = this.spec.open(this, ctx);\n        }\n        else if (RuleState.close === this.state) {\n            rule = this.spec.close(this, ctx);\n        }\n        return rule;\n    }\n}\nexports.Rule = Rule;\nlet norule = { id: 0, spec: { name: 'norule' } };\nclass RuleSpec {\n    constructor(name, def) {\n        this.name = name;\n        this.def = def;\n        function norm_cond(cond) {\n            if ('object' === typeof (cond)) {\n                if (cond.n) {\n                    //console.log('RSC', name, cond.n)\n                    return (_alt, rule, _ctx) => {\n                        let pass = true;\n                        for (let cn in cond.n) {\n                            //pass = pass && (null == ctx.n[cn] || (ctx.n[cn] <= cond.n[cn]))\n                            pass = pass && (null == rule.n[cn] || (rule.n[cn] <= cond.n[cn]));\n                            //console.log('PASS', pass, cn)\n                        }\n                        return pass;\n                    };\n                }\n            }\n            return cond;\n        }\n        for (let alt of this.def.open) {\n            if (null != alt.c) {\n                alt.c = norm_cond(alt.c);\n            }\n        }\n        for (let alt of this.def.close) {\n            if (null != alt.c) {\n                alt.c = norm_cond(alt.c);\n            }\n        }\n    }\n    open(rule, ctx) {\n        let next = rule;\n        let why = '';\n        let F = ctx.F;\n        let out;\n        if (this.def.before_open) {\n            out = this.def.before_open.call(this, rule, ctx);\n            rule.node = out && out.node || rule.node;\n        }\n        let act = (null == out || !out.done) ?\n            this.parse_alts(this.def.open, rule, ctx) : { m: [] };\n        if (act.e) {\n            throw new JsonicError(S.unexpected, { open: true }, act.e, rule, ctx);\n        }\n        rule.open = act.m;\n        if (act.n) {\n            // TODO: auto delete counters if not specified?\n            for (let cn in act.n) {\n                //ctx.n[cn] = (null == ctx.n[cn] ? 0 : ctx.n[cn]) + act.n[cn]\n                //ctx.n[cn] = 0 < ctx.n[cn] ? ctx.n[cn] : 0\n                //rule.n[cn] = (null == rule.n[cn] ? 0 : rule.n[cn]) + act.n[cn]\n                rule.n[cn] = 0 === act.n[cn] ? 0 : (null == rule.n[cn] ? 0 : rule.n[cn]) + act.n[cn];\n                rule.n[cn] = 0 < rule.n[cn] ? rule.n[cn] : 0;\n            }\n            //console.log('OPEN', act.n, rule.n)\n        }\n        if (act.p) {\n            ctx.rs.push(rule);\n            next = rule.child = new Rule(ctx.rsm[act.p], ctx, rule.node);\n            next.parent = rule;\n            next.n = { ...rule.n };\n            why += 'U';\n        }\n        else if (act.r) {\n            next = new Rule(ctx.rsm[act.r], ctx, rule.node);\n            next.parent = rule.parent;\n            next.n = { ...rule.n };\n            why += 'R';\n        }\n        else {\n            why += 'Z';\n        }\n        if (this.def.after_open) {\n            this.def.after_open.call(this, rule, ctx, next);\n        }\n        ctx.log && ctx.log(S.node, rule.name + '/' + rule.id, RuleState[rule.state], why, F(rule.node));\n        rule.state = RuleState.close;\n        return next;\n    }\n    close(rule, ctx) {\n        let next = norule;\n        let why = '';\n        if (this.def.before_close) {\n            this.def.before_close.call(this, rule, ctx);\n        }\n        let act = 0 < this.def.close.length ? this.parse_alts(this.def.close, rule, ctx) : {};\n        if (act.e) {\n            throw new JsonicError(S.unexpected, { close: true }, act.e, rule, ctx);\n        }\n        if (act.n) {\n            for (let cn in act.n) {\n                //ctx.n[cn] = (null == ctx.n[cn] ? 0 : ctx.n[cn]) + act.n[cn]\n                //ctx.n[cn] = 0 < ctx.n[cn] ? ctx.n[cn] : 0\n                rule.n[cn] = 0 === act.n[cn] ? 0 : (null == rule.n[cn] ? 0 : rule.n[cn]) + act.n[cn];\n                rule.n[cn] = 0 < rule.n[cn] ? rule.n[cn] : 0;\n            }\n            //console.log('CLOSE', act.n, rule.n)\n        }\n        if (act.h) {\n            next = act.h(this, rule, ctx) || next;\n            why += 'H';\n        }\n        if (act.p) {\n            ctx.rs.push(rule);\n            next = rule.child = new Rule(ctx.rsm[act.p], ctx, rule.node);\n            next.parent = rule;\n            next.n = { ...rule.n };\n            why += 'U';\n        }\n        else if (act.r) {\n            next = new Rule(ctx.rsm[act.r], ctx, rule.node);\n            next.parent = rule.parent;\n            next.n = { ...rule.n };\n            why += 'R';\n        }\n        else {\n            next = ctx.rs.pop() || norule;\n            why += 'O';\n        }\n        if (this.def.after_close) {\n            this.def.after_close.call(this, rule, ctx, next);\n        }\n        next.why = why;\n        ctx.log && ctx.log(S.node, rule.name + '/' + rule.id, RuleState[rule.state], why, ctx.F(rule.node));\n        return next;\n    }\n    // First match wins.\n    parse_alts(alts, rule, ctx) {\n        let out = undefined;\n        let alt;\n        let altI = 0;\n        let t = ctx.config.token;\n        // End token reached.\n        if (t.ZZ === ctx.t0.pin) {\n            out = { m: [] };\n        }\n        else {\n            if (0 < alts.length) {\n                for (altI = 0; altI < alts.length; altI++) {\n                    alt = alts[altI];\n                    // Optional custom condition\n                    let cond = alt.c ? alt.c(alt, rule, ctx) : true;\n                    if (cond) {\n                        // No tokens to match.\n                        if (null == alt.s || 0 === alt.s.length) {\n                            out = { ...alt, m: [] };\n                            break;\n                        }\n                        // Match 1 or 2 tokens in sequence.\n                        else if (alt.s[0] === ctx.t0.pin) {\n                            if (1 === alt.s.length) {\n                                out = { ...alt, m: [ctx.t0] };\n                                break;\n                            }\n                            else if (alt.s[1] === ctx.t1.pin) {\n                                out = { ...alt, m: [ctx.t0, ctx.t1] };\n                                break;\n                            }\n                        }\n                        // Match any token.\n                        else if (t.AA === alt.s[0]) {\n                            out = { ...alt, m: [ctx.t0] };\n                            break;\n                        }\n                    }\n                    /*\n                    else if (t.ZZ === ctx.t0.pin) {\n                      out = { ...alt, m: [] }\n                      break\n                    }\n                    */\n                }\n                out = out || { e: ctx.t0, m: [] };\n            }\n            //else if (t.ZZ === ctx.t0.pin) {\n            //  out = { m: [] }\n            //}\n        }\n        out = out || { m: [] };\n        ctx.log && ctx.log('parse', rule.name + '/' + rule.id, RuleState[rule.state], altI < alts.length ? 'alt=' + altI : 'no-alt', altI < alts.length && alt && alt.s ?\n            '[' + alt.s.map((pin) => t[pin]).join(' ') + ']' : '[]', ctx.tI, 'p=' + (out.p || ''), 'r=' + (out.r || ''), 'b=' + (out.b || ''), out.m.map((tkn) => t[tkn.pin]).join(' '), ctx.F(out.m.map((tkn) => tkn.src)), \n        //'n:' + Object.entries(ctx.n).join(';'),\n        'n:' + Object.entries(rule.n).join(';'), out);\n        if (out.m) {\n            let mI = 0;\n            let rewind = out.m.length - (out.b || 0);\n            while (mI++ < rewind) {\n                ctx.next();\n            }\n        }\n        /*\n        if (out.e) {\n          console.log(out.e)\n        }\n        */\n        return out;\n    }\n}\nexports.RuleSpec = RuleSpec;\nclass Parser {\n    constructor(opts, config) {\n        this.rsm = {};\n        this.opts = opts;\n        this.config = config;\n    }\n    init() {\n        let t = this.config.token;\n        let top = (_alt, _rule, ctx) => 0 === ctx.rs.length;\n        let OB = t.OB;\n        let CB = t.CB;\n        let OS = t.OS;\n        let CS = t.CS;\n        let CL = t.CL;\n        let CA = t.CA;\n        let TX = t.TX;\n        let NR = t.NR;\n        let ST = t.ST;\n        let VL = t.VL;\n        let AA = t.AA;\n        let rules = {\n            val: {\n                open: [\n                    // TODO: n - auto delete unmentioned counters\n                    { s: [OB, CA], p: S.map, n: { im: 0 } },\n                    { s: [OB], p: S.map, n: { im: 0 } },\n                    { s: [OS], p: S.list },\n                    { s: [CA], p: S.list, b: 1 },\n                    // Implicit map - operates at any depth\n                    // NOTE: `n.im` counts depth of implicit maps \n                    { s: [TX, CL], p: S.map, b: 2, n: { im: 1 } },\n                    { s: [ST, CL], p: S.map, b: 2, n: { im: 1 } },\n                    { s: [NR, CL], p: S.map, b: 2, n: { im: 1 } },\n                    { s: [VL, CL], p: S.map, b: 2, n: { im: 1 } },\n                    { s: [TX] },\n                    { s: [NR] },\n                    { s: [ST] },\n                    { s: [VL] },\n                ],\n                close: [\n                    // Implicit list works only at top level\n                    {\n                        s: [CA], c: top, r: S.elem,\n                        h: (_spec, rule, _ctx) => {\n                            rule.node = [rule.node];\n                        }\n                    },\n                    // Close value, and map or list, but perhaps there are more elem?\n                    { s: [AA], b: 1 },\n                ],\n                before_close: (rule) => {\n                    var _a, _b;\n                    rule.node = (_a = rule.child.node) !== null && _a !== void 0 ? _a : (_b = rule.open[0]) === null || _b === void 0 ? void 0 : _b.val;\n                },\n            },\n            map: {\n                before_open: () => {\n                    return { node: {} };\n                },\n                open: [\n                    { s: [CB] },\n                    { p: S.pair } // no tokens, pass node\n                ],\n                close: []\n            },\n            list: {\n                before_open: () => {\n                    return { node: [] };\n                },\n                open: [\n                    { s: [CS] },\n                    { p: S.elem } // no tokens, pass node\n                ],\n                close: []\n            },\n            // sets key:val on node\n            pair: {\n                open: [\n                    { s: [ST, CL], p: S.val },\n                    { s: [TX, CL], p: S.val },\n                    { s: [NR, CL], p: S.val },\n                    { s: [VL, CL], p: S.val },\n                    { s: [CB], b: 1 },\n                ],\n                close: [\n                    { s: [CB] },\n                    // NOTE: only proceed to second pair if implict depth <=1\n                    // Otherwise walk back up the implicit maps. This prevents\n                    // greedy capture of following pairs. See feature:property-dive test.\n                    { s: [CA], c: { n: { im: 1 } }, r: S.pair },\n                    { s: [CA], n: { im: -1 }, b: 1 },\n                    // Who needs commas anyway?\n                    { s: [ST, CL], c: { n: { im: 1 } }, r: S.pair, b: 2 },\n                    { s: [TX, CL], c: { n: { im: 1 } }, r: S.pair, b: 2 },\n                    { s: [NR, CL], c: { n: { im: 1 } }, r: S.pair, b: 2 },\n                    { s: [VL, CL], c: { n: { im: 1 } }, r: S.pair, b: 2 },\n                    { s: [ST, CL], n: { im: -1 }, b: 2 },\n                    { s: [TX, CL], n: { im: -1 }, b: 2 },\n                    { s: [NR, CL], n: { im: -1 }, b: 2 },\n                    { s: [VL, CL], n: { im: -1 }, b: 2 },\n                ],\n                before_close: (rule, ctx) => {\n                    let key_token = rule.open[0];\n                    if (key_token && CB !== key_token.pin) {\n                        let key = ST === key_token.pin ? key_token.val : key_token.src;\n                        let prev = rule.node[key];\n                        rule.node[key] = null == prev ? rule.child.node :\n                            (ctx.opts.object.extend ? util.deep(prev, rule.child.node) :\n                                rule.child.node);\n                    }\n                },\n            },\n            // push onto node\n            elem: {\n                open: [\n                    { s: [OB], p: S.map, n: { im: 0 } },\n                    { s: [OS], p: S.list },\n                    // TODO: replace with { p: S.val} as last entry\n                    // IMPORTANT! makes array values consistent with prop values\n                    { s: [TX] },\n                    { s: [NR] },\n                    { s: [ST] },\n                    { s: [VL] },\n                    // Insert null for initial comma\n                    { s: [CA, CA], b: 2 },\n                    { s: [CA] },\n                ],\n                close: [\n                    // Ignore trailing comma\n                    { s: [CA, CS] },\n                    // Next elemen\n                    { s: [CA], r: S.elem },\n                    // End lis\n                    { s: [CS] },\n                    // Who needs commas anyway?\n                    { s: [OB], p: S.map, n: { im: 0 } },\n                    { s: [OS], p: S.list, },\n                    { s: [TX, CL], p: S.map, n: { im: 0 }, b: 2 },\n                    { s: [NR, CL], p: S.map, n: { im: 0 }, b: 2 },\n                    { s: [ST, CL], p: S.map, n: { im: 0 }, b: 2 },\n                    { s: [VL, CL], p: S.map, n: { im: 0 }, b: 2 },\n                    { s: [TX], r: S.elem, b: 1 },\n                    { s: [NR], r: S.elem, b: 1 },\n                    { s: [ST], r: S.elem, b: 1 },\n                    { s: [VL], r: S.elem, b: 1 },\n                ],\n                after_open: (rule, _ctx, next) => {\n                    if (rule === next && rule.open[0]) {\n                        let val = rule.open[0].val;\n                        // Insert `null` if no value preceeded the comma (eg. [,1] -> [null, 1])\n                        rule.node.push(null != val ? val : null);\n                    }\n                },\n                before_close: (rule) => {\n                    if (rule.child.node) {\n                        rule.node.push(rule.child.node);\n                    }\n                },\n            }\n        };\n        this.rsm = Object.keys(rules).reduce((rs, rn) => {\n            rs[rn] = new RuleSpec(rn, rules[rn]);\n            return rs;\n        }, {});\n    }\n    rule(name, define) {\n        this.rsm[name] = null == define ? this.rsm[name] : (define(this.rsm[name], this.rsm) || this.rsm[name]);\n        return this.rsm[name];\n    }\n    start(lexer, src, jsonic, meta, partial_ctx) {\n        let opts = this.opts;\n        let config = this.config;\n        let root;\n        let ctx = {\n            rI: 1,\n            opts,\n            config,\n            meta: meta || {},\n            src: () => src,\n            root: () => root.node,\n            plugins: () => jsonic.internal().plugins,\n            node: undefined,\n            u2: lexer.end,\n            u1: lexer.end,\n            t0: lexer.end,\n            t1: lexer.end,\n            tI: -2,\n            next,\n            rs: [],\n            rsm: this.rsm,\n            //n: {},\n            log: (meta && meta.log) || undefined,\n            F: util.make_src_format(config),\n            use: {}\n        };\n        if (null != partial_ctx) {\n            ctx = util.deep(ctx, partial_ctx);\n        }\n        util.make_log(ctx);\n        let tn = (pin) => util.token(pin, this.config);\n        let lex = util.wrap_bad_lex(lexer.start(ctx), util.token('#BD', this.config), ctx);\n        let rule = new Rule(this.rsm.val, ctx);\n        root = rule;\n        // Maximum rule iterations (prevents infinite loops). Allow for\n        // rule open and close, and for each rule on each char to be\n        // virtual (like map, list), and double for safety margin (allows\n        // lots of backtracking), and apply a multipler.\n        let maxr = 2 * Object.keys(this.rsm).length * lex.src.length *\n            2 * opts.rule.maxmul;\n        // Lex next token.\n        function next(ignore = true) {\n            ctx.u2 = ctx.u1;\n            ctx.u1 = ctx.t0;\n            ctx.t0 = ctx.t1;\n            let t1;\n            do {\n                t1 = lex(rule);\n                ctx.tI++;\n            } while (ignore && config.tokenset.IGNORE.includes(t1.pin));\n            ctx.t1 = { ...t1 };\n            return ctx.t0;\n        }\n        // Look two tokens ahead\n        next();\n        next();\n        // Process rules on tokens\n        let rI = 0;\n        // This loop is the heart of the engine. Keep processing rule\n        // occurrences until there's none left.\n        while (norule !== rule && rI < maxr) {\n            ctx.log &&\n                ctx.log('rule', rule.name + '/' + rule.id, RuleState[rule.state], ctx.rs.length, ctx.tI, '[' + tn(ctx.t0.pin) + ' ' + tn(ctx.t1.pin) + ']', '[' + ctx.F(ctx.t0.src) + ' ' + ctx.F(ctx.t1.src) + ']', rule, ctx);\n            rule = rule.process(ctx);\n            ctx.log &&\n                ctx.log('stack', ctx.rs.length, ctx.rs.map((r) => r.name + '/' + r.id).join(';'), rule, ctx);\n            rI++;\n        }\n        // TODO: must end with t.ZZ token else error\n        // NOTE: by returning root, we get implicit closing of maps and lists.\n        return root.node;\n    }\n    clone(opts, config) {\n        let parser = new Parser(opts, config);\n        parser.rsm = Object\n            .keys(this.rsm)\n            .reduce((a, rn) => (a[rn] = util.clone(this.rsm[rn]), a), {});\n        return parser;\n    }\n}\nexports.Parser = Parser;\nlet util = {\n    // Uniquely resolve or assign token pin number\n    token: function token(ref, config, jsonic) {\n        let tokenmap = config.token;\n        let token = tokenmap[ref];\n        if (null == token && S.string === typeof (ref)) {\n            token = config.tokenI++;\n            tokenmap[token] = ref;\n            tokenmap[ref] = token;\n            tokenmap[ref.substring(1)] = token;\n            if (null != jsonic) {\n                Object.assign(jsonic.token, config.token);\n            }\n        }\n        return token;\n    },\n    // Deep override for plain data. Retains base object and array.\n    // Array merge by `over` index, `over` wins non-matching types, expect:\n    // `undefined` always loses, `over` plain objects inject into functions,\n    // and `over` functions always win. Over always copied.\n    deep: function (base, ...rest) {\n        let base_is_function = S.function === typeof (base);\n        let base_is_object = null != base &&\n            (S.object === typeof (base) || base_is_function);\n        for (let over of rest) {\n            let over_is_function = S.function === typeof (over);\n            let over_is_object = null != over &&\n                (S.object === typeof (over) || over_is_function);\n            if (base_is_object &&\n                over_is_object &&\n                !over_is_function &&\n                (Array.isArray(base) === Array.isArray(over))) {\n                for (let k in over) {\n                    base[k] = util.deep(base[k], over[k]);\n                }\n            }\n            else {\n                base = undefined === over ? base :\n                    over_is_function ? over :\n                        (over_is_object ?\n                            util.deep(Array.isArray(over) ? [] : {}, over) : over);\n                base_is_function = S.function === typeof (base);\n                base_is_object = null != base &&\n                    (S.object === typeof (base) || base_is_function);\n            }\n        }\n        return base;\n    },\n    clone: function (class_instance) {\n        return util.deep(Object.create(Object.getPrototypeOf(class_instance)), class_instance);\n    },\n    // Convert string to char code array.\n    // 'ab' -> [97,98]\n    s2cca: function (s) {\n        return s.split('').map((c) => c.charCodeAt(0));\n    },\n    longest: (strs) => strs.reduce((a, s) => a < s.length ? s.length : a, 0),\n    make_src_format: (config) => (s, j) => null == s ? '' : (j = JSON.stringify(s),\n        j.substring(0, config.debug.maxlen) +\n            (config.debug.maxlen < j.length ? '...' : '')),\n    // Special debug logging to console (use Jsonic('...', {log:N})).\n    // log:N -> console.dir to depth N\n    // log:-1 -> console.dir to depth 1, omitting objects (good summary!)\n    make_log: (ctx) => {\n        if ('number' === typeof ctx.log) {\n            let exclude_objects = false;\n            let logdepth = ctx.log;\n            if (-1 === logdepth) {\n                logdepth = 1;\n                exclude_objects = true;\n            }\n            ctx.log = (...rest) => {\n                if (exclude_objects) {\n                    let logstr = rest\n                        .filter((item) => S.object != typeof (item))\n                        .join('\\t');\n                    ctx.opts.debug.get_console().log(logstr);\n                }\n                else {\n                    ctx.opts.debug.get_console().dir(rest, { depth: logdepth });\n                }\n                return undefined;\n            };\n        }\n    },\n    wrap_bad_lex: (lex, BD, ctx) => {\n        let wrap = (rule) => {\n            let token = lex(rule);\n            if (BD === token.pin) {\n                let details = {};\n                if (null != token.use) {\n                    details.use = token.use;\n                }\n                throw new JsonicError(token.why || S.unexpected, details, token, rule, ctx);\n            }\n            return token;\n        };\n        wrap.src = lex.src;\n        return wrap;\n    },\n    errinject: (s, code, details, token, rule, ctx) => {\n        return s.replace(/\\$([\\w_]+)/g, (_m, name) => {\n            return JSON.stringify('code' === name ? code : (details[name] ||\n                ctx.meta[name] ||\n                token[name] ||\n                rule[name] ||\n                ctx[name] ||\n                ctx.opts[name] ||\n                ctx.config[name] ||\n                '$' + name));\n        });\n    },\n    extract: (src, errtxt, token) => {\n        let loc = 0 < token.loc ? token.loc : 0;\n        let row = 0 < token.row ? token.row : 0;\n        let col = 0 < token.col ? token.col : 0;\n        let tsrc = null == token.src ? '' : token.src;\n        let behind = src.substring(Math.max(0, loc - 333), loc).split('\\n');\n        let ahead = src.substring(loc, loc + 333).split('\\n');\n        let pad = 2 + ('' + (row + 2)).length;\n        let rI = row < 2 ? 0 : row - 2;\n        let ln = (s) => '\\x1b[34m' + ('' + (rI++)).padStart(pad, ' ') +\n            ' | \\x1b[0m' + (null == s ? '' : s);\n        let blen = behind.length;\n        let lines = [\n            2 < blen ? ln(behind[blen - 3]) : null,\n            1 < blen ? ln(behind[blen - 2]) : null,\n            ln(behind[blen - 1] + ahead[0]),\n            (' '.repeat(pad)) + '   ' +\n                ' '.repeat(col) +\n                '\\x1b[31m' + '^'.repeat(tsrc.length || 1) +\n                ' ' + errtxt + '\\x1b[0m',\n            ln(ahead[1]),\n            ln(ahead[2]),\n        ]\n            .filter((line) => null != line)\n            .join('\\n');\n        return lines;\n    },\n    handle_meta_mode: (self, src, meta) => {\n        let opts = self.options;\n        if (S.function === typeof (opts.mode[meta.mode])) {\n            try {\n                return opts.mode[meta.mode].call(self, src, meta);\n            }\n            catch (ex) {\n                if ('SyntaxError' === ex.name) {\n                    let loc = 0;\n                    let row = 0;\n                    let col = 0;\n                    let tsrc = '';\n                    let errloc = ex.message.match(/^Unexpected token (.) .*position\\s+(\\d+)/i);\n                    if (errloc) {\n                        tsrc = errloc[1];\n                        loc = parseInt(errloc[2]);\n                        row = src.substring(0, loc).replace(/[^\\n]/g, '').length;\n                        //row = row < 0 ? 0 : row\n                        let cI = loc - 1;\n                        while (-1 < cI && '\\n' !== src.charAt(cI))\n                            cI--;\n                        col = cI < loc ? src.substring(cI, loc).length - 1 : 0;\n                    }\n                    let token = ex.token || {\n                        pin: self.token.UK,\n                        loc: loc,\n                        len: tsrc.length,\n                        row: ex.lineNumber || row,\n                        col: ex.columnNumber || col,\n                        val: undefined,\n                        src: tsrc,\n                    };\n                    throw new JsonicError(ex.code || 'json', ex.details || {\n                        msg: ex.message\n                    }, token, {}, ex.ctx || {\n                        rI: -1,\n                        opts,\n                        config: { token: {} },\n                        token: {},\n                        meta,\n                        src: () => src,\n                        root: () => undefined,\n                        plugins: () => [],\n                        node: undefined,\n                        u2: token,\n                        u1: token,\n                        t0: token,\n                        t1: token,\n                        tI: -1,\n                        rs: [],\n                        next: () => token,\n                        rsm: {},\n                        n: {},\n                        log: meta.log,\n                        F: util.make_src_format(self.internal().config),\n                        use: {},\n                    });\n                }\n                else\n                    throw ex;\n            }\n        }\n        else {\n            return [false];\n        }\n    },\n    // Idempotent normalization of options.\n    build_config_from_options: function (config, opts) {\n        let cc = (s) => s.charCodeAt(0);\n        let token_names = Object.keys(opts.token);\n        // Index of tokens by name.\n        token_names.forEach(tn => util.token(tn, config));\n        let single_char_token_names = token_names\n            .filter(tn => null != opts.token[tn].c);\n        // Sparse index array of single char codes\n        config.single = single_char_token_names\n            .reduce((a, tn) => (a[cc(opts.token[tn].c)] =\n            config.token[tn], a), []);\n        let multi_char_token_names = token_names\n            .filter(tn => S.string === typeof opts.token[tn]);\n        // Char code arrays for lookup by char code.\n        config.start = multi_char_token_names\n            .reduce((a, tn) => (a[tn.substring(1)] = util.s2cca(opts.token[tn]), a), {});\n        config.multi = multi_char_token_names\n            .reduce((a, tn) => (a[tn.substring(1)] = opts.token[tn], a), {});\n        let tokenset_names = token_names\n            .filter(tn => null != opts.token[tn].s);\n        // Char code arrays for lookup by char code.\n        config.tokenset = tokenset_names\n            .reduce((a, tsn) => (a[tsn.substring(1)] = (opts.token[tsn].s.split(',')\n            .map((tn) => config.token[tn])),\n            a), {});\n        // Lookup table for escape chars, indexed by denotating char (e.g. n for \\n).\n        opts.escape = opts.escape || {};\n        config.escape = Object.keys(opts.escape)\n            .reduce((a, ed) => (a[ed.charCodeAt(0)] = opts.escape[ed], a), []);\n        config.start_cm = [];\n        config.cm_single = '';\n        config.cmk = [];\n        config.cmk0 = '';\n        config.cmk1 = '';\n        if (opts.comment) {\n            let comment_markers = Object.keys(opts.comment);\n            comment_markers.forEach(k => {\n                // Single char comment marker (eg. `#`)\n                if (1 === k.length) {\n                    config.start_cm.push(k.charCodeAt(0));\n                    config.cm_single += k;\n                }\n                // String comment marker (eg. `//`)\n                else {\n                    config.start_cm.push(k.charCodeAt(0));\n                    config.cmk.push(k);\n                    config.cmk0 += k[0];\n                    config.cmk1 += k[1];\n                }\n            });\n            config.cmk_maxlen = util.longest(comment_markers);\n        }\n        config.start_cm_char =\n            config.start_cm.map((cc) => String.fromCharCode(cc)).join('');\n        config.start_bm = [];\n        config.bmk = [];\n        let block_markers = Object.keys(opts.string.block);\n        block_markers.forEach(k => {\n            config.start_bm.push(k.charCodeAt(0));\n            config.bmk.push(k);\n        });\n        config.bmk_maxlen = util.longest(block_markers);\n        config.single_char =\n            config.single.map((_s, cc) => String.fromCharCode(cc)).join('');\n        // Enders are char sets that end lexing for a given token\n        config.value_ender =\n            config.multi.SP +\n                config.multi.LN +\n                config.single_char +\n                config.start_cm_char;\n        config.text_ender = config.value_ender;\n        config.hoover_ender =\n            config.multi.LN +\n                config.single_char +\n                config.start_cm_char;\n        config.lex = {\n            core: {\n                LN: util.token(opts.lex.core.LN, config)\n            }\n        };\n        config.number = {\n            sep_re: null != opts.number.sep ? new RegExp(opts.number.sep, 'g') : null\n        };\n        config.debug = opts.debug;\n        // TOOD: maybe make this a debug option?\n        // console.log(config)\n    },\n};\nexports.util = util;\nfunction make(first, parent) {\n    // Handle polymorphic params.\n    let param_opts = first;\n    if (S.function === typeof (first)) {\n        param_opts = {};\n        parent = first;\n    }\n    let lexer;\n    let parser;\n    let config;\n    let plugins;\n    // Merge options.\n    let opts = util.deep({}, parent ? { ...parent.options } : make_standard_options(), param_opts);\n    // Create primary parsing function\n    let self = function Jsonic(src, meta, partial_ctx) {\n        if (S.string === typeof (src)) {\n            let internal = self.internal();\n            let [done, out] = (null != meta && null != meta.mode) ? util.handle_meta_mode(self, src, meta) :\n                [false];\n            if (!done) {\n                out = internal.parser.start(internal.lexer, src, self, meta, partial_ctx);\n            }\n            return out;\n        }\n        return src;\n    };\n    self.token = function token(ref) {\n        return util.token(ref, config, self);\n    };\n    // Transfer parent properties (preserves plugin decorations, etc).\n    if (parent) {\n        for (let k in parent) {\n            self[k] = parent[k];\n        }\n        self.parent = parent;\n        let parent_internal = parent.internal();\n        config = util.deep({}, parent_internal.config);\n        util.build_config_from_options(config, opts);\n        Object.assign(self.token, config.token);\n        plugins = [...parent_internal.plugins];\n        lexer = parent_internal.lexer.clone(config);\n        parser = parent_internal.parser.clone(opts, config);\n    }\n    else {\n        config = {\n            tokenI: 1,\n            token: {}\n        };\n        util.build_config_from_options(config, opts);\n        plugins = [];\n        lexer = new Lexer(config);\n        parser = new Parser(opts, config);\n        parser.init();\n    }\n    Object.assign(self.token, config.token);\n    self.internal = () => ({\n        lexer,\n        parser,\n        config,\n        plugins,\n    });\n    let optioner = (change_opts) => {\n        if (null != change_opts && S.object === typeof (change_opts)) {\n            util.build_config_from_options(config, util.deep(opts, change_opts));\n            for (let k in opts) {\n                self.options[k] = opts[k];\n            }\n        }\n        return self;\n    };\n    self.options = util.deep(optioner, opts);\n    self.parse = self;\n    self.use = function use(plugin, plugin_opts) {\n        self.options({ plugin: { [plugin.name]: plugin_opts || {} } });\n        self.internal().plugins.push(plugin);\n        return plugin(self) || self;\n    };\n    self.rule = function rule(name, define) {\n        let rule = self.internal().parser.rule(name, define);\n        return null == define ? rule : self;\n    };\n    self.lex = function lex(state, match) {\n        let lexer = self.internal().lexer;\n        let matching = lexer.lex(state, match);\n        return null == match ? matching : self;\n    };\n    self.make = function (opts) {\n        return make(opts, self);\n    };\n    Object.defineProperty(self.make, 'name', { value: 'make' });\n    return self;\n}\nexports.make = make;\n// Generate hint text lookup.\n// NOTE: generated and inserted by hint.js\nfunction make_hint(d = (t, r = 'replace') => t[r](/[A-Z]/g, (m) => ' ' + m.toLowerCase())[r](/[~%][a-z]/g, (m) => ('~' == m[0] ? ' ' : '') + m[1].toUpperCase()), s = '~sinceTheErrorIsUnknown,ThisIsProbablyABugInsideJsonic\\nitself.~pleaseConsiderPostingAGithubIssue -Thanks!|~theCharacter(s) $srcShouldNotOccurAtThisPointAsItIsNot\\nvalid %j%s%o%nSyntax,EvenUnderTheRelaxedJsonicRules.~ifItIs\\nnotObviouslyWrong,TheActualSyntaxErrorMayBeElsewhere.~try\\ncommentingOutLargerAreasAroundThisPointUntilYouGetNoErrors,\\nthenRemoveTheCommentsInSmallSectionsUntilYouFindThe\\noffendingSyntax.~n%o%t%e:~alsoCheckIfAnyPluginsOrModesYouAre\\nusingExpectDifferentSyntaxInThisCase.|~theEscapeSequence $srcDoesNotEncodeAValidUnicodeCodePoint\\nnumber.~youMayNeedToValidateYourStringDataManuallyUsingTest\\ncodeToSeeHow~javaScriptWillInterpretIt.~alsoConsiderThatYour\\ndataMayHaveBecomeCorrupted,OrTheEscapeSequenceHasNotBeen\\ngeneratedCorrectly.|~theEscapeSequence $srcDoesNotEncodeAValid~a%s%c%i%iCharacter.~you\\nmayNeedToValidateYourStringDataManuallyUsingTestCodeToSee\\nhow~javaScriptWillInterpretIt.~alsoConsiderThatYourDataMay\\nhaveBecomeCorrupted,OrTheEscapeSequenceHasNotBeenGenerated\\ncorrectly.|~stringValuesCannotContainUnprintableCharacters (characterCodes\\nbelow 32).~theCharacter $srcIsUnprintable.~youMayNeedToRemove\\ntheseCharactersFromYourSourceData.~alsoCheckThatItHasNot\\nbecomeCorrupted.|~stringValuesCannotBeMissingTheirFinalQuoteCharacter,Which\\nshouldMatchTheirInitialQuoteCharacter.'.split('|')) { return 'unknown|unexpected|invalid_unicode|invalid_ascii|unprintable|unterminated'.split('|').reduce((a, n, i) => (a[n] = d(s[i]), a), {}); }\nlet Jsonic = make();\nexports.Jsonic = Jsonic;\nJsonic.Jsonic = Jsonic;\nJsonic.JsonicError = JsonicError;\nJsonic.Lexer = Lexer;\nJsonic.Parser = Parser;\nJsonic.Rule = Rule;\nJsonic.RuleSpec = RuleSpec;\nJsonic.util = util;\nJsonic.make = make;\nexports.default = Jsonic;\n//-NODE-MODULE-FIX;('undefined' != typeof(module) && (module.exports = exports.Jsonic));\n//# sourceMappingURL=jsonic.js.map\n"
  },
  {
    "path": "docs/plugin/csv.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>csv | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/17.0d1f36b1.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/csv.html#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" class=\"sidebar-link\">native</a></li><li><a href=\"/plugin/csv.html\" aria-current=\"page\" class=\"active sidebar-link\">csv</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/plugin/csv.html#usage\" class=\"sidebar-link\">Usage</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/csv.html#syntax\" class=\"sidebar-link\">Syntax</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/csv.html#options\" class=\"sidebar-link\">Options</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/csv.html#implementation\" class=\"sidebar-link\">Implementation</a></li></ul></li><li><a href=\"/plugin/hoover.html\" class=\"sidebar-link\">hoover</a></li><li><a href=\"/plugin/json.html\" class=\"sidebar-link\">json</a></li><li><a href=\"/plugin/dynamic.html\" class=\"sidebar-link\">dynamic</a></li><li><a href=\"/plugin/multifile.html\" class=\"sidebar-link\">multifile</a></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/csv.html#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/csv.html#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"csv\"><a href=\"#csv\" class=\"header-anchor\">#</a> <code>csv</code></h1> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax only supports standard JSON value\nkeywords: <code>true</code>, <code>false</code>, <code>null</code>.</p> <p>Wouldn't it be nice to have <code>undefined</code>, <code>Infinity</code>, <code>NaN</code> as well?\nAnd while we're at it, how about recognizing literal <code>/regexp/</code>\nsyntax, and ISO (<code>2021-03-19T17:15:51.845Z</code>) dates?</p> <p>The <code>csv</code> plugin will do this for you!</p> <p>This is a good plugin to copy and extend if you just want to add some\n&quot;magic&quot; values to your source data.</p> <h2 id=\"usage\"><a href=\"#usage\" class=\"header-anchor\">#</a> Usage</h2> <p>To use the plugin, <code>require</code> or <code>import</code> the module path: <code>jsonic/plugin/csv</code>:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// Node.js</span>\n<span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Csv <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/csv'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Web</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Csv <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic/plugin/csv'</span>\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(The convention for loading modules that are Jsonic plugins is to deconstruct: <code>{ PluginName }</code> )</small></p> <p>Once loaded, parse your source data as normal.</p> <h3 id=\"quick-example\"><a href=\"#quick-example\" class=\"header-anchor\">#</a> Quick example</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Csv <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/csv'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Csv<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h2 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h2> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax remains available, with the following\nextensions:</p> <h3 id=\"csv-value-keywords\"><a href=\"#csv-value-keywords\" class=\"header-anchor\">#</a> Csv value keywords</h3> <ul><li><code>undefined</code></li> <li><code>NaN</code></li> <li><code>Infinity</code> (optional prefix <code>+</code> or <code>-</code>)</li></ul> <h3 id=\"regular-expressions\"><a href=\"#regular-expressions\" class=\"header-anchor\">#</a> Regular Expressions</h3> <p>Characters between '/' and '/' are converted to a <code>RegExp</code> object.</p> <ul><li><code>/a/</code> // === new RegExp('a')</li> <li><code>/\\//g</code> // === new RegExp('\\/','g')</li></ul> <h3 id=\"iso-dates\"><a href=\"#iso-dates\" class=\"header-anchor\">#</a> ISO Dates</h3> <p>Unquoted text that matches the ISO date format is converted into a <code>Date</code> object.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \n</code></pre></div><h2 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h2> <p>This plugin has no options.</p> <h2 id=\"implementation\"><a href=\"#implementation\" class=\"header-anchor\">#</a> Implementation</h2> <p>The source code for this plugin is\nhere: <a href=\"github.com/jsonicjs/jsonic/blob/master/plugin/csv.ts\"><code>plugin/csv.ts</code></a>.</p> <p>TODO - discuss</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/plugin/native.html\" class=\"prev\">\n        native\n      </a></span> <span class=\"next\"><a href=\"/plugin/hoover.html\">\n        hoover\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/17.0d1f36b1.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/plugin/dynamic.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>dynamic | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/18.0f184e32.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/dynamic.html#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" class=\"sidebar-link\">native</a></li><li><a href=\"/plugin/csv.html\" class=\"sidebar-link\">csv</a></li><li><a href=\"/plugin/hoover.html\" class=\"sidebar-link\">hoover</a></li><li><a href=\"/plugin/json.html\" class=\"sidebar-link\">json</a></li><li><a href=\"/plugin/dynamic.html\" aria-current=\"page\" class=\"active sidebar-link\">dynamic</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/plugin/dynamic.html#usage\" class=\"sidebar-link\">Usage</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/dynamic.html#syntax\" class=\"sidebar-link\">Syntax</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/dynamic.html#options\" class=\"sidebar-link\">Options</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/dynamic.html#implementation\" class=\"sidebar-link\">Implementation</a></li></ul></li><li><a href=\"/plugin/multifile.html\" class=\"sidebar-link\">multifile</a></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/dynamic.html#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/dynamic.html#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"dynamic\"><a href=\"#dynamic\" class=\"header-anchor\">#</a> <code>dynamic</code></h1> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax only supports standard JSON value\nkeywords: <code>true</code>, <code>false</code>, <code>null</code>.</p> <p>Wouldn't it be nice to have <code>undefined</code>, <code>Infinity</code>, <code>NaN</code> as well?\nAnd while we're at it, how about recognizing literal <code>/regexp/</code>\nsyntax, and ISO (<code>2021-03-19T17:15:51.845Z</code>) dates?</p> <p>The <code>dynamic</code> plugin will do this for you!</p> <p>This is a good plugin to copy and extend if you just want to add some\n&quot;magic&quot; values to your source data.</p> <h2 id=\"usage\"><a href=\"#usage\" class=\"header-anchor\">#</a> Usage</h2> <p>To use the plugin, <code>require</code> or <code>import</code> the module path: <code>jsonic/plugin/dynamic</code>:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// Node.js</span>\n<span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Dynamic <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/dynamic'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Web</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Dynamic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic/plugin/dynamic'</span>\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(The convention for loading modules that are Jsonic plugins is to deconstruct: <code>{ PluginName }</code> )</small></p> <p>Once loaded, parse your source data as normal.</p> <h3 id=\"quick-example\"><a href=\"#quick-example\" class=\"header-anchor\">#</a> Quick example</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Dynamic <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/dynamic'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Dynamic<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h2 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h2> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax remains available, with the following\nextensions:</p> <h3 id=\"dynamic-value-keywords\"><a href=\"#dynamic-value-keywords\" class=\"header-anchor\">#</a> Dynamic value keywords</h3> <ul><li><code>undefined</code></li> <li><code>NaN</code></li> <li><code>Infinity</code> (optional prefix <code>+</code> or <code>-</code>)</li></ul> <h3 id=\"regular-expressions\"><a href=\"#regular-expressions\" class=\"header-anchor\">#</a> Regular Expressions</h3> <p>Characters between '/' and '/' are converted to a <code>RegExp</code> object.</p> <ul><li><code>/a/</code> // === new RegExp('a')</li> <li><code>/\\//g</code> // === new RegExp('\\/','g')</li></ul> <h3 id=\"iso-dates\"><a href=\"#iso-dates\" class=\"header-anchor\">#</a> ISO Dates</h3> <p>Unquoted text that matches the ISO date format is converted into a <code>Date</code> object.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \n</code></pre></div><h2 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h2> <p>This plugin has no options.</p> <h2 id=\"implementation\"><a href=\"#implementation\" class=\"header-anchor\">#</a> Implementation</h2> <p>The source code for this plugin is\nhere: <a href=\"github.com/jsonicjs/jsonic/blob/master/plugin/dynamic.ts\"><code>plugin/dynamic.ts</code></a>.</p> <p>TODO - discuss</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/plugin/json.html\" class=\"prev\">\n        json\n      </a></span> <span class=\"next\"><a href=\"/plugin/multifile.html\">\n        multifile\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/18.0f184e32.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/plugin/hoover.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>hoover | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/19.57ad231b.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/hoover.html#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" class=\"sidebar-link\">native</a></li><li><a href=\"/plugin/csv.html\" class=\"sidebar-link\">csv</a></li><li><a href=\"/plugin/hoover.html\" aria-current=\"page\" class=\"active sidebar-link\">hoover</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/plugin/hoover.html#usage\" class=\"sidebar-link\">Usage</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/hoover.html#syntax\" class=\"sidebar-link\">Syntax</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/hoover.html#options\" class=\"sidebar-link\">Options</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/hoover.html#implementation\" class=\"sidebar-link\">Implementation</a></li></ul></li><li><a href=\"/plugin/json.html\" class=\"sidebar-link\">json</a></li><li><a href=\"/plugin/dynamic.html\" class=\"sidebar-link\">dynamic</a></li><li><a href=\"/plugin/multifile.html\" class=\"sidebar-link\">multifile</a></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/hoover.html#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/hoover.html#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"hoover\"><a href=\"#hoover\" class=\"header-anchor\">#</a> <code>hoover</code></h1> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax only supports standard JSON value\nkeywords: <code>true</code>, <code>false</code>, <code>null</code>.</p> <p>Wouldn't it be nice to have <code>undefined</code>, <code>Infinity</code>, <code>NaN</code> as well?\nAnd while we're at it, how about recognizing literal <code>/regexp/</code>\nsyntax, and ISO (<code>2021-03-19T17:15:51.845Z</code>) dates?</p> <p>The <code>hoover</code> plugin will do this for you!</p> <p>This is a good plugin to copy and extend if you just want to add some\n&quot;magic&quot; values to your source data.</p> <h2 id=\"usage\"><a href=\"#usage\" class=\"header-anchor\">#</a> Usage</h2> <p>To use the plugin, <code>require</code> or <code>import</code> the module path: <code>jsonic/plugin/hoover</code>:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// Node.js</span>\n<span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Hoover <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/hoover'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Web</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Hoover <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic/plugin/hoover'</span>\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(The convention for loading modules that are Jsonic plugins is to deconstruct: <code>{ PluginName }</code> )</small></p> <p>Once loaded, parse your source data as normal.</p> <h3 id=\"quick-example\"><a href=\"#quick-example\" class=\"header-anchor\">#</a> Quick example</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Hoover <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/hoover'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Hoover<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h2 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h2> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax remains available, with the following\nextensions:</p> <h3 id=\"hoover-value-keywords\"><a href=\"#hoover-value-keywords\" class=\"header-anchor\">#</a> Hoover value keywords</h3> <ul><li><code>undefined</code></li> <li><code>NaN</code></li> <li><code>Infinity</code> (optional prefix <code>+</code> or <code>-</code>)</li></ul> <h3 id=\"regular-expressions\"><a href=\"#regular-expressions\" class=\"header-anchor\">#</a> Regular Expressions</h3> <p>Characters between '/' and '/' are converted to a <code>RegExp</code> object.</p> <ul><li><code>/a/</code> // === new RegExp('a')</li> <li><code>/\\//g</code> // === new RegExp('\\/','g')</li></ul> <h3 id=\"iso-dates\"><a href=\"#iso-dates\" class=\"header-anchor\">#</a> ISO Dates</h3> <p>Unquoted text that matches the ISO date format is converted into a <code>Date</code> object.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \n</code></pre></div><h2 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h2> <p>This plugin has no options.</p> <h2 id=\"implementation\"><a href=\"#implementation\" class=\"header-anchor\">#</a> Implementation</h2> <p>The source code for this plugin is\nhere: <a href=\"github.com/jsonicjs/jsonic/blob/master/plugin/hoover.ts\"><code>plugin/hoover.ts</code></a>.</p> <p>TODO - discuss</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/plugin/csv.html\" class=\"prev\">\n        csv\n      </a></span> <span class=\"next\"><a href=\"/plugin/json.html\">\n        json\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/19.57ad231b.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/plugin/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Plugins | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/20.58c8b075.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" class=\"sidebar-link\">native</a></li><li><a href=\"/plugin/csv.html\" class=\"sidebar-link\">csv</a></li><li><a href=\"/plugin/hoover.html\" class=\"sidebar-link\">hoover</a></li><li><a href=\"/plugin/json.html\" class=\"sidebar-link\">json</a></li><li><a href=\"/plugin/dynamic.html\" class=\"sidebar-link\">dynamic</a></li><li><a href=\"/plugin/multifile.html\" class=\"sidebar-link\">multifile</a></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"plugins\"><a href=\"#plugins\" class=\"header-anchor\">#</a> Plugins</h1> <p>There is a standard <span class=\"jsn-name-self\">Jsonic</span> <a href=\"/ref/syntax\">syntax</a>, which you\nshould probably just use if all you want is easy-going JSON.</p> <p>But <span class=\"jsn-name-self\">Jsonic</span> itself is meant to be an extensible parser for\nJSON-like languages, so you feel the need for a little more power,\nyou've come to the right place!</p> <p>A small set of plugins are built into the standard package. You load\nthem separately (this keeps the core small for web app use cases).</p> <p>There is a wider set of standard plugins under the <a href=\"https://www.npmjs.com/org/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\">@jsonic\norganisation<span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a>.</p> <p>These plugins also serve as examples of how to write your own\nextensions to JSON. Instead of starting from\nscratch, <a href=\"/guide/improve-plugin-tutorial\">copy and improve</a>!</p> <p><a name=\"builtin-plugins\"></a></p> <h2 id=\"built-in-plugins\"><a href=\"#built-in-plugins\" class=\"header-anchor\">#</a> Built-in plugins</h2> <ul><li><a href=\"#native\">native</a>: Parse native JavaScript values such as <code>undefined</code>, <code>NaN</code>, etc.</li> <li><a href=\"#csv\">csv</a>: Parse CSV data that can contain embedded JSON.</li> <li><a href=\"#hoover\">hoover</a>: TODO.</li> <li><a href=\"#json\">json</a>: TODO.</li> <li><a href=\"#dynamic\">dynamic</a>: TODO.</li> <li><a href=\"#multifile\">multifile</a>: TODO.</li> <li><a href=\"#legacy-stringify\">legacy-stringify</a>: TODO.</li></ul> <h3 id=\"native\"><a href=\"#native\" class=\"header-anchor\">#</a> <code>native</code></h3> <p><a href=\"/plugin/native\">details</a> →</p> <p>Parse native JavaScript values such as <code>undefined</code>, <code>NaN</code>, <code>Infinity</code>,\nliteral regular expressions, and ISO-formatted dates.</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> Native <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/native'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Native<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h3 id=\"csv\"><a href=\"#csv\" class=\"header-anchor\">#</a> <code>csv</code></h3> <p><a href=\"/plugin/csv\">details</a> →</p> <p>Parse CSV data that can contain embedded JSON, and also supports\ncomments and other <span class=\"jsn-name-self\">Jsonic</span> sugar.</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> Csv <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/csv'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Csv<span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// === [{&quot;a&quot;:1, &quot;b&quot;:2}, {&quot;a&quot;:3,&quot;b&quot;:4}]</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\na,b      // first line is headers\n1,2\n3,4\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span> \n</code></pre></div><p><a name=\"standard-plugins\"></a></p> <h3 id=\"hoover\"><a href=\"#hoover\" class=\"header-anchor\">#</a> <code>hoover</code></h3> <h3 id=\"json\"><a href=\"#json\" class=\"header-anchor\">#</a> <code>json</code></h3> <h3 id=\"dynamic\"><a href=\"#dynamic\" class=\"header-anchor\">#</a> <code>dynamic</code></h3> <h3 id=\"multifile\"><a href=\"#multifile\" class=\"header-anchor\">#</a> <code>multifile</code></h3> <h3 id=\"legacy-stringify\"><a href=\"#legacy-stringify\" class=\"header-anchor\">#</a> <code>legacy-stringify</code></h3> <h2 id=\"standard-plugins\"><a href=\"#standard-plugins\" class=\"header-anchor\">#</a> Standard plugins</h2> <p><a name=\"community-plugins\"></a></p> <h2 id=\"community-plugins\"><a href=\"#community-plugins\" class=\"header-anchor\">#</a> Community plugins</h2></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/20.58c8b075.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/plugin/json.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>json | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/21.0c46ffa9.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/json.html#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" class=\"sidebar-link\">native</a></li><li><a href=\"/plugin/csv.html\" class=\"sidebar-link\">csv</a></li><li><a href=\"/plugin/hoover.html\" class=\"sidebar-link\">hoover</a></li><li><a href=\"/plugin/json.html\" aria-current=\"page\" class=\"active sidebar-link\">json</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/plugin/json.html#usage\" class=\"sidebar-link\">Usage</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/json.html#syntax\" class=\"sidebar-link\">Syntax</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/json.html#options\" class=\"sidebar-link\">Options</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/json.html#implementation\" class=\"sidebar-link\">Implementation</a></li></ul></li><li><a href=\"/plugin/dynamic.html\" class=\"sidebar-link\">dynamic</a></li><li><a href=\"/plugin/multifile.html\" class=\"sidebar-link\">multifile</a></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/json.html#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/json.html#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"json\"><a href=\"#json\" class=\"header-anchor\">#</a> <code>json</code></h1> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax only supports standard JSON value\nkeywords: <code>true</code>, <code>false</code>, <code>null</code>.</p> <p>Wouldn't it be nice to have <code>undefined</code>, <code>Infinity</code>, <code>NaN</code> as well?\nAnd while we're at it, how about recognizing literal <code>/regexp/</code>\nsyntax, and ISO (<code>2021-03-19T17:15:51.845Z</code>) dates?</p> <p>The <code>json</code> plugin will do this for you!</p> <p>This is a good plugin to copy and extend if you just want to add some\n&quot;magic&quot; values to your source data.</p> <h2 id=\"usage\"><a href=\"#usage\" class=\"header-anchor\">#</a> Usage</h2> <p>To use the plugin, <code>require</code> or <code>import</code> the module path: <code>jsonic/plugin/json</code>:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// Node.js</span>\n<span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Json <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/json'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Web</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Json <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic/plugin/json'</span>\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(The convention for loading modules that are Jsonic plugins is to deconstruct: <code>{ PluginName }</code> )</small></p> <p>Once loaded, parse your source data as normal.</p> <h3 id=\"quick-example\"><a href=\"#quick-example\" class=\"header-anchor\">#</a> Quick example</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Json <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/json'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Json<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h2 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h2> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax remains available, with the following\nextensions:</p> <h3 id=\"json-value-keywords\"><a href=\"#json-value-keywords\" class=\"header-anchor\">#</a> Json value keywords</h3> <ul><li><code>undefined</code></li> <li><code>NaN</code></li> <li><code>Infinity</code> (optional prefix <code>+</code> or <code>-</code>)</li></ul> <h3 id=\"regular-expressions\"><a href=\"#regular-expressions\" class=\"header-anchor\">#</a> Regular Expressions</h3> <p>Characters between '/' and '/' are converted to a <code>RegExp</code> object.</p> <ul><li><code>/a/</code> // === new RegExp('a')</li> <li><code>/\\//g</code> // === new RegExp('\\/','g')</li></ul> <h3 id=\"iso-dates\"><a href=\"#iso-dates\" class=\"header-anchor\">#</a> ISO Dates</h3> <p>Unquoted text that matches the ISO date format is converted into a <code>Date</code> object.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \n</code></pre></div><h2 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h2> <p>This plugin has no options.</p> <h2 id=\"implementation\"><a href=\"#implementation\" class=\"header-anchor\">#</a> Implementation</h2> <p>The source code for this plugin is\nhere: <a href=\"github.com/jsonicjs/jsonic/blob/master/plugin/json.ts\"><code>plugin/json.ts</code></a>.</p> <p>TODO - discuss</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/plugin/hoover.html\" class=\"prev\">\n        hoover\n      </a></span> <span class=\"next\"><a href=\"/plugin/dynamic.html\">\n        dynamic\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/21.0c46ffa9.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/plugin/multifile.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>multifile | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/23.18c270f0.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/multifile.html#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" class=\"sidebar-link\">native</a></li><li><a href=\"/plugin/csv.html\" class=\"sidebar-link\">csv</a></li><li><a href=\"/plugin/hoover.html\" class=\"sidebar-link\">hoover</a></li><li><a href=\"/plugin/json.html\" class=\"sidebar-link\">json</a></li><li><a href=\"/plugin/dynamic.html\" class=\"sidebar-link\">dynamic</a></li><li><a href=\"/plugin/multifile.html\" aria-current=\"page\" class=\"active sidebar-link\">multifile</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/plugin/multifile.html#usage\" class=\"sidebar-link\">Usage</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/multifile.html#syntax\" class=\"sidebar-link\">Syntax</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/multifile.html#options\" class=\"sidebar-link\">Options</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/multifile.html#implementation\" class=\"sidebar-link\">Implementation</a></li></ul></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/multifile.html#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/multifile.html#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"multifile\"><a href=\"#multifile\" class=\"header-anchor\">#</a> <code>multifile</code></h1> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax only supports standard JSON value\nkeywords: <code>true</code>, <code>false</code>, <code>null</code>.</p> <p>Wouldn't it be nice to have <code>undefined</code>, <code>Infinity</code>, <code>NaN</code> as well?\nAnd while we're at it, how about recognizing literal <code>/regexp/</code>\nsyntax, and ISO (<code>2021-03-19T17:15:51.845Z</code>) dates?</p> <p>The <code>multifile</code> plugin will do this for you!</p> <p>This is a good plugin to copy and extend if you just want to add some\n&quot;magic&quot; values to your source data.</p> <h2 id=\"usage\"><a href=\"#usage\" class=\"header-anchor\">#</a> Usage</h2> <p>To use the plugin, <code>require</code> or <code>import</code> the module path: <code>jsonic/plugin/multifile</code>:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// Node.js</span>\n<span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Multifile <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/multifile'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Web</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Multifile <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic/plugin/multifile'</span>\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(The convention for loading modules that are Jsonic plugins is to deconstruct: <code>{ PluginName }</code> )</small></p> <p>Once loaded, parse your source data as normal.</p> <h3 id=\"quick-example\"><a href=\"#quick-example\" class=\"header-anchor\">#</a> Quick example</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Multifile <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/multifile'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Multifile<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h2 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h2> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax remains available, with the following\nextensions:</p> <h3 id=\"multifile-value-keywords\"><a href=\"#multifile-value-keywords\" class=\"header-anchor\">#</a> Multifile value keywords</h3> <ul><li><code>undefined</code></li> <li><code>NaN</code></li> <li><code>Infinity</code> (optional prefix <code>+</code> or <code>-</code>)</li></ul> <h3 id=\"regular-expressions\"><a href=\"#regular-expressions\" class=\"header-anchor\">#</a> Regular Expressions</h3> <p>Characters between '/' and '/' are converted to a <code>RegExp</code> object.</p> <ul><li><code>/a/</code> // === new RegExp('a')</li> <li><code>/\\//g</code> // === new RegExp('\\/','g')</li></ul> <h3 id=\"iso-dates\"><a href=\"#iso-dates\" class=\"header-anchor\">#</a> ISO Dates</h3> <p>Unquoted text that matches the ISO date format is converted into a <code>Date</code> object.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \n</code></pre></div><h2 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h2> <p>This plugin has no options.</p> <h2 id=\"implementation\"><a href=\"#implementation\" class=\"header-anchor\">#</a> Implementation</h2> <p>The source code for this plugin is\nhere: <a href=\"github.com/jsonicjs/jsonic/blob/master/plugin/multifile.ts\"><code>plugin/multifile.ts</code></a>.</p> <p>TODO - discuss</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/plugin/dynamic.html\" class=\"prev\">\n        dynamic\n      </a></span> <span class=\"next\"><a href=\"/plugin/legacy-stringify.html\">\n        legacy-stringify\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/23.18c270f0.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/plugin/native.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>native | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/24.5895d76a.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link router-link-active\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/native.html#builtin-plugins\" class=\"sidebar-heading clickable open\"><span>Builtin Plugins</span> <span class=\"arrow down\"></span></a> <ul class=\"sidebar-links sidebar-group-items\"><li><a href=\"/plugin/native.html\" aria-current=\"page\" class=\"active sidebar-link\">native</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/plugin/native.html#usage\" class=\"sidebar-link\">Usage</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/native.html#syntax\" class=\"sidebar-link\">Syntax</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/native.html#options\" class=\"sidebar-link\">Options</a></li><li class=\"sidebar-sub-header\"><a href=\"/plugin/native.html#implementation\" class=\"sidebar-link\">Implementation</a></li></ul></li><li><a href=\"/plugin/csv.html\" class=\"sidebar-link\">csv</a></li><li><a href=\"/plugin/hoover.html\" class=\"sidebar-link\">hoover</a></li><li><a href=\"/plugin/json.html\" class=\"sidebar-link\">json</a></li><li><a href=\"/plugin/dynamic.html\" class=\"sidebar-link\">dynamic</a></li><li><a href=\"/plugin/multifile.html\" class=\"sidebar-link\">multifile</a></li><li><a href=\"/plugin/legacy-stringify.html\" class=\"sidebar-link\">legacy-stringify</a></li></ul></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/native.html#standard-plugins\" class=\"sidebar-heading clickable\"><span>Standard Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li><li><section class=\"sidebar-group collapsable depth-0\"><a href=\"/plugin/native.html#community-plugins\" class=\"sidebar-heading clickable\"><span>Community Plugins</span> <span class=\"arrow right\"></span></a> <!----></section></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"native\"><a href=\"#native\" class=\"header-anchor\">#</a> <code>native</code></h1> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax only supports standard JSON value\nkeywords: <code>true</code>, <code>false</code>, <code>null</code>.</p> <p>Wouldn't it be nice to have <code>undefined</code>, <code>Infinity</code>, <code>NaN</code> as well?\nAnd while we're at it, how about recognizing literal <code>/regexp/</code>\nsyntax, and ISO (<code>2021-03-19T17:15:51.845Z</code>) dates?</p> <p>The <code>native</code> plugin will do this for you!</p> <p>This is a good plugin to copy and extend if you just want to add some\n&quot;magic&quot; values to your source data.</p> <h2 id=\"usage\"><a href=\"#usage\" class=\"header-anchor\">#</a> Usage</h2> <p>To use the plugin, <code>require</code> or <code>import</code> the module path: <code>jsonic/plugin/native</code>:</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// Node.js</span>\n<span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Native <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/native'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Web</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Native <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'jsonic/plugin/native'</span>\n</code></pre></div><p style=\"color:#888;text-align:right;margin-top:-20px;\"><small style=\"font-size:10px;\">(The convention for loading modules that are Jsonic plugins is to deconstruct: <code>{ PluginName }</code> )</small></p> <p>Once loaded, parse your source data as normal.</p> <h3 id=\"quick-example\"><a href=\"#quick-example\" class=\"header-anchor\">#</a> Quick example</h3> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> <span class=\"token punctuation\">{</span> Native <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'jsonic/plugin/native'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// or import</span>\n<span class=\"token keyword\">let</span> extra <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Native<span class=\"token punctuation\">)</span>\n<span class=\"token function\">extra</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:NaN'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {&quot;a&quot;: NaN}</span>\n</code></pre></div><h2 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h2> <p>The standard <span class=\"jsn-name-self\">Jsonic</span> syntax remains available, with the following\nextensions:</p> <h3 id=\"native-value-keywords\"><a href=\"#native-value-keywords\" class=\"header-anchor\">#</a> Native value keywords</h3> <ul><li><code>undefined</code></li> <li><code>NaN</code></li> <li><code>Infinity</code> (optional prefix <code>+</code> or <code>-</code>)</li></ul> <h3 id=\"regular-expressions\"><a href=\"#regular-expressions\" class=\"header-anchor\">#</a> Regular Expressions</h3> <p>Characters between '/' and '/' are converted to a <code>RegExp</code> object.</p> <ul><li><code>/a/</code> // === new RegExp('a')</li> <li><code>/\\//g</code> // === new RegExp('\\/','g')</li></ul> <h3 id=\"iso-dates\"><a href=\"#iso-dates\" class=\"header-anchor\">#</a> ISO Dates</h3> <p>Unquoted text that matches the ISO date format is converted into a <code>Date</code> object.</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>when: 2021-03-19T17:15:51.845Z // == new Date('2021-03-19T17:15:51.845Z') \n</code></pre></div><h2 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h2> <p>This plugin has no options.</p> <h2 id=\"implementation\"><a href=\"#implementation\" class=\"header-anchor\">#</a> Implementation</h2> <p>The source code for this plugin is\nhere: <a href=\"github.com/jsonicjs/jsonic/blob/master/plugin/native.ts\"><code>plugin/native.ts</code></a>.</p> <p>TODO - discuss</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><!----> <span class=\"next\"><a href=\"/plugin/csv.html\">\n        csv\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/24.5895d76a.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/railroad-diagrams.css",
    "content": "svg.railroad-diagram {\n    background-color: hsl(30,20%,95%);\n}\nsvg.railroad-diagram path {\n    stroke-width: 3;\n    stroke: black;\n    fill: rgba(0,0,0,0);\n}\nsvg.railroad-diagram text {\n    font: bold 14px monospace;\n    text-anchor: middle;\n    white-space: pre;\n}\nsvg.railroad-diagram text.diagram-text {\n    font-size: 12px;\n}\nsvg.railroad-diagram text.diagram-arrow {\n    font-size: 16px;\n}\nsvg.railroad-diagram text.label {\n    text-anchor: start;\n}\nsvg.railroad-diagram text.comment {\n    font: italic 12px monospace;\n}\nsvg.railroad-diagram g.non-terminal text {\n    /*font-style: italic;*/\n}\nsvg.railroad-diagram rect {\n    stroke-width: 3;\n    stroke: black;\n    fill: hsl(120,100%,90%);\n}\nsvg.railroad-diagram rect.group-box {\n    stroke: gray;\n    stroke-dasharray: 10 5;\n    fill: none;\n}\nsvg.railroad-diagram path.diagram-text {\n    stroke-width: 3;\n    stroke: black;\n    fill: white;\n    cursor: help;\n}\nsvg.railroad-diagram g.diagram-text:hover path.diagram-text {\n    fill: #eee;\n}\n"
  },
  {
    "path": "docs/ref/api.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>API | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/25.a347f1d6.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/ref/api.html\" aria-current=\"page\" class=\"active sidebar-link\">API</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/api.html#methods\" class=\"sidebar-link\">Methods</a></li></ul></li><li><a href=\"/ref/options.html\" class=\"sidebar-link\">Options</a></li><li><a href=\"/ref/syntax.html\" class=\"sidebar-link\">Syntax</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"api\"><a href=\"#api\" class=\"header-anchor\">#</a> API</h1> <p>The <span class=\"jsn-name-self\">Jsonic</span> API is deliberately kept as small as possible.</p> <div class=\"custom-block tip\"><p class=\"custom-block-title\">TIP</p> <p>If all you want to do is parse easy-going JSON, you don't need this API!\nJust call <code>Jsonic(...your-json-source...)</code> and use the return value.</p> <p>This API is for customizing the JSON parser, so if that is your game, read on...</p></div> <p>The default import <code>Jsonic</code> is intended as utility function and to be\nused as-is. For customization, and to access the API methods, create a\nnew <span class=\"jsn-name-self\">Jsonic</span> instance with <code>Jsonic.make()</code>.</p> <p>Some plugins may decorate the main <code>Jsonic</code> object with additional methods.</p> <ul><li>     🍓 🍐 <a href=\"#jsonic-just-parse-already\"><code>Jsonic</code></a></li> <li>          🍐 <a href=\"#id-unique-instance-identifier\"><code>id</code></a></li> <li>     🍓      <a href=\"#tostring-string-description-of-the-jsonic-instance\"><code>toString</code></a></li> <li>     🍓      <a href=\"#make-create-a-new-customizable-jsonic-instance\"><code>make</code></a></li> <li>🍒 🍓 🍐 <a href=\"#options-get-and-set-options-for-a-jsonic-instance\"><code>options</code></a></li> <li>🍒 🍓      <a href=\"#use-register-a-plugin\"><code>use</code></a></li> <li>🍒 🍓      <a href=\"#rule-define-or-modify-a-parser-rule\"><code>rule</code></a></li> <li>🍒 🍓      <a href=\"#lex-define-a-lex-matcher\"><code>lex</code></a></li> <li>🍒 🍓 🍐 <a href=\"#token-resolve-a-token-by-name-or-index\"><code>token</code></a></li></ul> <small>\n(🍒 available after <code>Jsonic.make()</code>, 🍓 method or function, 🍐 property or set of properties)\n</small> <p><br><br></p> <h2 id=\"methods\"><a href=\"#methods\" class=\"header-anchor\">#</a> Methods</h2> <h3 id=\"jsonic-just-parse-already\"><a href=\"#jsonic-just-parse-already\" class=\"header-anchor\">#</a> 🍓 🍐 <code>Jsonic</code>: Just parse already!</h3> <p><code>Jsonic(source: string, meta?: object): any</code></p> <p><em>Returns</em>: <code>any</code> Object (or value) containing the parsed JSON data.</p> <ul><li><code>source: string</code> <em><small>required</small></em> : The JSON source text to parse.</li> <li><code>meta?: object</code> <em><small>optional</small></em> : Provide meta data for this parse.</li></ul> <p>This is the top level utility function that parses JSON source text\nusing the standard syntax extensions of <span class=\"jsn-name-self\">Jsonic</span>. It cannot be\ncustomized (call <code>Jsonic.make()</code> to do that), and always behaves the\nsame way.</p> <blockquote><ul><li>🍓 Function: <code>Jsonic(...)</code></li> <li>🍐 Property set: <code>jsonic.id</code></li></ul></blockquote> <h4 id=\"example\"><a href=\"#example\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> earth <span class=\"token operator\">=</span> <span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'name: Terra, moons: [{name: Luna}]'</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>The <code>earth</code> variable now contains the following data:</p> <div class=\"language-json extra-class\"><pre class=\"language-json\"><code><span class=\"token comment\">// earth -&gt;</span>\n<span class=\"token punctuation\">{</span>\n  <span class=\"token property\">&quot;name&quot;</span><span class=\"token operator\">:</span> <span class=\"token string\">&quot;Terra&quot;</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">&quot;moons&quot;</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">&quot;name&quot;</span><span class=\"token operator\">:</span> <span class=\"token string\">&quot;Luna&quot;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span>\n</code></pre></div><h4 id=\"example-using-the-meta-parameter\"><a href=\"#example-using-the-meta-parameter\" class=\"header-anchor\">#</a> ✨ Example: using the <code>meta</code> parameter</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> one <span class=\"token operator\">=</span> <span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'1'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token literal-property property\">log</span><span class=\"token operator\">:</span><span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// one === 1</span>\n</code></pre></div><p>The <code>meta</code> value of <code>{log:-1}</code> prints a debug log of the lexing and\nparsing process to <code>STDOUT</code>. Very useful when you are writing a\nplugin! See the plugin section for more details.</p> <div class=\"language-sh extra-class\"><pre class=\"language-sh\"><code>lex\t<span class=\"token comment\">#NR\t&quot;1&quot;\t1\t0:1\t@LTP</span>\nlex\t<span class=\"token comment\">#ZZ\t\t1\t0:1\t@LTP</span>\nrule\tval/1\t<span class=\"token function\">open</span>\t<span class=\"token number\">0</span>\t<span class=\"token number\">0</span>\t<span class=\"token punctuation\">[</span><span class=\"token comment\">#NR #ZZ]\t[&quot;1&quot; ]</span>\nparse\tval/1\t<span class=\"token function\">open</span>\t<span class=\"token assign-left variable\">alt</span><span class=\"token operator\">=</span><span class=\"token number\">10</span>\t<span class=\"token punctuation\">[</span><span class=\"token comment\">#NR]\t0\tp=\tr=\tb=\t#NR\t[&quot;1&quot;]\tc:\tn:</span>\nlex\t<span class=\"token comment\">#ZZ\t\t1\t0:1\t@LTP</span>\n<span class=\"token function\">node</span>\tval/1\t<span class=\"token function\">open</span>\t<span class=\"token assign-left variable\">w</span><span class=\"token operator\">=</span>Z\t\nstack\t<span class=\"token number\">0</span>\t\nrule\tval/1\tclose\t<span class=\"token number\">0</span>\t<span class=\"token number\">1</span>\t<span class=\"token punctuation\">[</span><span class=\"token comment\">#ZZ #ZZ]\t[ ]</span>\nparse\tval/1\tclose\t<span class=\"token assign-left variable\">alt</span><span class=\"token operator\">=</span><span class=\"token number\">2</span>\t<span class=\"token punctuation\">[</span><span class=\"token comment\">#AA]\t1\tp=\tr=\tb=1\t#ZZ\t[null]\tc:\tn:</span>\n<span class=\"token function\">node</span>\tval/1\tclose\t<span class=\"token assign-left variable\">w</span><span class=\"token operator\">=</span>O\t<span class=\"token number\">1</span>\nstack\t<span class=\"token number\">0</span>\n</code></pre></div><p>Apart from <code>log</code>, the meta object contains plugin-specific\nparameters. See your friendly neighbourhood plugin documentation for\nmore.</p> <p><br><br></p> <hr> <h3 id=\"id-unique-instance-identifier\"><a href=\"#id-unique-instance-identifier\" class=\"header-anchor\">#</a> 🍐 <code>id</code>: Unique instance identifier</h3> <p><code>.id: string</code></p> <p>It's useful to be able to identify unique instances when you're debugging.</p> <p>Use the <code>tag</code> option to set a custom tag for the instance.</p> <blockquote><ul><li>🍐 Property: <code>jsonic.id</code></li></ul></blockquote> <h4 id=\"example-2\"><a href=\"#example-2\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// format: Jsonic/&lt;Date.now()&gt;/&lt;Math.random()[2:8]&gt;/&lt;options.tag&gt;</span>\nJsonic<span class=\"token punctuation\">.</span>id <span class=\"token comment\">// === 'Jsonic/1614085850042/022636/-'</span>\nJsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">tag</span><span class=\"token operator\">:</span><span class=\"token string\">'foo'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>id <span class=\"token comment\">// === 'Jsonic/1614085851902/375149/foo'</span>\n</code></pre></div><p><br><br></p> <hr> <h3 id=\"tostring-string-description-of-the-jsonic-instance\"><a href=\"#tostring-string-description-of-the-jsonic-instance\" class=\"header-anchor\">#</a> 🍓 <code>toString</code>: String description of the Jsonic instance</h3> <p><code>.toString(): string</code></p> <p>Returns the value of the <code>.id</code> property as the string description of\nthe instance.</p> <blockquote><ul><li>🍓 Method: <code>jsonic.toString()</code></li></ul></blockquote> <h4 id=\"example-3\"><a href=\"#example-3\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token comment\">// format: Jsonic/&lt;Date.now()&gt;/&lt;Math.random()[2:8]&gt;/&lt;options.tag&gt;</span>\n<span class=\"token string\">''</span><span class=\"token operator\">+</span>Jsonic <span class=\"token comment\">// === 'Jsonic/1614085850042/022636/-'</span>\n<span class=\"token string\">''</span><span class=\"token operator\">+</span><span class=\"token punctuation\">(</span>Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">tag</span><span class=\"token operator\">:</span><span class=\"token string\">'foo'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === 'Jsonic/1614085851902/375149/foo'</span>\n</code></pre></div><p><br><br></p> <hr> <h3 id=\"make-create-a-new-customizable-jsonic-instance\"><a href=\"#make-create-a-new-customizable-jsonic-instance\" class=\"header-anchor\">#</a> 🍓 <code>make</code>: Create a new customizable Jsonic instance.</h3> <p><code>.make(options?: object): Jsonic</code></p> <p><em>Returns</em>: <code>Jsonic</code> A new Jsonic instance that can be modified.</p> <ul><li><code>options?: object</code> <em><small>optional</small></em> : Partial options tree.</li></ul> <p>The returned function can be used in the same way as the top level\n<code>Jsonic</code> function. It also exposes the rest of the API (such as\n<code>options</code>) so you can customize the parser.</p> <blockquote><ul><li>🍓 Method: <code>jsonic.make(...)</code></li></ul></blockquote> <h4 id=\"example-4\"><a href=\"#example-4\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> array_of_numbers <span class=\"token operator\">=</span> <span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'1,2,3'</span><span class=\"token punctuation\">)</span> \n<span class=\"token comment\">// array_of_numbers === [1, 2, 3]</span>\n\n<span class=\"token keyword\">let</span> no_numbers_please <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">number</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token literal-property property\">lex</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">let</span> array_of_strings <span class=\"token operator\">=</span> <span class=\"token function\">no_numbers_please</span><span class=\"token punctuation\">(</span><span class=\"token string\">'1,2,3'</span><span class=\"token punctuation\">)</span> \n<span class=\"token comment\">// array_of_strings === ['1', '2', '3']</span>\n</code></pre></div><p>You must call <code>make</code> to customize <span class=\"jsn-name-self\">Jsonic</span>. This protects the\n<code>Jsonic</code> import which is a shared global object.</p> <p>Calling <code>make</code> again on the new <code>Jsonic</code> instance will generate\nanother new instance that inherits the configuration of its parent and\ncan itself be independently customized. Which is what you want.</p> <h4 id=\"example-child-instances-inherit-from-parent-instances\"><a href=\"#example-child-instances-inherit-from-parent-instances\" class=\"header-anchor\">#</a> ✨ Example: child instances inherit from parent instances</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> no_numbers_please <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">number</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token literal-property property\">lex</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token function\">no_numbers_please</span><span class=\"token punctuation\">(</span><span class=\"token string\">'1,2,3'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === ['1', '2', '3'] as before</span>\n\n<span class=\"token keyword\">let</span> pipe_separated <span class=\"token operator\">=</span> no_numbers_please<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">token</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token string-property property\">'#CA'</span><span class=\"token operator\">:</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">c</span><span class=\"token operator\">:</span><span class=\"token string\">'|'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token function\">pipe_separated</span><span class=\"token punctuation\">(</span><span class=\"token string\">'1|2|3'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === ['1', '2', '3'], but:</span>\n<span class=\"token function\">pipe_separated</span><span class=\"token punctuation\">(</span><span class=\"token string\">'1,2,3'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === '1,2,3' !!!</span>\n</code></pre></div><p>To understand how the <code>token</code> option works, and all the other\noptions, see the <a href=\"/ref/options/\">Options</a> section.</p> <p><br><br></p> <hr> <h3 id=\"options-get-and-set-options-for-a-jsonic-instance\"><a href=\"#options-get-and-set-options-for-a-jsonic-instance\" class=\"header-anchor\">#</a> 🍓 🍐 <code>options</code>: Get and set options for a Jsonic instance.</h3> <p><code>.options(options: object): object</code></p> <p><em>Returns</em>: <code>object</code> merged object containing the full option tree</p> <ul><li><code>options: object</code> <em><small>required</small></em> : Partial options tree.</li></ul> <blockquote><ul><li>🍒 Only available on: <code>jsonic = Jsonic.make()</code></li> <li>🍓 Method: <code>jsonic.options(...)</code></li> <li>🍐 Property set of option tree: <code>jsonic.options.number.lex</code></li></ul></blockquote> <h4 id=\"example-5\"><a href=\"#example-5\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\njsonic<span class=\"token punctuation\">.</span><span class=\"token function\">options</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>comment<span class=\"token punctuation\">.</span>lex <span class=\"token comment\">// === true</span>\njsonic<span class=\"token punctuation\">.</span>options<span class=\"token punctuation\">.</span>comment<span class=\"token punctuation\">.</span>lex <span class=\"token comment\">// === true - as a convenience</span>\n\n<span class=\"token keyword\">let</span> no_comment <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\nno_comment<span class=\"token punctuation\">.</span><span class=\"token function\">options</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">comment</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token literal-property property\">lex</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Returns {&quot;a&quot;: 1, &quot;#b&quot;: 2}</span>\n<span class=\"token function\">no_comment</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n  a: 1\n  #b: 2\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Whereas this returns only {&quot;a&quot;: 1} as # starts a one line comment</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n  a: 1\n  #b: 2\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span>\n</code></pre></div><p><br><br></p> <hr> <h3 id=\"use-register-a-plugin\"><a href=\"#use-register-a-plugin\" class=\"header-anchor\">#</a> 🍒 🍓 <code>use</code>: Register a plugin.</h3> <p><code>.use(plugin: function, plugin_options?: object): Jsonic</code></p> <p><em>Returns</em>: Jsonic instance (this allows chaining)</p> <ul><li><code>plugin: function</code> <em><small>required</small></em> : Plugin definition function\n<ul><li><code>(jsonic: Jsonic) =&gt; Jsonic</code></li></ul></li> <li><code>plugin_options?: object</code> <em><small>optional</small></em> : Plugin-specific options</li></ul> <blockquote><ul><li>🍒 Only available on: <code>jsonic = Jsonic.make()</code></li> <li>🍓 Method: <code>jsonic.use(...)</code></li></ul></blockquote> <h4 id=\"example-6\"><a href=\"#example-6\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span> <span class=\"token function\">piper</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">jsonic</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">options</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">token</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token string-property property\">'#CA'</span><span class=\"token operator\">:</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">c</span><span class=\"token operator\">:</span><span class=\"token string\">'|'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token function\">jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a|b|c'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === ['a', 'b', 'c']</span>\n</code></pre></div><p>Plugins are defined by a function that takes the <code>Jsonic</code> instance as\na first parameter, and then changes the options and parsing rules of\nthat instance. For more, see the <a href=\"/guide/write-a-plugin\">plugin writing guide</a>.</p> <h4 id=\"example-plugin-options\"><a href=\"#example-plugin-options\" class=\"header-anchor\">#</a> ✨ Example: plugin options</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">function</span> <span class=\"token function\">sepper</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">jsonic</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> sep <span class=\"token operator\">=</span> jsonic<span class=\"token punctuation\">.</span>options<span class=\"token punctuation\">.</span>plugin<span class=\"token punctuation\">.</span>sepper<span class=\"token punctuation\">.</span>sep\n  jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">options</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">token</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token string-property property\">'#CA'</span><span class=\"token operator\">:</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">c</span><span class=\"token operator\">:</span>sep<span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>sepper<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token literal-property property\">sep</span><span class=\"token operator\">:</span><span class=\"token string\">';'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token function\">jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a;b;c'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === ['a', 'b', 'c']</span>\n</code></pre></div><p>Plugin options are added to the main options under the <code>plugin</code> key using the\nname of the plugin function as a sub-key. Thus <code>function sepper(...)</code> means that\n<code>jsonic.options.plugin.sepper</code> contains the plugin option.</p> <p>Notice that you can refer to options directly as properties of the\n<code>.options</code> method, as a convenience.</p> <h4 id=\"example-plugin-chaining\"><a href=\"#example-plugin-chaining\" class=\"header-anchor\">#</a> ✨ Example: plugin chaining</h4> <p>When defining a custom <span class=\"jsn-name-self\">Jsonic</span> instance, you'll probably be registering\nmultiple plugins. The <code>.use</code> method can be chained to make this easier.</p> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">function</span> <span class=\"token function\">foo</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">jsonic</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  jsonic<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">foo</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token number\">1</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">bar</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">jsonic</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  jsonic<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">bar</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">foo</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">2</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>foo<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>bar<span class=\"token punctuation\">)</span>\n<span class=\"token comment\">// jsonic.foo() === 1</span>\n<span class=\"token comment\">// jsonic.bar() === 2</span>\n</code></pre></div><p><br><br></p> <hr> <h3 id=\"rule-define-or-modify-a-parser-rule\"><a href=\"#rule-define-or-modify-a-parser-rule\" class=\"header-anchor\">#</a> 🍒 🍓 <code>rule</code>: Define or modify a parser rule.</h3> <p><code>.rule(name?: string, define?: function): RuleSpec</code></p> <p><em>Returns</em>: <code>RuleSpec</code> Rule specification</p> <ul><li><code>name?: string</code> <em><small>optional</small></em> : Rule name</li> <li><code>define?: function</code> <em><small>optional</small></em> : Rule definition function\n<ul><li><code>(rs: RuleSpec, rsm: RuleSpecMap) =&gt; RuleSpec</code></li></ul></li></ul> <p>The <code>.rule</code> method (and the <code>.lex</code> and <code>.token</code>) methods ar intended\nmostly for use inside plugin definitions. They allow you to modify the way that\n<span class=\"jsn-name-self\">Jsonic</span> works.</p> <p>The <code>.rule</code> method takes the name of a rule and if it exists, provides\nthe rule specification as first parameter to the rule definition\nfunction. If the rule does not exist, you can create a new rule\nspecification and return that to define a new rule.</p> <p>The details of rule definition are covered in the <a href=\"/plugins/\">Plugins</a>\nsection.</p> <blockquote><ul><li>🍒 Only available on: <code>jsonic = Jsonic.make()</code></li> <li>🍓 Method: <code>jsonic.rule(...)</code></li></ul></blockquote> <h4 id=\"example-7\"><a href=\"#example-7\" class=\"header-anchor\">#</a> 🍒 ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> concat <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// Get all the rules</span>\nObject<span class=\"token punctuation\">.</span><span class=\"token function\">keys</span><span class=\"token punctuation\">(</span>concat<span class=\"token punctuation\">.</span><span class=\"token function\">rule</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === ['val', 'map', 'list', 'pair', 'elem']</span>\n\n<span class=\"token comment\">// Get a rule by name</span>\n<span class=\"token keyword\">let</span> val_rule <span class=\"token operator\">=</span> jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">rule</span><span class=\"token punctuation\">(</span><span class=\"token string\">'val'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// val_rule.name === 'val'</span>\n\n<span class=\"token comment\">// Modify a rule </span>\n<span class=\"token keyword\">let</span> <span class=\"token constant\">ST</span> <span class=\"token operator\">=</span> concat<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span><span class=\"token constant\">ST</span>\nconcat<span class=\"token punctuation\">.</span><span class=\"token function\">rule</span><span class=\"token punctuation\">(</span><span class=\"token string\">'val'</span><span class=\"token punctuation\">,</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">rule</span><span class=\"token punctuation\">)</span><span class=\"token operator\">=&gt;</span><span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Concatentate strings (ST) instead of forming array elements</span>\n  rule<span class=\"token punctuation\">.</span>def<span class=\"token punctuation\">.</span>open<span class=\"token punctuation\">.</span><span class=\"token function\">unshift</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">s</span><span class=\"token operator\">:</span><span class=\"token punctuation\">[</span><span class=\"token constant\">ST</span><span class=\"token punctuation\">,</span><span class=\"token constant\">ST</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token function-variable function\">h</span><span class=\"token operator\">:</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">alt<span class=\"token punctuation\">,</span>rule<span class=\"token punctuation\">,</span>ctx</span><span class=\"token punctuation\">)</span><span class=\"token operator\">=&gt;</span><span class=\"token punctuation\">{</span>\n    rule<span class=\"token punctuation\">.</span>node <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>t0<span class=\"token punctuation\">.</span>val <span class=\"token operator\">+</span> ctx<span class=\"token punctuation\">.</span>t1<span class=\"token punctuation\">.</span>val\n    <span class=\"token comment\">// Disable default value handling</span>\n    rule<span class=\"token punctuation\">.</span>bc <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token function\">concat</span><span class=\"token punctuation\">(</span><span class=\"token string\">'&quot;a&quot; &quot;b&quot;'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === 'ab'</span>\n<span class=\"token function\">Jsonic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'&quot;a&quot; &quot;b&quot;'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === ['a', 'b']</span>\n\n<span class=\"token comment\">// Create a new rule (for a new token)</span>\nconcat<span class=\"token punctuation\">.</span><span class=\"token function\">options</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token literal-property property\">token</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token string-property property\">'#HH'</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token literal-property property\">c</span><span class=\"token operator\">:</span><span class=\"token string\">'%'</span><span class=\"token punctuation\">}</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">let</span> <span class=\"token constant\">HH</span> <span class=\"token operator\">=</span> concat<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span><span class=\"token constant\">HH</span>\nconcat<span class=\"token punctuation\">.</span><span class=\"token function\">rule</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hundred'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">=&gt;</span><span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">RuleSpec</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    <span class=\"token function-variable function\">after_open</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">rule</span><span class=\"token punctuation\">)</span><span class=\"token operator\">=&gt;</span><span class=\"token punctuation\">{</span>\n      <span class=\"token comment\">// % always becomes the value 100</span>\n      rule<span class=\"token punctuation\">.</span>node <span class=\"token operator\">=</span> <span class=\"token number\">100</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\nconcat<span class=\"token punctuation\">.</span><span class=\"token function\">rule</span><span class=\"token punctuation\">(</span><span class=\"token string\">'val'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">rulespec</span><span class=\"token punctuation\">)</span><span class=\"token operator\">=&gt;</span><span class=\"token punctuation\">{</span>\n  rulespec<span class=\"token punctuation\">.</span>def<span class=\"token punctuation\">.</span>open<span class=\"token punctuation\">.</span><span class=\"token function\">unshift</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">s</span><span class=\"token operator\">:</span><span class=\"token punctuation\">[</span><span class=\"token constant\">HH</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token literal-property property\">p</span><span class=\"token operator\">:</span><span class=\"token string\">'hundred'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token function\">concat</span><span class=\"token punctuation\">(</span><span class=\"token string\">'{x:1, y:%}'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {x:1, y:100}</span>\n</code></pre></div><p><br><br></p> <hr> <h3 id=\"lex-define-a-lex-matcher\"><a href=\"#lex-define-a-lex-matcher\" class=\"header-anchor\">#</a> 🍒 🍓 <code>lex</code>: Define a lex matcher.</h3> <p><code>.lex(state?: Tin, match?: function): LexMatcher[]</code></p> <p><em>Returns</em>: <code>LexMatcher[]</code> Ordered list of lex matchers for this lex state.</p> <ul><li><code>state?: Tin</code> <em><small>optional</small></em> : Token identifier number</li> <li><code>matcher?: function</code> <em><small>optional</small></em> : Lex matcher function\n<ul><li><code>(state: LexMatcherState) =&gt; LexMatcherResult</code></li></ul></li></ul> <p>The <code>.lex</code> method (like the <code>.rule</code> and <code>.token</code> methods) allows you\nto change the way that <span class=\"jsn-name-self\">Jsonic</span> works. The <code>.lex</code> method attaches\na matcher function to a given lex state. This matcher has the\nopportunity to examine the current source text position and generate a\ntoken, or pass lexing over to the standard machinery.</p> <p>The <span class=\"jsn-name-self\">Jsonic</span> is state based, although most of the normal lexing\nhappens in the top lex state (LTP).</p> <p>For more about lex matchers, see the <a href=\"/plugins/\">Plugins</a> section.</p> <blockquote><ul><li>🍒 Only available on: <code>jsonic = Jsonic.make()</code></li> <li>🍓 Method: <code>jsonic.lex(...)</code></li></ul></blockquote> <h4 id=\"example-8\"><a href=\"#example-8\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> tens <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">let</span> <span class=\"token constant\">VL</span> <span class=\"token operator\">=</span> tens<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span><span class=\"token constant\">VL</span>\n<span class=\"token keyword\">let</span> <span class=\"token constant\">LTP</span> <span class=\"token operator\">=</span> tens<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span><span class=\"token constant\">LTP</span>\n\n<span class=\"token comment\">// Match characters in the top lex state (LTP)</span>\ntens<span class=\"token punctuation\">.</span><span class=\"token function\">lex</span><span class=\"token punctuation\">(</span><span class=\"token constant\">LTP</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span> <span class=\"token function\">tens_matcher</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">state</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\n  <span class=\"token comment\">// % -&gt; 10, %% -&gt; 20, %%% -&gt; 30, etc.</span>\n  <span class=\"token keyword\">let</span> marks <span class=\"token operator\">=</span> state<span class=\"token punctuation\">.</span>src<span class=\"token punctuation\">.</span><span class=\"token function\">substring</span><span class=\"token punctuation\">(</span>state<span class=\"token punctuation\">.</span>sI<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">match</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">^%+</span><span class=\"token regex-delimiter\">/</span></span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">if</span><span class=\"token punctuation\">(</span>marks<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">let</span> len <span class=\"token operator\">=</span> marks<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>length\n    state<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span>tin <span class=\"token operator\">=</span> <span class=\"token constant\">VL</span>\n    state<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span>val <span class=\"token operator\">=</span> <span class=\"token number\">10</span> <span class=\"token operator\">*</span> len\n\n    <span class=\"token comment\">// Update lexer position and column</span>\n    <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token literal-property property\">sI</span><span class=\"token operator\">:</span> state<span class=\"token punctuation\">.</span>sI <span class=\"token operator\">+</span> len<span class=\"token punctuation\">,</span>\n      <span class=\"token literal-property property\">cI</span><span class=\"token operator\">:</span> state<span class=\"token punctuation\">.</span>cI <span class=\"token operator\">+</span> len\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token function\">tens</span><span class=\"token punctuation\">(</span><span class=\"token string\">'a:1,b:%%,c:[%%%%]'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === {a:1, b:20, c:[40]}</span>\n</code></pre></div><p><br><br></p> <hr> <h3 id=\"token-resolve-a-token-by-name-or-index\"><a href=\"#token-resolve-a-token-by-name-or-index\" class=\"header-anchor\">#</a> 🍒 🍓 🍐 <code>token</code>: Resolve a token by name or index.</h3> <p><code>.token(ref: Tin | string): string | Tin</code></p> <p><em>Returns</em>: <code>string | Tin</code> Token identifier or token name (opposite of <code>ref</code> type).</p> <ul><li><code>ref: Tin | string</code> <em><small>required</small></em> : Token identifier number or name</li></ul> <p>The <code>.token</code> method lets you get the unique token identification\nnumber (<code>Tin</code>) of a named token in the current <code>Jsonic</code> instance, or\nlookup the name of a token by its <code>Tin</code>.</p> <p>As lexer states must also be unique, they are generated as\npseudo-tokens using the same index of tokens. While child <code>Jsonic</code>\ninstances (generated with <code>.make</code>) will inherit the index of their\nparents, in general token identification is usable only for a specific\n<code>Jsonic</code> instance.</p> <blockquote><ul><li>🍒 Only available on: <code>jsonic = Jsonic.make()</code></li> <li>🍓 Method: <code>jsonic.token(...)</code></li> <li>🍐 Property set of token names and Tins: <code>jsonic.token.NR</code></li></ul></blockquote> <h4 id=\"example-9\"><a href=\"#example-9\" class=\"header-anchor\">#</a> ✨ Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\njsonic<span class=\"token punctuation\">.</span>token<span class=\"token punctuation\">.</span><span class=\"token constant\">ST</span> <span class=\"token comment\">// === 11, String token identification number</span>\njsonic<span class=\"token punctuation\">.</span><span class=\"token function\">token</span><span class=\"token punctuation\">(</span><span class=\"token number\">11</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === '#ST', String token name</span>\njsonic<span class=\"token punctuation\">.</span><span class=\"token function\">token</span><span class=\"token punctuation\">(</span><span class=\"token string\">'#ST'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// === 11, String token name</span>\n</code></pre></div></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><!----> <span class=\"next\"><a href=\"/ref/options.html\">\n        Options\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/25.a347f1d6.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/ref/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Reference | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/26.c7b8345d.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" aria-current=\"page\" class=\"nav-link router-link-exact-active router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/ref/api.html\" class=\"sidebar-link\">API</a></li><li><a href=\"/ref/options.html\" class=\"sidebar-link\">Options</a></li><li><a href=\"/ref/syntax.html\" class=\"sidebar-link\">Syntax</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"reference\"><a href=\"#reference\" class=\"header-anchor\">#</a> Reference</h1> <p>Lorem ipsum</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/26.c7b8345d.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/ref/options.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Options | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/27.4e6f90b7.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/ref/api.html\" class=\"sidebar-link\">API</a></li><li><a href=\"/ref/options.html\" aria-current=\"page\" class=\"active sidebar-link\">Options</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#tag\" class=\"sidebar-link\">tag</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#line\" class=\"sidebar-link\">line</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#line-lex\" class=\"sidebar-link\">line.lex</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#line-row\" class=\"sidebar-link\">line.row</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#line-sep\" class=\"sidebar-link\">line.sep</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#comment\" class=\"sidebar-link\">comment</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#comment-lex\" class=\"sidebar-link\">comment.lex</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#comment-balance\" class=\"sidebar-link\">comment.balance</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#comment-marker\" class=\"sidebar-link\">comment.marker</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#space\" class=\"sidebar-link\">space</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#space-lex\" class=\"sidebar-link\">space.lex</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number\" class=\"sidebar-link\">number</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number-lex\" class=\"sidebar-link\">number.lex</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number-hex\" class=\"sidebar-link\">number.hex</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number-oct\" class=\"sidebar-link\">number.oct</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number-bin\" class=\"sidebar-link\">number.bin</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number-digital\" class=\"sidebar-link\">number.digital</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#number-sep\" class=\"sidebar-link\">number.sep</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#block\" class=\"sidebar-link\">block</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#block-lex-true\" class=\"sidebar-link\">block.lex: true</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#block-marker\" class=\"sidebar-link\">block.marker</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#string\" class=\"sidebar-link\">string</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#string-lex\" class=\"sidebar-link\">string.lex</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#string-escape\" class=\"sidebar-link\">string.escape</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#string-multiline\" class=\"sidebar-link\">string.multiline</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#string-escapedouble\" class=\"sidebar-link\">string.escapedouble</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#text\" class=\"sidebar-link\">text</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#text-lex\" class=\"sidebar-link\">text.lex</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#map\" class=\"sidebar-link\">map</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#map-extend\" class=\"sidebar-link\">map.extend</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#value\" class=\"sidebar-link\">value</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#value-lex\" class=\"sidebar-link\">value.lex</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#value-src\" class=\"sidebar-link\">value.src:</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#value-src-2\" class=\"sidebar-link\">value.src:</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#plugin\" class=\"sidebar-link\">plugin</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#debug\" class=\"sidebar-link\">debug</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#debug-get-console\" class=\"sidebar-link\">debug.get_console</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#debug-maxlen\" class=\"sidebar-link\">debug.maxlen</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#debug-print\" class=\"sidebar-link\">debug.print</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#debug-print-config\" class=\"sidebar-link\">debug.print.config</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#error\" class=\"sidebar-link\">error</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#hint\" class=\"sidebar-link\">hint</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#token\" class=\"sidebar-link\">token</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#rule\" class=\"sidebar-link\">rule</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#rule-start\" class=\"sidebar-link\">rule.start</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#rule-finish\" class=\"sidebar-link\">rule.finish</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#rule-maxmul\" class=\"sidebar-link\">rule.maxmul</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#config\" class=\"sidebar-link\">config</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#config-modify\" class=\"sidebar-link\">config.modify</a></li></ul></li><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#parser\" class=\"sidebar-link\">parser</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/options.html#parser-start\" class=\"sidebar-link\">parser.start</a></li></ul></li></ul></li><li><a href=\"/ref/syntax.html\" class=\"sidebar-link\">Syntax</a></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"options\"><a href=\"#options\" class=\"header-anchor\">#</a> Options</h1> <h2 id=\"tag\"><a href=\"#tag\" class=\"header-anchor\">#</a> tag</h2> <ul><li>Type: <code>string</code></li> <li>Default <code>-</code></li></ul> <p>Suffix to append to the <code>Jsonic</code> instance identifier. Useful if you have\nto debug multiple instances with different options and plugins.</p> <h4 id=\"example\"><a href=\"#example\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token literal-property property\">tag</span><span class=\"token operator\">:</span><span class=\"token string\">'foo'</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\njsonic<span class=\"token punctuation\">.</span>id <span class=\"token comment\">// === 'Jsonic/1614085851902/375149/foo'</span>\n</code></pre></div><h2 id=\"line\"><a href=\"#line\" class=\"header-anchor\">#</a> line</h2> <p>Grouping of options for the lexing of lines.</p> <h3 id=\"line-lex\"><a href=\"#line-lex\" class=\"header-anchor\">#</a> line.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-2\"><a href=\"#example-2\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"line-row\"><a href=\"#line-row\" class=\"header-anchor\">#</a> line.row</h3> <ul><li>Type: <code>string</code></li> <li>Default <code>'\\n'</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-3\"><a href=\"#example-3\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"line-sep\"><a href=\"#line-sep\" class=\"header-anchor\">#</a> line.sep</h3> <ul><li>Type: <code>string</code> (partial RegExp)</li> <li>Default: <code>'\\r*\\n'</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-4\"><a href=\"#example-4\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"comment\"><a href=\"#comment\" class=\"header-anchor\">#</a> comment</h2> <h3 id=\"comment-lex\"><a href=\"#comment-lex\" class=\"header-anchor\">#</a> comment.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-5\"><a href=\"#example-5\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"comment-balance\"><a href=\"#comment-balance\" class=\"header-anchor\">#</a> comment.balance</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-6\"><a href=\"#example-6\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"comment-marker\"><a href=\"#comment-marker\" class=\"header-anchor\">#</a> comment.marker</h3> <ul><li>Type: <code>{[string]: string}</code></li> <li>Default:<code>{ '#': true, '//': true, '/*': '*/' }</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-7\"><a href=\"#example-7\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"space\"><a href=\"#space\" class=\"header-anchor\">#</a> space</h2> <h3 id=\"space-lex\"><a href=\"#space-lex\" class=\"header-anchor\">#</a> space.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-8\"><a href=\"#example-8\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"number\"><a href=\"#number\" class=\"header-anchor\">#</a> number</h2> <h3 id=\"number-lex\"><a href=\"#number-lex\" class=\"header-anchor\">#</a> number.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-9\"><a href=\"#example-9\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"number-hex\"><a href=\"#number-hex\" class=\"header-anchor\">#</a> number.hex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-10\"><a href=\"#example-10\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"number-oct\"><a href=\"#number-oct\" class=\"header-anchor\">#</a> number.oct</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-11\"><a href=\"#example-11\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"number-bin\"><a href=\"#number-bin\" class=\"header-anchor\">#</a> number.bin</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-12\"><a href=\"#example-12\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"number-digital\"><a href=\"#number-digital\" class=\"header-anchor\">#</a> number.digital</h3> <ul><li>Type: <code>string</code></li> <li>Default: <code>'-1023456789._xoeEaAbBcCdDfF+'</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-13\"><a href=\"#example-13\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"number-sep\"><a href=\"#number-sep\" class=\"header-anchor\">#</a> number.sep</h3> <ul><li>Type: <code>string</code></li> <li>Default: <code>'_'</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-14\"><a href=\"#example-14\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"block\"><a href=\"#block\" class=\"header-anchor\">#</a> block</h2> <h3 id=\"block-lex-true\"><a href=\"#block-lex-true\" class=\"header-anchor\">#</a> block.lex: true</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-15\"><a href=\"#example-15\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"block-marker\"><a href=\"#block-marker\" class=\"header-anchor\">#</a> block.marker</h3> <ul><li>Type: <code>{[string]: string}</code></li> <li>Default: <code>{ '\\'\\'\\'': '\\'\\'\\'' }</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-16\"><a href=\"#example-16\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"string\"><a href=\"#string\" class=\"header-anchor\">#</a> string</h2> <h3 id=\"string-lex\"><a href=\"#string-lex\" class=\"header-anchor\">#</a> string.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-17\"><a href=\"#example-17\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"string-escape\"><a href=\"#string-escape\" class=\"header-anchor\">#</a> string.escape</h3> <ul><li>Type: <code>{[string]: string}</code></li> <li>Default: <code>{ b: '\\b', f: '\\f', n: '\\n', r: '\\r', t: '\\t', }</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-18\"><a href=\"#example-18\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"string-multiline\"><a href=\"#string-multiline\" class=\"header-anchor\">#</a> string.multiline</h3> <ul><li>Type: <code>string</code></li> <li>Default: <code>`</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-19\"><a href=\"#example-19\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"string-escapedouble\"><a href=\"#string-escapedouble\" class=\"header-anchor\">#</a> string.escapedouble</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>false</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-20\"><a href=\"#example-20\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"text\"><a href=\"#text\" class=\"header-anchor\">#</a> text</h2> <h3 id=\"text-lex\"><a href=\"#text-lex\" class=\"header-anchor\">#</a> text.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-21\"><a href=\"#example-21\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"map\"><a href=\"#map\" class=\"header-anchor\">#</a> map</h2> <h3 id=\"map-extend\"><a href=\"#map-extend\" class=\"header-anchor\">#</a> map.extend</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-22\"><a href=\"#example-22\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"value\"><a href=\"#value\" class=\"header-anchor\">#</a> value</h2> <h3 id=\"value-lex\"><a href=\"#value-lex\" class=\"header-anchor\">#</a> value.lex</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-23\"><a href=\"#example-23\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"value-src\"><a href=\"#value-src\" class=\"header-anchor\">#</a> value.src:</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-24\"><a href=\"#example-24\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"value-src-2\"><a href=\"#value-src-2\" class=\"header-anchor\">#</a> value.src:</h3> <ul><li>Type: <code>{[string]: string}</code></li> <li>Default: <code>{ 'null': null, 'true': true, 'false': false, }</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-25\"><a href=\"#example-25\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><div class=\"language- extra-class\"><pre><code>},\n</code></pre></div><h2 id=\"plugin\"><a href=\"#plugin\" class=\"header-anchor\">#</a> plugin</h2> <h2 id=\"debug\"><a href=\"#debug\" class=\"header-anchor\">#</a> debug</h2> <h3 id=\"debug-get-console\"><a href=\"#debug-get-console\" class=\"header-anchor\">#</a> debug.get_console</h3> <ul><li>Type: <code>() =&gt; console</code></li> <li>Default: <code>() =&gt; console</code> (System <code>console</code>)</li></ul> <p>Lorem ipsum</p> <h4 id=\"example-26\"><a href=\"#example-26\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><p>:</p> <h3 id=\"debug-maxlen\"><a href=\"#debug-maxlen\" class=\"header-anchor\">#</a> debug.maxlen</h3> <ul><li>Type: <code>number</code></li> <li>Default: <code>33</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-27\"><a href=\"#example-27\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"debug-print\"><a href=\"#debug-print\" class=\"header-anchor\">#</a> debug.print</h3> <h3 id=\"debug-print-config\"><a href=\"#debug-print-config\" class=\"header-anchor\">#</a> debug.print.config</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>false</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-28\"><a href=\"#example-28\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"error\"><a href=\"#error\" class=\"header-anchor\">#</a> error</h2> <h2 id=\"hint\"><a href=\"#hint\" class=\"header-anchor\">#</a> hint</h2> <h2 id=\"token\"><a href=\"#token\" class=\"header-anchor\">#</a> token</h2> <h2 id=\"rule\"><a href=\"#rule\" class=\"header-anchor\">#</a> rule</h2> <h3 id=\"rule-start\"><a href=\"#rule-start\" class=\"header-anchor\">#</a> rule.start</h3> <ul><li>Type: <code>string</code></li> <li>Default: <code>'val'</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-29\"><a href=\"#example-29\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"rule-finish\"><a href=\"#rule-finish\" class=\"header-anchor\">#</a> rule.finish</h3> <ul><li>Type: <code>boolean</code></li> <li>Default: <code>true</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-30\"><a href=\"#example-30\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h3 id=\"rule-maxmul\"><a href=\"#rule-maxmul\" class=\"header-anchor\">#</a> rule.maxmul</h3> <ul><li>Type: <code>number</code></li> <li>Default: <code>3</code></li></ul> <p>Lorem ipsum</p> <h4 id=\"example-31\"><a href=\"#example-31\" class=\"header-anchor\">#</a> Example</h4> <div class=\"language-js extra-class\"><pre class=\"language-js\"><code><span class=\"token keyword\">let</span> jsonic <span class=\"token operator\">=</span> Jsonic<span class=\"token punctuation\">.</span><span class=\"token function\">make</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n</code></pre></div><h2 id=\"config\"><a href=\"#config\" class=\"header-anchor\">#</a> config</h2> <h3 id=\"config-modify\"><a href=\"#config-modify\" class=\"header-anchor\">#</a> config.modify</h3> <h2 id=\"parser\"><a href=\"#parser\" class=\"header-anchor\">#</a> parser</h2> <h3 id=\"parser-start\"><a href=\"#parser-start\" class=\"header-anchor\">#</a> parser.start</h3></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/ref/api.html\" class=\"prev\">\n        API\n      </a></span> <span class=\"next\"><a href=\"/ref/syntax.html\">\n        Syntax\n      </a>\n      →\n    </span></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/27.4e6f90b7.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/ref/syntax.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Syntax | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/28.6e0fa7bc.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/3.6ce1235a.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/7.6be3acca.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link router-link-active\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <ul class=\"sidebar-links\"><li><a href=\"/ref/api.html\" class=\"sidebar-link\">API</a></li><li><a href=\"/ref/options.html\" class=\"sidebar-link\">Options</a></li><li><a href=\"/ref/syntax.html\" aria-current=\"page\" class=\"active sidebar-link\">Syntax</a><ul class=\"sidebar-sub-headers\"><li class=\"sidebar-sub-header\"><a href=\"/ref/syntax.html#cheatsheet\" class=\"sidebar-link\">Cheatsheet</a></li><li class=\"sidebar-sub-header\"><a href=\"/ref/syntax.html#railroad-diagrams\" class=\"sidebar-link\">Railroad Diagrams</a></li></ul></li></ul> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"syntax\"><a href=\"#syntax\" class=\"header-anchor\">#</a> Syntax</h1> <h2 id=\"cheatsheet\"><a href=\"#cheatsheet\" class=\"header-anchor\">#</a> Cheatsheet</h2> <p>This document shows the features of the <span class=\"jsn-name-self\">Jsonic</span> syntax:</p> <div class=\"language-jsonic extra-class\"><pre class=\"language-text\"><code>// jsonic                              // JSON\n\n  // cheat...       # comments\n  /*\n   * ...sheet!\n   */\n                    # implicit top level    { \n  a:                # implicit null           &quot;a&quot;: null,\n\n  b: 1              # optional comma           &quot;b&quot;: 1,\n  c: 1\n  c: 2              # last duplicate wins      &quot;c&quot;: 2,\n  \n  d: f: 3           # key chains               &quot;d&quot;: {\n  d: g: h: 4        # object merging           &quot;f&quot;: 3,\n                                                 &quot;g&quot;: {\n                                                   &quot;h&quot;: 4\n                                                 }   \n                                               }\n                                      \nTODO\n                                             }\n</code></pre></div><h2 id=\"railroad-diagrams\"><a href=\"#railroad-diagrams\" class=\"header-anchor\">#</a> Railroad Diagrams</h2> <div><div><h3>jsonic</h3> <p> After parsing, either an object (<i>map</i>), an array\n(<i>list</i>), or a scalar value is returned. An empty document is\nconsidered valid and returns <code>undefined</code>. While <i>map</i>\nand <i>list</i> are strictly redundant here as they can be children of\n<i>value</i>, implicit maps (no surrounding braces <code>{}</code>)\nand implicit lists (no surrounding square brackets <code>[]</code>)\nare special cases at the top level.</p> <svg width=\"233\" height=\"152\" viewBox=\"0 0 233 152\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 21v20m0 -10h20\"></path></g> <g><path d=\"M40 31h0\"></path> <path d=\"M193 31h0\"></path> <path d=\"M40 31h20\"></path> <g class=\"non-terminal\"><path d=\"M60 31h33.75\"></path> <path d=\"M139.25 31h33.75\"></path> <rect x=\"93.75\" y=\"20\" width=\"45.5\" height=\"22\"></rect> <text x=\"116.5\" y=\"35\">map</text></g> <path d=\"M173 31h20\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M60 61h29.5\"></path> <path d=\"M143.5 61h29.5\"></path> <rect x=\"89.5\" y=\"50\" width=\"54\" height=\"22\"></rect> <text x=\"116.5\" y=\"65\">list</text></g> <path d=\"M173 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M60 91h25.25\"></path> <path d=\"M147.75 91h25.25\"></path> <rect x=\"85.25\" y=\"80\" width=\"62.5\" height=\"22\"></rect> <text x=\"116.5\" y=\"95\">value</text></g> <path d=\"M173 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 121h0\"></path> <path d=\"M173 121h0\"></path> <g class=\"terminal\"><path d=\"M60 121h0\"></path> <path d=\"M80 121h0\"></path> <rect x=\"60\" y=\"110\" width=\"20\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"70\" y=\"125\"></text></g> <path d=\"M80 121h10\"></path> <path d=\"M90 121h10\"></path> <g class=\"comment\"><path d=\"M100 121h0\"></path> <path d=\"M173 121h0\"></path> <text x=\"136.5\" y=\"126\" class=\"comment\">undefined</text></g></g> <path d=\"M173 121a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"></path></g> <path d=\"M 193 31 h 20 m 0 -10 v 20\"></path></g></svg></div><div><h3>map</h3> <p>One or more keys may preceed a value, creating child maps as\nneeded. Missing values are <code>null</code>. Spurious commas are not\nallowed. Key-value pairs can be separated by <i>space</i> and new lines.</p> <svg width=\"559\" height=\"99\" viewBox=\"0 0 559 99\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 30v20m0 -10h20\"></path></g> <path d=\"M40 40h10\"></path> <g><path d=\"M50 40h0\"></path> <path d=\"M509 40h0\"></path> <g class=\"terminal\"><path d=\"M50 40h0\"></path> <path d=\"M78.5 40h0\"></path> <rect x=\"50\" y=\"29\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"64.25\" y=\"44\">{</text></g> <path d=\"M78.5 40h10\"></path> <g><path d=\"M88.5 40h0\"></path> <path d=\"M470.5 40h0\"></path> <path d=\"M88.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M108.5 20h342\"></path></g> <path d=\"M450.5 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M88.5 40h20\"></path> <g><path d=\"M108.5 40h0\"></path> <path d=\"M450.5 40h0\"></path> <path d=\"M108.5 40h10\"></path> <g><path d=\"M118.5 40h0\"></path> <path d=\"M440.5 40h0\"></path> <g><path d=\"M118.5 40h0\"></path> <path d=\"M345 40h0\"></path> <g><path d=\"M118.5 40h0\"></path> <path d=\"M232.5 40h0\"></path> <path d=\"M118.5 40h10\"></path> <g><path d=\"M128.5 40h0\"></path> <path d=\"M222.5 40h0\"></path> <g class=\"non-terminal\"><path d=\"M128.5 40h0\"></path> <path d=\"M174 40h0\"></path> <rect x=\"128.5\" y=\"29\" width=\"45.5\" height=\"22\"></rect> <text x=\"151.25\" y=\"44\">key</text></g> <path d=\"M174 40h10\"></path> <path d=\"M184 40h10\"></path> <g class=\"terminal\"><path d=\"M194 40h0\"></path> <path d=\"M222.5 40h0\"></path> <rect x=\"194\" y=\"29\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208.25\" y=\"44\">:</text></g></g> <path d=\"M222.5 40h10\"></path> <path d=\"M128.5 40a10 10 0 0 0 -10 10v0a10 10 0 0 0 10 10\"></path> <g><path d=\"M128.5 60h94\"></path></g> <path d=\"M222.5 60a10 10 0 0 0 10 -10v0a10 10 0 0 0 -10 -10\"></path></g> <path d=\"M232.5 40h10\"></path> <g><path d=\"M242.5 40h0\"></path> <path d=\"M345 40h0\"></path> <path d=\"M242.5 40h20\"></path> <g><path d=\"M262.5 40h62.5\"></path></g> <path d=\"M325 40h20\"></path> <path d=\"M242.5 40a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M262.5 60h0\"></path> <path d=\"M325 60h0\"></path> <rect x=\"262.5\" y=\"49\" width=\"62.5\" height=\"22\"></rect> <text x=\"293.75\" y=\"64\">value</text></g> <path d=\"M325 60a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path></g></g> <path d=\"M345 40h10\"></path> <g><path d=\"M355 40h0\"></path> <path d=\"M440.5 40h0\"></path> <path d=\"M355 40h20\"></path> <g><path d=\"M375 40h45.5\"></path></g> <path d=\"M420.5 40h20\"></path> <path d=\"M355 40a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M375 60h0\"></path> <path d=\"M420.5 60h0\"></path> <rect x=\"375\" y=\"49\" width=\"45.5\" height=\"22\"></rect> <text x=\"397.75\" y=\"64\">sep</text></g> <path d=\"M420.5 60a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path></g></g> <path d=\"M440.5 40h10\"></path> <path d=\"M118.5 40a10 10 0 0 0 -10 10v19a10 10 0 0 0 10 10\"></path> <g><path d=\"M118.5 79h322\"></path></g> <path d=\"M440.5 79a10 10 0 0 0 10 -10v-19a10 10 0 0 0 -10 -10\"></path></g> <path d=\"M450.5 40h20\"></path></g> <path d=\"M470.5 40h10\"></path> <g class=\"terminal\"><path d=\"M480.5 40h0\"></path> <path d=\"M509 40h0\"></path> <rect x=\"480.5\" y=\"29\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"494.75\" y=\"44\">}</text></g></g> <path d=\"M509 40h10\"></path> <path d=\"M 519 40 h 20 m 0 -10 v 20\"></path></g></svg></div><div><h3>list</h3> <p> List <i>values</i> are separated by <i>space</i>, commas and new\nlines.  A trailing comma is ignored. A comma without a preceding value\ninserts an implicit <code>null</code>.  </p> <svg width=\"425\" height=\"108\" viewBox=\"0 0 425 108\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 30v20m0 -10h20\"></path></g> <path d=\"M40 40h10\"></path> <g><path d=\"M50 40h0\"></path> <path d=\"M375 40h0\"></path> <g class=\"terminal\"><path d=\"M50 40h0\"></path> <path d=\"M78.5 40h0\"></path> <rect x=\"50\" y=\"29\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"64.25\" y=\"44\">[</text></g> <path d=\"M78.5 40h10\"></path> <path d=\"M88.5 40h10\"></path> <g><path d=\"M98.5 40h0\"></path> <path d=\"M326.5 40h0\"></path> <path d=\"M98.5 40h10\"></path> <g><path d=\"M108.5 40h0\"></path> <path d=\"M316.5 40h0\"></path> <g><path d=\"M108.5 40h0\"></path> <path d=\"M211 40h0\"></path> <path d=\"M108.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M128.5 20h62.5\"></path></g> <path d=\"M191 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M108.5 40h20\"></path> <g class=\"non-terminal\"><path d=\"M128.5 40h0\"></path> <path d=\"M191 40h0\"></path> <rect x=\"128.5\" y=\"29\" width=\"62.5\" height=\"22\"></rect> <text x=\"159.75\" y=\"44\">value</text></g> <path d=\"M191 40h20\"></path></g> <g><path d=\"M211 40h0\"></path> <path d=\"M316.5 40h0\"></path> <path d=\"M211 40h20\"></path> <g><path d=\"M231 40h65.5\"></path></g> <path d=\"M296.5 40h20\"></path> <path d=\"M211 40a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <g><path d=\"M231 60h0\"></path> <path d=\"M296.5 60h0\"></path> <path d=\"M231 60h10\"></path> <g class=\"non-terminal\"><path d=\"M241 60h0\"></path> <path d=\"M286.5 60h0\"></path> <rect x=\"241\" y=\"49\" width=\"45.5\" height=\"22\"></rect> <text x=\"263.75\" y=\"64\">sep</text></g> <path d=\"M286.5 60h10\"></path> <path d=\"M241 60a10 10 0 0 0 -10 10v0a10 10 0 0 0 10 10\"></path> <g><path d=\"M241 80h45.5\"></path></g> <path d=\"M286.5 80a10 10 0 0 0 10 -10v0a10 10 0 0 0 -10 -10\"></path></g> <path d=\"M296.5 60a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path></g></g> <path d=\"M316.5 40h10\"></path> <path d=\"M108.5 40a10 10 0 0 0 -10 10v28a10 10 0 0 0 10 10\"></path> <g><path d=\"M108.5 88h208\"></path></g> <path d=\"M316.5 88a10 10 0 0 0 10 -10v-28a10 10 0 0 0 -10 -10\"></path></g> <path d=\"M326.5 40h10\"></path> <path d=\"M336.5 40h10\"></path> <g class=\"terminal\"><path d=\"M346.5 40h0\"></path> <path d=\"M375 40h0\"></path> <rect x=\"346.5\" y=\"29\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"360.75\" y=\"44\">]</text></g></g> <path d=\"M375 40h10\"></path> <path d=\"M 385 40 h 20 m 0 -10 v 20\"></path></g></svg></div><div><h3>value</h3> <p> A <i>value</i> can be surrounded by optional <i>space</i> (To\nmatch <a href=\"http://json.org\">json.org</a>, our non-empty\n<i>space</i> must be optional).  </p> <svg width=\"416\" height=\"251\" viewBox=\"0 0 416 251\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 30v20m0 -10h20\"></path></g> <path d=\"M40 40h10\"></path> <g><path d=\"M50 40h0\"></path> <path d=\"M366 40h0\"></path> <g><path d=\"M50 40h0\"></path> <path d=\"M152.5 40h0\"></path> <path d=\"M50 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M70 20h62.5\"></path></g> <path d=\"M132.5 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M50 40h20\"></path> <g class=\"non-terminal\"><path d=\"M70 40h0\"></path> <path d=\"M132.5 40h0\"></path> <rect x=\"70\" y=\"29\" width=\"62.5\" height=\"22\"></rect> <text x=\"101.25\" y=\"44\">space</text></g> <path d=\"M132.5 40h20\"></path></g> <g><path d=\"M152.5 40h0\"></path> <path d=\"M263.5 40h0\"></path> <path d=\"M152.5 40h20\"></path> <g class=\"non-terminal\"><path d=\"M172.5 40h12.75\"></path> <path d=\"M230.75 40h12.75\"></path> <rect x=\"185.25\" y=\"29\" width=\"45.5\" height=\"22\"></rect> <text x=\"208\" y=\"44\">map</text></g> <path d=\"M243.5 40h20\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M172.5 70h8.5\"></path> <path d=\"M235 70h8.5\"></path> <rect x=\"181\" y=\"59\" width=\"54\" height=\"22\"></rect> <text x=\"208\" y=\"74\">list</text></g> <path d=\"M243.5 70a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M172.5 100h0\"></path> <path d=\"M243.5 100h0\"></path> <rect x=\"172.5\" y=\"89\" width=\"71\" height=\"22\"></rect> <text x=\"208\" y=\"104\">string</text></g> <path d=\"M243.5 100a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M172.5 130h0\"></path> <path d=\"M243.5 130h0\"></path> <rect x=\"172.5\" y=\"119\" width=\"71\" height=\"22\"></rect> <text x=\"208\" y=\"134\">number</text></g> <path d=\"M243.5 130a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v100a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M172.5 160h8.5\"></path> <path d=\"M235 160h8.5\"></path> <rect x=\"181\" y=\"149\" width=\"54\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208\" y=\"164\">true</text></g> <path d=\"M243.5 160a10 10 0 0 0 10 -10v-100a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v130a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M172.5 190h4.25\"></path> <path d=\"M239.25 190h4.25\"></path> <rect x=\"176.75\" y=\"179\" width=\"62.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208\" y=\"194\">false</text></g> <path d=\"M243.5 190a10 10 0 0 0 10 -10v-130a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v160a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M172.5 220h8.5\"></path> <path d=\"M235 220h8.5\"></path> <rect x=\"181\" y=\"209\" width=\"54\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208\" y=\"224\">null</text></g> <path d=\"M243.5 220a10 10 0 0 0 10 -10v-160a10 10 0 0 1 10 -10\"></path></g> <g><path d=\"M263.5 40h0\"></path> <path d=\"M366 40h0\"></path> <path d=\"M263.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M283.5 20h62.5\"></path></g> <path d=\"M346 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M263.5 40h20\"></path> <g class=\"non-terminal\"><path d=\"M283.5 40h0\"></path> <path d=\"M346 40h0\"></path> <rect x=\"283.5\" y=\"29\" width=\"62.5\" height=\"22\"></rect> <text x=\"314.75\" y=\"44\">space</text></g> <path d=\"M346 40h20\"></path></g></g> <path d=\"M366 40h10\"></path> <path d=\"M 376 40 h 20 m 0 -10 v 20\"></path></g></svg></div><div><h3>key</h3> <p>Keys are verbatim source characters including unquoted text, number and literal value representations, but excluding punctuation ({}[]...).</p> <svg width=\"416\" height=\"191\" viewBox=\"0 0 416 191\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 30v20m0 -10h20\"></path></g> <path d=\"M40 40h10\"></path> <g><path d=\"M50 40h0\"></path> <path d=\"M366 40h0\"></path> <g><path d=\"M50 40h0\"></path> <path d=\"M152.5 40h0\"></path> <path d=\"M50 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M70 20h62.5\"></path></g> <path d=\"M132.5 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M50 40h20\"></path> <g class=\"non-terminal\"><path d=\"M70 40h0\"></path> <path d=\"M132.5 40h0\"></path> <rect x=\"70\" y=\"29\" width=\"62.5\" height=\"22\"></rect> <text x=\"101.25\" y=\"44\">space</text></g> <path d=\"M132.5 40h20\"></path></g> <g><path d=\"M152.5 40h0\"></path> <path d=\"M263.5 40h0\"></path> <path d=\"M152.5 40h20\"></path> <g class=\"non-terminal\"><path d=\"M172.5 40h0\"></path> <path d=\"M243.5 40h0\"></path> <rect x=\"172.5\" y=\"29\" width=\"71\" height=\"22\"></rect> <text x=\"208\" y=\"44\">string</text></g> <path d=\"M243.5 40h20\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M172.5 70h0\"></path> <path d=\"M243.5 70h0\"></path> <rect x=\"172.5\" y=\"59\" width=\"71\" height=\"22\"></rect> <text x=\"208\" y=\"74\">number</text></g> <path d=\"M243.5 70a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M172.5 100h8.5\"></path> <path d=\"M235 100h8.5\"></path> <rect x=\"181\" y=\"89\" width=\"54\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208\" y=\"104\">true</text></g> <path d=\"M243.5 100a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M172.5 130h4.25\"></path> <path d=\"M239.25 130h4.25\"></path> <rect x=\"176.75\" y=\"119\" width=\"62.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208\" y=\"134\">false</text></g> <path d=\"M243.5 130a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"></path> <path d=\"M152.5 40a10 10 0 0 1 10 10v100a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M172.5 160h8.5\"></path> <path d=\"M235 160h8.5\"></path> <rect x=\"181\" y=\"149\" width=\"54\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"208\" y=\"164\">null</text></g> <path d=\"M243.5 160a10 10 0 0 0 10 -10v-100a10 10 0 0 1 10 -10\"></path></g> <g><path d=\"M263.5 40h0\"></path> <path d=\"M366 40h0\"></path> <path d=\"M263.5 40a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M283.5 20h62.5\"></path></g> <path d=\"M346 20a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M263.5 40h20\"></path> <g class=\"non-terminal\"><path d=\"M283.5 40h0\"></path> <path d=\"M346 40h0\"></path> <rect x=\"283.5\" y=\"29\" width=\"62.5\" height=\"22\"></rect> <text x=\"314.75\" y=\"44\">space</text></g> <path d=\"M346 40h20\"></path></g></g> <path d=\"M366 40h10\"></path> <path d=\"M 376 40 h 20 m 0 -10 v 20\"></path></g></svg></div><div><h3>sep</h3> <p> Commas, spaces, new lines, and nothing, all separate values equivalently. </p> <svg width=\"182.5\" height=\"111\" viewBox=\"0 0 182.5 111\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 20v20m10 -20v20m-10 -10h20\"></path></g> <g><path d=\"M40 30h0\"></path> <path d=\"M142.5 30h0\"></path> <path d=\"M40 30h20\"></path> <g><path d=\"M60 30h62.5\"></path></g> <path d=\"M122.5 30h20\"></path> <path d=\"M40 30a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M60 50h17\"></path> <path d=\"M105.5 50h17\"></path> <rect x=\"77\" y=\"39\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"91.25\" y=\"54\">,</text></g> <path d=\"M122.5 50a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <path d=\"M40 30a10 10 0 0 1 10 10v30a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M60 80h0\"></path> <path d=\"M122.5 80h0\"></path> <rect x=\"60\" y=\"69\" width=\"62.5\" height=\"22\"></rect> <text x=\"91.25\" y=\"84\">space</text></g> <path d=\"M122.5 80a10 10 0 0 0 10 -10v-30a10 10 0 0 1 10 -10\"></path></g> <path d=\"M 142.5 30 h 20 m -10 -10 v 20 m 10 -20 v 20\"></path></g></svg></div><div><h3>space</h3> <p> There must be at least one space character in the space token\n(unlike <a href=\"http://json.org\">json.org</a>—thus we make\n<i>space</i> optional elsewhere as needed). Space can contain one or comments.</p> <svg width=\"377.5\" height=\"190\" viewBox=\"0 0 377.5 190\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 21v20m10 -20v20m-10 -10h20\"></path></g> <g><path d=\"M40 31h0\"></path> <path d=\"M337.5 31h0\"></path> <path d=\"M40 31h20\"></path> <g><path d=\"M60 31h0\"></path> <path d=\"M317.5 31h0\"></path> <path d=\"M60 31h10\"></path> <g><path d=\"M70 31h0\"></path> <path d=\"M307.5 31h0\"></path> <path d=\"M70 31h20\"></path> <g><path d=\"M90 31h30.75\"></path> <path d=\"M256.75 31h30.75\"></path> <g class=\"terminal\"><path d=\"M120.75 31h0\"></path> <path d=\"M191.75 31h0\"></path> <rect x=\"120.75\" y=\"20\" width=\"71\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"156.25\" y=\"35\">«0x20»</text></g> <path d=\"M191.75 31h10\"></path> <path d=\"M201.75 31h10\"></path> <g class=\"comment\"><path d=\"M211.75 31h0\"></path> <path d=\"M256.75 31h0\"></path> <text x=\"234.25\" y=\"36\" class=\"comment\">space</text></g></g> <path d=\"M287.5 31h20\"></path> <path d=\"M70 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g><path d=\"M90 61h24.5\"></path> <path d=\"M263 61h24.5\"></path> <g class=\"terminal\"><path d=\"M114.5 61h0\"></path> <path d=\"M177 61h0\"></path> <rect x=\"114.5\" y=\"50\" width=\"62.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"145.75\" y=\"65\">«0xA»</text></g> <path d=\"M177 61h10\"></path> <path d=\"M187 61h10\"></path> <g class=\"comment\"><path d=\"M197 61h0\"></path> <path d=\"M263 61h0\"></path> <text x=\"230\" y=\"66\" class=\"comment\">linefeed</text></g></g> <path d=\"M287.5 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M70 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g><path d=\"M90 91h0\"></path> <path d=\"M287.5 91h0\"></path> <g class=\"terminal\"><path d=\"M90 91h0\"></path> <path d=\"M152.5 91h0\"></path> <rect x=\"90\" y=\"80\" width=\"62.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"121.25\" y=\"95\">«0xD»</text></g> <path d=\"M152.5 91h10\"></path> <path d=\"M162.5 91h10\"></path> <g class=\"comment\"><path d=\"M172.5 91h0\"></path> <path d=\"M287.5 91h0\"></path> <text x=\"230\" y=\"96\" class=\"comment\">carriage return</text></g></g> <path d=\"M287.5 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path> <path d=\"M70 31a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"></path> <g><path d=\"M90 121h3.5\"></path> <path d=\"M284 121h3.5\"></path> <g class=\"terminal\"><path d=\"M93.5 121h0\"></path> <path d=\"M156 121h0\"></path> <rect x=\"93.5\" y=\"110\" width=\"62.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"124.75\" y=\"125\">«0x9»</text></g> <path d=\"M156 121h10\"></path> <path d=\"M166 121h10\"></path> <g class=\"comment\"><path d=\"M176 121h0\"></path> <path d=\"M284 121h0\"></path> <text x=\"230\" y=\"126\" class=\"comment\">horizontal tab</text></g></g> <path d=\"M287.5 121a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"></path> <path d=\"M70 31a10 10 0 0 1 10 10v100a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M90 151h59\"></path> <path d=\"M228.5 151h59\"></path> <rect x=\"149\" y=\"140\" width=\"79.5\" height=\"22\"></rect> <text x=\"188.75\" y=\"155\">comment</text></g> <path d=\"M287.5 151a10 10 0 0 0 10 -10v-100a10 10 0 0 1 10 -10\"></path></g> <path d=\"M307.5 31h10\"></path> <path d=\"M70 31a10 10 0 0 0 -10 10v119a10 10 0 0 0 10 10\"></path> <g><path d=\"M70 170h237.5\"></path></g> <path d=\"M307.5 170a10 10 0 0 0 10 -10v-119a10 10 0 0 0 -10 -10\"></path></g> <path d=\"M317.5 31h20\"></path></g> <path d=\"M 337.5 31 h 20 m -10 -10 v 20 m 10 -20 v 20\"></path></g></svg></div><div><h3>string</h3> <p>Quoted strings (using «&quot;'`») work as in JavaScript, \nincluding escaping. Triple single quotes operate as backticks, but\nremove the indent from each line.</p> <svg width=\"630.5\" height=\"242\" viewBox=\"0 0 630.5 242\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 21v20m10 -20v20m-10 -10h20\"></path></g> <g><path d=\"M40 31h0\"></path> <path d=\"M590.5 31h0\"></path> <path d=\"M40 31h20\"></path> <g><path d=\"M60 31h27.5\"></path> <path d=\"M543 31h27.5\"></path> <g class=\"non-terminal\"><path d=\"M87.5 31h0\"></path> <path d=\"M422 31h0\"></path> <rect x=\"87.5\" y=\"20\" width=\"334.5\" height=\"22\"></rect> <text x=\"254.75\" y=\"35\">all-chars ∖ «\\t \\r\\n\\b\\f\\'&quot;`{}[]:,/#»</text></g> <path d=\"M422 31h10\"></path> <path d=\"M432 31h10\"></path> <g class=\"comment\"><path d=\"M442 31h0\"></path> <path d=\"M543 31h0\"></path> <text x=\"492.5\" y=\"36\" class=\"comment\">unquoted text</text></g></g> <path d=\"M570.5 31h20\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 61h86\"></path> <path d=\"M484.5 61h86\"></path> <g><path d=\"M146 61h0\"></path> <path d=\"M214.5 61h0\"></path> <path d=\"M146 61h20\"></path> <g class=\"terminal\"><path d=\"M166 61h0\"></path> <path d=\"M194.5 61h0\"></path> <rect x=\"166\" y=\"50\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"180.25\" y=\"65\">&quot;</text></g> <path d=\"M194.5 61h20\"></path></g> <path d=\"M214.5 61h10\"></path> <g class=\"non-terminal\"><path d=\"M224.5 61h0\"></path> <path d=\"M406 61h0\"></path> <rect x=\"224.5\" y=\"50\" width=\"181.5\" height=\"22\"></rect> <text x=\"315.25\" y=\"65\">escaped-chars ∪ «'»</text></g> <path d=\"M406 61h10\"></path> <g><path d=\"M416 61h0\"></path> <path d=\"M484.5 61h0\"></path> <path d=\"M416 61h20\"></path> <g class=\"terminal\"><path d=\"M436 61h0\"></path> <path d=\"M464.5 61h0\"></path> <rect x=\"436\" y=\"50\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"450.25\" y=\"65\">&quot;</text></g> <path d=\"M464.5 61h20\"></path></g></g> <path d=\"M570.5 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 91h86\"></path> <path d=\"M484.5 91h86\"></path> <g><path d=\"M146 91h0\"></path> <path d=\"M214.5 91h0\"></path> <path d=\"M146 91h20\"></path> <g class=\"terminal\"><path d=\"M166 91h0\"></path> <path d=\"M194.5 91h0\"></path> <rect x=\"166\" y=\"80\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"180.25\" y=\"95\">'</text></g> <path d=\"M194.5 91h20\"></path></g> <path d=\"M214.5 91h10\"></path> <g class=\"non-terminal\"><path d=\"M224.5 91h0\"></path> <path d=\"M406 91h0\"></path> <rect x=\"224.5\" y=\"80\" width=\"181.5\" height=\"22\"></rect> <text x=\"315.25\" y=\"95\">escaped-chars ∪ «&quot;»</text></g> <path d=\"M406 91h10\"></path> <g><path d=\"M416 91h0\"></path> <path d=\"M484.5 91h0\"></path> <path d=\"M416 91h20\"></path> <g class=\"terminal\"><path d=\"M436 91h0\"></path> <path d=\"M464.5 91h0\"></path> <rect x=\"436\" y=\"80\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"450.25\" y=\"95\">'</text></g> <path d=\"M464.5 91h20\"></path></g></g> <path d=\"M570.5 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v70a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 121h93\"></path> <path d=\"M477.5 121h93\"></path> <g><path d=\"M153 121h0\"></path> <path d=\"M221.5 121h0\"></path> <path d=\"M153 121h20\"></path> <g class=\"terminal\"><path d=\"M173 121h0\"></path> <path d=\"M201.5 121h0\"></path> <rect x=\"173\" y=\"110\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"187.25\" y=\"125\">`</text></g> <path d=\"M201.5 121h20\"></path></g> <path d=\"M221.5 121h10\"></path> <g><path d=\"M231.5 121h0\"></path> <path d=\"M399 121h0\"></path> <g><path d=\"M231.5 121h0\"></path> <path d=\"M399 121h0\"></path> <path d=\"M231.5 121h10\"></path> <g><path d=\"M241.5 121h0\"></path> <path d=\"M389 121h0\"></path> <g class=\"non-terminal\"><path d=\"M241.5 121h0\"></path> <path d=\"M389 121h0\"></path> <rect x=\"241.5\" y=\"110\" width=\"147.5\" height=\"22\"></rect> <text x=\"315.25\" y=\"125\">all-chars ∖ «`»</text></g></g> <path d=\"M389 121h10\"></path> <path d=\"M241.5 121a10 10 0 0 0 -10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M241.5 151h8.5\"></path> <path d=\"M380.5 151h8.5\"></path> <rect x=\"250\" y=\"140\" width=\"130.5\" height=\"22\"></rect> <text x=\"315.25\" y=\"155\">escaped-chars</text></g> <path d=\"M389 151a10 10 0 0 0 10 -10v-10a10 10 0 0 0 -10 -10\"></path></g></g> <path d=\"M399 121h10\"></path> <g><path d=\"M409 121h0\"></path> <path d=\"M477.5 121h0\"></path> <path d=\"M409 121h20\"></path> <g class=\"terminal\"><path d=\"M429 121h0\"></path> <path d=\"M457.5 121h0\"></path> <rect x=\"429\" y=\"110\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"443.25\" y=\"125\">`</text></g> <path d=\"M457.5 121h20\"></path></g></g> <path d=\"M570.5 121a10 10 0 0 0 10 -10v-70a10 10 0 0 1 10 -10\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v130a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 181h0\"></path> <path d=\"M570.5 181h0\"></path> <g><path d=\"M60 181h0\"></path> <path d=\"M145.5 181h0\"></path> <path d=\"M60 181h20\"></path> <g class=\"terminal\"><path d=\"M80 181h0\"></path> <path d=\"M125.5 181h0\"></path> <rect x=\"80\" y=\"170\" width=\"45.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"102.75\" y=\"185\">'''</text></g> <path d=\"M125.5 181h20\"></path></g> <path d=\"M145.5 181h10\"></path> <g><path d=\"M155.5 181h0\"></path> <path d=\"M475 181h0\"></path> <g><path d=\"M155.5 181h0\"></path> <path d=\"M340 181h0\"></path> <path d=\"M155.5 181h10\"></path> <g><path d=\"M165.5 181h0\"></path> <path d=\"M330 181h0\"></path> <g class=\"non-terminal\"><path d=\"M165.5 181h0\"></path> <path d=\"M330 181h0\"></path> <rect x=\"165.5\" y=\"170\" width=\"164.5\" height=\"22\"></rect> <text x=\"247.75\" y=\"185\">all-chars ∖ &quot;'''&quot;</text></g></g> <path d=\"M330 181h10\"></path> <path d=\"M165.5 181a10 10 0 0 0 -10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M165.5 211h17\"></path> <path d=\"M313 211h17\"></path> <rect x=\"182.5\" y=\"200\" width=\"130.5\" height=\"22\"></rect> <text x=\"247.75\" y=\"215\">escaped-chars</text></g> <path d=\"M330 211a10 10 0 0 0 10 -10v-10a10 10 0 0 0 -10 -10\"></path></g> <path d=\"M340 181h10\"></path> <path d=\"M350 181h10\"></path> <g class=\"comment\"><path d=\"M360 181h0\"></path> <path d=\"M475 181h0\"></path> <text x=\"417.5\" y=\"186\" class=\"comment\">indents removed</text></g></g> <path d=\"M475 181h10\"></path> <g><path d=\"M485 181h0\"></path> <path d=\"M570.5 181h0\"></path> <path d=\"M485 181h20\"></path> <g class=\"terminal\"><path d=\"M505 181h0\"></path> <path d=\"M550.5 181h0\"></path> <rect x=\"505\" y=\"170\" width=\"45.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"527.75\" y=\"185\">'''</text></g> <path d=\"M550.5 181h20\"></path></g></g> <path d=\"M570.5 181a10 10 0 0 0 10 -10v-130a10 10 0 0 1 10 -10\"></path></g> <path d=\"M 590.5 31 h 20 m -10 -10 v 20 m 10 -20 v 20\"></path></g></svg></div><div><h3>number</h3> <p>Number literals work as in JavaScript. \nUnderscore can be used to separate digits.</p> <svg width=\"857\" height=\"311\" viewBox=\"0 0 857 311\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 38v20m10 -20v20m-10 -10h20\"></path></g> <g><path d=\"M40 48h0\"></path> <path d=\"M817 48h0\"></path> <path d=\"M40 48a10 10 0 0 0 10 -10v-8a10 10 0 0 1 10 -10h398.5\"></path> <path d=\"M118.5 291h678.5a10 10 0 0 0 10 -10v-223a10 10 0 0 1 10 -10\"></path> <path d=\"M40 48h10\"></path> <g class=\"terminal\"><path d=\"M50 48h10\"></path> <path d=\"M88.5 48h10\"></path> <rect x=\"60\" y=\"37\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"74.25\" y=\"52\">0</text></g> <path d=\"M98.5 48a10 10 0 0 1 10 10v223a10 10 0 0 0 10 10\"></path> <path d=\"M98.5 20a10 10 0 0 1 10 10v8a10 10 0 0 0 10 10\"></path> <g><path d=\"M118.5 48h10\"></path> <path d=\"M128.5 48h10\"></path> <g><path d=\"M138.5 48h95.75\"></path> <path d=\"M342.75 48h95.75\"></path> <path d=\"M234.25 48a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M254.25 28h68.5\"></path></g> <path d=\"M322.75 28a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M234.25 48h20\"></path> <g><path d=\"M254.25 48h0\"></path> <path d=\"M322.75 48h0\"></path> <path d=\"M254.25 48h20\"></path> <g class=\"terminal\"><path d=\"M274.25 48h0\"></path> <path d=\"M302.75 48h0\"></path> <rect x=\"274.25\" y=\"37\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"288.5\" y=\"52\">-</text></g> <path d=\"M302.75 48h20\"></path> <path d=\"M254.25 48a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M274.25 78h0\"></path> <path d=\"M302.75 78h0\"></path> <rect x=\"274.25\" y=\"67\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"288.5\" y=\"82\">+</text></g> <path d=\"M302.75 78a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path></g> <path d=\"M322.75 48h20\"></path></g> <path d=\"M438.5 48a10 10 0 0 1 10 10v29a10 10 0 0 1 -10 10h-300a10 10 0 0 0 -10 10v0a10 10 0 0 0 10 10\"></path> <g><path d=\"M138.5 117h18\"></path> <path d=\"M420.5 117h18\"></path> <path d=\"M156.5 117h20\"></path> <g class=\"terminal\"><path d=\"M176.5 117h97.75\"></path> <path d=\"M302.75 117h97.75\"></path> <rect x=\"274.25\" y=\"106\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"288.5\" y=\"121\">0</text></g> <path d=\"M400.5 117h20\"></path> <path d=\"M156.5 117a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"non-terminal\"><path d=\"M176.5 147h0\"></path> <path d=\"M400.5 147h0\"></path> <rect x=\"176.5\" y=\"136\" width=\"224\" height=\"22\"></rect> <text x=\"288.5\" y=\"151\">digits [1-9][0-9]* ∪ «_»</text></g> <path d=\"M400.5 147a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path></g> <path d=\"M438.5 117a10 10 0 0 1 10 10v29a10 10 0 0 1 -10 10h-300a10 10 0 0 0 -10 10v8a10 10 0 0 0 10 10\"></path> <g><path d=\"M138.5 194h15\"></path> <path d=\"M423.5 194h15\"></path> <path d=\"M153.5 194a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M173.5 174h230\"></path></g> <path d=\"M403.5 174a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M153.5 194h20\"></path> <g><path d=\"M173.5 194h0\"></path> <path d=\"M403.5 194h0\"></path> <g class=\"terminal\"><path d=\"M173.5 194h0\"></path> <path d=\"M202 194h0\"></path> <rect x=\"173.5\" y=\"183\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"187.75\" y=\"198\">.</text></g> <path d=\"M202 194h10\"></path> <path d=\"M212 194h10\"></path> <g class=\"non-terminal\"><path d=\"M222 194h0\"></path> <path d=\"M403.5 194h0\"></path> <rect x=\"222\" y=\"183\" width=\"181.5\" height=\"22\"></rect> <text x=\"312.75\" y=\"198\">digits [0-9]* ∪ «_»</text></g></g> <path d=\"M403.5 194h20\"></path></g> <path d=\"M438.5 194a10 10 0 0 1 10 10v0a10 10 0 0 1 -10 10h-300a10 10 0 0 0 -10 10v8a10 10 0 0 0 10 10\"></path> <g><path d=\"M138.5 242h0\"></path> <path d=\"M438.5 242h0\"></path> <path d=\"M138.5 242a10 10 0 0 0 10 -10v0a10 10 0 0 1 10 -10\"></path> <g><path d=\"M158.5 222h260\"></path></g> <path d=\"M418.5 222a10 10 0 0 1 10 10v0a10 10 0 0 0 10 10\"></path> <path d=\"M138.5 242h20\"></path> <g><path d=\"M158.5 242h0\"></path> <path d=\"M418.5 242h0\"></path> <g><path d=\"M158.5 242h0\"></path> <path d=\"M227 242h0\"></path> <path d=\"M158.5 242h20\"></path> <g class=\"terminal\"><path d=\"M178.5 242h0\"></path> <path d=\"M207 242h0\"></path> <rect x=\"178.5\" y=\"231\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"192.75\" y=\"246\">e</text></g> <path d=\"M207 242h20\"></path> <path d=\"M158.5 242a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g class=\"terminal\"><path d=\"M178.5 272h0\"></path> <path d=\"M207 272h0\"></path> <rect x=\"178.5\" y=\"261\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"192.75\" y=\"276\">E</text></g> <path d=\"M207 272a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path></g> <path d=\"M227 242h10\"></path> <g class=\"non-terminal\"><path d=\"M237 242h0\"></path> <path d=\"M418.5 242h0\"></path> <rect x=\"237\" y=\"231\" width=\"181.5\" height=\"22\"></rect> <text x=\"327.75\" y=\"246\">digits [0-9]* ∪ «_»</text></g></g> <path d=\"M418.5 242h20\"></path></g> <path d=\"M438.5 242h10\"></path> <path d=\"M448.5 242h10\"></path></g> <path d=\"M458.5 242a10 10 0 0 1 10 10v29a10 10 0 0 0 10 10\"></path> <path d=\"M458.5 20a10 10 0 0 1 10 10v8a10 10 0 0 0 10 10\"></path> <g><path d=\"M478.5 48h10\"></path> <path d=\"M797 48h10\"></path> <g class=\"terminal\"><path d=\"M488.5 48h0\"></path> <path d=\"M517 48h0\"></path> <rect x=\"488.5\" y=\"37\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"502.75\" y=\"52\">0</text></g> <path d=\"M517 48h10\"></path> <g><path d=\"M527 48h0\"></path> <path d=\"M797 48h0\"></path> <path d=\"M527 48h20\"></path> <g><path d=\"M547 48h0\"></path> <path d=\"M777 48h0\"></path> <g class=\"terminal\"><path d=\"M547 48h0\"></path> <path d=\"M575.5 48h0\"></path> <rect x=\"547\" y=\"37\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"561.25\" y=\"52\">x</text></g> <path d=\"M575.5 48h10\"></path> <path d=\"M585.5 48h10\"></path> <g class=\"non-terminal\"><path d=\"M595.5 48h0\"></path> <path d=\"M777 48h0\"></path> <rect x=\"595.5\" y=\"37\" width=\"181.5\" height=\"22\"></rect> <text x=\"686.25\" y=\"52\">digits a-fA-F ∪ «_»</text></g></g> <path d=\"M777 48h20\"></path> <path d=\"M527 48a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g><path d=\"M547 78h12.75\"></path> <path d=\"M764.25 78h12.75\"></path> <g class=\"terminal\"><path d=\"M559.75 78h0\"></path> <path d=\"M588.25 78h0\"></path> <rect x=\"559.75\" y=\"67\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"574\" y=\"82\">o</text></g> <path d=\"M588.25 78h10\"></path> <path d=\"M598.25 78h10\"></path> <g class=\"non-terminal\"><path d=\"M608.25 78h0\"></path> <path d=\"M764.25 78h0\"></path> <rect x=\"608.25\" y=\"67\" width=\"156\" height=\"22\"></rect> <text x=\"686.25\" y=\"82\">digits 0-7 ∪ «_»</text></g></g> <path d=\"M777 78a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M527 48a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g><path d=\"M547 108h12.75\"></path> <path d=\"M764.25 108h12.75\"></path> <g class=\"terminal\"><path d=\"M559.75 108h0\"></path> <path d=\"M588.25 108h0\"></path> <rect x=\"559.75\" y=\"97\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"574\" y=\"112\">b</text></g> <path d=\"M588.25 108h10\"></path> <path d=\"M598.25 108h10\"></path> <g class=\"non-terminal\"><path d=\"M608.25 108h0\"></path> <path d=\"M764.25 108h0\"></path> <rect x=\"608.25\" y=\"97\" width=\"156\" height=\"22\"></rect> <text x=\"686.25\" y=\"112\">digits 0-1 ∪ «_»</text></g></g> <path d=\"M777 108a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path></g></g> <path d=\"M807 48h10\"></path></g> <path d=\"M 817 48 h 20 m -10 -10 v 20 m 10 -20 v 20\"></path></g></svg></div><div><h3>comment</h3> <p>Comments work as in JavaScript. \nSingle line comments can also be introduced with «#». Balanced comments can nest.</p> <svg width=\"426.5\" height=\"122\" viewBox=\"0 0 426.5 122\" class=\"railroad-diagram\"><g transform=\"translate(.5 .5)\"><g><path d=\"M20 21v20m10 -20v20m-10 -10h20\"></path></g> <g><path d=\"M40 31h0\"></path> <path d=\"M386.5 31h0\"></path> <path d=\"M40 31h20\"></path> <g><path d=\"M60 31h18.25\"></path> <path d=\"M348.25 31h18.25\"></path> <g class=\"terminal\"><path d=\"M78.25 31h0\"></path> <path d=\"M115.25 31h0\"></path> <rect x=\"78.25\" y=\"20\" width=\"37\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"96.75\" y=\"35\">/*</text></g> <path d=\"M115.25 31h10\"></path> <path d=\"M125.25 31h10\"></path> <g class=\"non-terminal\"><path d=\"M135.25 31h0\"></path> <path d=\"M291.25 31h0\"></path> <rect x=\"135.25\" y=\"20\" width=\"156\" height=\"22\"></rect> <text x=\"213.25\" y=\"35\">all-chars ∖ &quot;*/&quot;</text></g> <path d=\"M291.25 31h10\"></path> <path d=\"M301.25 31h10\"></path> <g class=\"terminal\"><path d=\"M311.25 31h0\"></path> <path d=\"M348.25 31h0\"></path> <rect x=\"311.25\" y=\"20\" width=\"37\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"329.75\" y=\"35\">*/</text></g></g> <path d=\"M366.5 31h20\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v10a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 61h0\"></path> <path d=\"M366.5 61h0\"></path> <g class=\"terminal\"><path d=\"M60 61h0\"></path> <path d=\"M97 61h0\"></path> <rect x=\"60\" y=\"50\" width=\"37\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"78.5\" y=\"65\">//</text></g> <path d=\"M97 61h10\"></path> <path d=\"M107 61h10\"></path> <g class=\"non-terminal\"><path d=\"M117 61h0\"></path> <path d=\"M366.5 61h0\"></path> <rect x=\"117\" y=\"50\" width=\"249.5\" height=\"22\"></rect> <text x=\"241.75\" y=\"65\">all-chars ∖ (&quot;//&quot; ∪ «\\r\\n»)</text></g></g> <path d=\"M366.5 61a10 10 0 0 0 10 -10v-10a10 10 0 0 1 10 -10\"></path> <path d=\"M40 31a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10\"></path> <g><path d=\"M60 91h8.5\"></path> <path d=\"M358 91h8.5\"></path> <g class=\"terminal\"><path d=\"M68.5 91h0\"></path> <path d=\"M97 91h0\"></path> <rect x=\"68.5\" y=\"80\" width=\"28.5\" height=\"22\" rx=\"10\" ry=\"10\"></rect> <text x=\"82.75\" y=\"95\">#</text></g> <path d=\"M97 91h10\"></path> <path d=\"M107 91h10\"></path> <g class=\"non-terminal\"><path d=\"M117 91h0\"></path> <path d=\"M358 91h0\"></path> <rect x=\"117\" y=\"80\" width=\"241\" height=\"22\"></rect> <text x=\"237.5\" y=\"95\">all-chars ∖ (&quot;#&quot; ∪ «\\r\\n»)</text></g></g> <path d=\"M366.5 91a10 10 0 0 0 10 -10v-40a10 10 0 0 1 10 -10\"></path></g> <path d=\"M 386.5 31 h 20 m -10 -10 v 20 m 10 -20 v 20\"></path></g></svg></div></div></div> <footer class=\"page-edit\"><!----> <!----></footer> <div class=\"page-nav\"><p class=\"inner\"><span class=\"prev\">\n      ←\n      <a href=\"/ref/options.html\" class=\"prev\">\n        Options\n      </a></span> <!----></p></div> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/28.6e0fa7bc.js\" defer></script><script src=\"/assets/js/3.6ce1235a.js\" defer></script><script src=\"/assets/js/7.6be3acca.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/tutorial/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Tutorials | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/29.77b8e3b6.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container no-sidebar\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <!----> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"tutorials\"><a href=\"#tutorials\" class=\"header-anchor\">#</a> Tutorials</h1> <ul><li><a href=\"/tutorial/write-a-plugin\">Writing a plugin</a> <ul><li>Short desc</li></ul></li> <li><a href=\"/tutorial/parsing-csv\">Parsing CSV</a> <ul><li>Short desc</li></ul></li></ul></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/29.77b8e3b6.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/tutorial/parsing-csv.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Parsing CSV | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/30.69b1b865.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container no-sidebar\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <!----> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"parsing-csv\"><a href=\"#parsing-csv\" class=\"header-anchor\">#</a> Parsing CSV</h1></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/30.69b1b865.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/tutorial/write-a-parser.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/31.c68f976d.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/32.0a08a140.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container no-sidebar\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <!----> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><p>@jsonic/expr</p></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/31.c68f976d.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/tutorial/write-a-plugin.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <title>Write a plugin | Jsonic</title>\n    <meta name=\"generator\" content=\"VuePress 1.9.8\">\n    <link rel=\"stylesheet\" href=\"/railroad-diagrams.css\">\n    <meta name=\"description\" content=\"A JSON parser for Node.js that isn&#39;t strict.\">\n    \n    <link rel=\"preload\" href=\"/assets/css/0.styles.610e9dca.css\" as=\"style\"><link rel=\"preload\" href=\"/assets/js/app.0c621e62.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/2.34930047.js\" as=\"script\"><link rel=\"preload\" href=\"/assets/js/32.0a08a140.js\" as=\"script\"><link rel=\"prefetch\" href=\"/assets/js/10.88d9a57b.js\"><link rel=\"prefetch\" href=\"/assets/js/11.7988a5fa.js\"><link rel=\"prefetch\" href=\"/assets/js/12.d9f3d941.js\"><link rel=\"prefetch\" href=\"/assets/js/13.775f91ca.js\"><link rel=\"prefetch\" href=\"/assets/js/14.f56c2700.js\"><link rel=\"prefetch\" href=\"/assets/js/15.00058088.js\"><link rel=\"prefetch\" href=\"/assets/js/16.70a6eea0.js\"><link rel=\"prefetch\" href=\"/assets/js/17.0d1f36b1.js\"><link rel=\"prefetch\" href=\"/assets/js/18.0f184e32.js\"><link rel=\"prefetch\" href=\"/assets/js/19.57ad231b.js\"><link rel=\"prefetch\" href=\"/assets/js/20.58c8b075.js\"><link rel=\"prefetch\" href=\"/assets/js/21.0c46ffa9.js\"><link rel=\"prefetch\" href=\"/assets/js/22.965cbc0d.js\"><link rel=\"prefetch\" href=\"/assets/js/23.18c270f0.js\"><link rel=\"prefetch\" href=\"/assets/js/24.5895d76a.js\"><link rel=\"prefetch\" href=\"/assets/js/25.a347f1d6.js\"><link rel=\"prefetch\" href=\"/assets/js/26.c7b8345d.js\"><link rel=\"prefetch\" href=\"/assets/js/27.4e6f90b7.js\"><link rel=\"prefetch\" href=\"/assets/js/28.6e0fa7bc.js\"><link rel=\"prefetch\" href=\"/assets/js/29.77b8e3b6.js\"><link rel=\"prefetch\" href=\"/assets/js/3.6ce1235a.js\"><link rel=\"prefetch\" href=\"/assets/js/30.69b1b865.js\"><link rel=\"prefetch\" href=\"/assets/js/31.c68f976d.js\"><link rel=\"prefetch\" href=\"/assets/js/4.064b2ac3.js\"><link rel=\"prefetch\" href=\"/assets/js/5.46815478.js\"><link rel=\"prefetch\" href=\"/assets/js/6.f03d59ff.js\"><link rel=\"prefetch\" href=\"/assets/js/7.6be3acca.js\"><link rel=\"prefetch\" href=\"/assets/js/8.0a081a71.js\"><link rel=\"prefetch\" href=\"/assets/js/9.9b6e31d5.js\">\n    <link rel=\"stylesheet\" href=\"/assets/css/0.styles.610e9dca.css\">\n  </head>\n  <body>\n    <div id=\"app\" data-server-rendered=\"true\"><div class=\"theme-container no-sidebar\"><header class=\"navbar\"><div class=\"sidebar-button\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" role=\"img\" viewBox=\"0 0 448 512\" class=\"icon\"><path fill=\"currentColor\" d=\"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z\"></path></svg></div> <a href=\"/\" class=\"home-link router-link-active\"><!----> <span class=\"site-name\">Jsonic</span></a> <div class=\"links\"><div class=\"search-box\"><input aria-label=\"Search\" autocomplete=\"off\" spellcheck=\"false\" value=\"\"> <!----></div> <nav class=\"nav-links can-hide\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class=\"sidebar-mask\"></div> <aside class=\"sidebar\"><nav class=\"nav-links\"><div class=\"nav-item\"><a href=\"/\" class=\"nav-link\">\n  Home\n</a></div><div class=\"nav-item\"><a href=\"/guide/\" class=\"nav-link\">\n  Guide\n</a></div><div class=\"nav-item\"><a href=\"/ref/\" class=\"nav-link\">\n  Reference\n</a></div><div class=\"nav-item\"><a href=\"/plugin/\" class=\"nav-link\">\n  Plugins\n</a></div><div class=\"nav-item\"><a href=\"/community/\" class=\"nav-link\">\n  Community\n</a></div><div class=\"nav-item\"><a href=\"https://github.com/jsonicjs/jsonic\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"nav-link external\">\n  Github\n  <span><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\" x=\"0px\" y=\"0px\" viewBox=\"0 0 100 100\" width=\"15\" height=\"15\" class=\"icon outbound\"><path fill=\"currentColor\" d=\"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z\"></path> <polygon fill=\"currentColor\" points=\"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9\"></polygon></svg> <span class=\"sr-only\">(opens new window)</span></span></a></div> <!----></nav>  <!----> </aside> <main class=\"page\"> <div class=\"theme-default-content content__default\"><h1 id=\"write-a-plugin\"><a href=\"#write-a-plugin\" class=\"header-anchor\">#</a> Write a plugin</h1></div> <footer class=\"page-edit\"><!----> <!----></footer> <!----> </main></div><div class=\"global-ui\"></div></div>\n    <script src=\"/assets/js/app.0c621e62.js\" defer></script><script src=\"/assets/js/2.34930047.js\" defer></script><script src=\"/assets/js/32.0a08a140.js\" defer></script>\n  </body>\n</html>\n"
  },
  {
    "path": "go/README.md",
    "content": "# jsonic (Go)\n\nVersion: 0.1.22\n\nA Go port of [jsonic](https://github.com/jsonicjs/jsonic), the lenient\nJSON parser. Same architecture, same syntax, same results. If you\nalready use jsonic in TypeScript, you know what this does. If you don't,\nread on.\n\njsonic accepts all standard JSON -- and then goes further. Unquoted\nkeys, implicit objects, comments, trailing commas, single-quoted\nstrings, multiline strings, path diving, and more. It parses what you\nmeant, not just what you typed.\n\n## Install\n\n```bash\ngo get github.com/jsonicjs/jsonic/go@latest\n```\n\n## Quick Example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/jsonicjs/jsonic/go\"\n)\n\nfunc main() {\n    result, err := jsonic.Parse(\"a:1, b:2\")\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(result) // map[a:1 b:2]\n}\n```\n\nThat's it. No schema, no struct tags, no ceremony.\n\n## Configured Instance\n\nYou don't have to accept the defaults. `Make` gives you a configured\nparser instance with whatever behavior you need:\n\n```go\nfunc boolp(b bool) *bool { return &b }\n\nj := jsonic.Make(jsonic.Options{\n    Number: &jsonic.NumberOptions{Lex: boolp(false)},\n})\n\nresult, err := j.Parse(\"a:1, b:2\")\n// {\"a\": \"1\", \"b\": \"2\"} — numbers are kept as strings\n```\n\nOptions compose. Turn things off, turn things on. You can always change\nit later.\n\n## Syntax\n\njsonic accepts all standard JSON plus the relaxations listed in the\n[syntax reference](doc/syntax.md). Here are the highlights:\n\n- **Unquoted keys**: `a:1` &rarr; `{\"a\": 1}`\n- **Implicit objects**: `a:1,b:2` &rarr; `{\"a\": 1, \"b\": 2}`\n- **Implicit arrays**: `a,b,c` &rarr; `[\"a\", \"b\", \"c\"]`\n- **Comments**: `#`, `//`, `/* */`\n- **Single/backtick quotes**: `'hello'`, `` `hello` ``\n- **Path diving**: `a:b:1` &rarr; `{\"a\": {\"b\": 1}}`\n- **Trailing commas**: `{a:1,}` &rarr; `{\"a\": 1}`\n- **All number formats**: hex, octal, binary, separators\n\n## Documentation\n\n- [API Reference](doc/api.md) -- types, functions, and methods\n- [Syntax Reference](doc/syntax.md) -- all supported syntax\n- [Options Reference](doc/options.md) -- configuration options\n- [Plugin Guide](doc/plugins.md) -- writing plugins\n- [Differences from TypeScript](doc/differences.md) -- what to know if you use both\n\n## License\n\nMIT. Copyright (c) Richard Rodger.\n"
  },
  {
    "path": "go/alignment_test.go",
    "content": "package jsonic\n\n// Alignment tests validate that Go behavior matches the authoritative TypeScript\n// implementation. Tests are split into two categories:\n//\n// 1. Shared TSV tests (alignment-*.tsv) - input/expected pairs that both TS and\n//    Go runners validate, ensuring identical parse results.\n//\n// 2. Direct Go tests - for features requiring custom options, error checking, or\n//    Go-specific APIs that cannot be expressed in a simple TSV format.\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// =====================================================================\n// Shared TSV tests (also run by TS in test/alignment.test.js)\n// =====================================================================\n\n// TestAlignmentValues tests that NaN, Infinity, etc. are parsed as text, not\n// as special float values. TS has never had these in default value.def.\nfunc TestAlignmentValues(t *testing.T) {\n\trunParserTSV(t, \"alignment-values.tsv\", Make())\n}\n\n// TestAlignmentSafeKey tests that __proto__ and constructor keys are blocked\n// when safe.key is true (the default).\nfunc TestAlignmentSafeKey(t *testing.T) {\n\trunParserTSV(t, \"alignment-safe-key.tsv\", Make())\n}\n\n// TestAlignmentMapMerge tests duplicate-key deep merge behavior with default\n// map.extend=true setting.\nfunc TestAlignmentMapMerge(t *testing.T) {\n\trunParserTSV(t, \"alignment-map-merge.tsv\", Make())\n}\n\n// TestAlignmentNumberText tests that number-like strings followed by text\n// characters are parsed as text (e.g. \"1a\" -> \"1a\").\nfunc TestAlignmentNumberText(t *testing.T) {\n\trunParserTSV(t, \"alignment-number-text.tsv\", Make())\n}\n\n// TestAlignmentStructure tests auto-close behavior for unclosed structures.\nfunc TestAlignmentStructure(t *testing.T) {\n\trunParserTSV(t, \"alignment-structure.tsv\", Make())\n}\n\n// TestAlignmentEmpty tests that empty/comment-only inputs return null.\nfunc TestAlignmentEmpty(t *testing.T) {\n\tpath := filepath.Join(specDir(), \"alignment-empty.tsv\")\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load alignment-empty.tsv: %v\", err)\n\t}\n\n\tj := Make()\n\tfor _, row := range rows {\n\t\tif len(row.cols) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tinput := preprocessEscapes(row.cols[0])\n\t\texpectedStr := row.cols[1]\n\n\t\t// \"null\" in the expected column means nil result.\n\t\tif expectedStr != \"null\" {\n\t\t\tt.Errorf(\"line %d: unexpected expected value %q (only null supported)\", row.lineNo, expectedStr)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot, err := j.Parse(input)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error: %v\", row.lineNo, input, err)\n\t\t\tcontinue\n\t\t}\n\t\tgotPlain := stripRefs(got)\n\t\tif gotPlain != nil {\n\t\t\tt.Errorf(\"line %d: Parse(%q) got %s, expected nil\",\n\t\t\t\trow.lineNo, input, formatValue(gotPlain))\n\t\t}\n\t}\n}\n\n// TestAlignmentErrors tests that specific inputs produce parse errors.\n// TSV format: input<TAB>ERROR:<code>\nfunc TestAlignmentErrors(t *testing.T) {\n\tpath := filepath.Join(specDir(), \"alignment-errors.tsv\")\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load alignment-errors.tsv: %v\", err)\n\t}\n\n\tj := Make()\n\tfor _, row := range rows {\n\t\tif len(row.cols) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tinput := preprocessEscapes(row.cols[0])\n\t\texpectedStr := row.cols[1]\n\n\t\tif !strings.HasPrefix(expectedStr, \"ERROR:\") {\n\t\t\tt.Errorf(\"line %d: expected column must start with ERROR:, got %q\", row.lineNo, expectedStr)\n\t\t\tcontinue\n\t\t}\n\t\texpectedCode := strings.TrimPrefix(expectedStr, \"ERROR:\")\n\n\t\t_, parseErr := j.Parse(input)\n\t\tif parseErr == nil {\n\t\t\tt.Errorf(\"line %d: Parse(%q) should have returned error (expected %s), got nil\",\n\t\t\t\trow.lineNo, input, expectedCode)\n\t\t\tcontinue\n\t\t}\n\t\tje, ok := parseErr.(*JsonicError)\n\t\tif !ok {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error should be *JsonicError, got %T: %v\",\n\t\t\t\trow.lineNo, input, parseErr, parseErr)\n\t\t\tcontinue\n\t\t}\n\t\tif je.Code != expectedCode {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error code got %q, want %q\",\n\t\t\t\trow.lineNo, input, je.Code, expectedCode)\n\t\t}\n\t}\n}\n\n// --- Error TSV runner (2-column: input, ERROR:<code>) ---\n\nfunc runErrorTSV(t *testing.T, file string, j *Jsonic) {\n\tt.Helper()\n\tpath := filepath.Join(specDir(), file)\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load %s: %v\", file, err)\n\t}\n\n\tfor _, row := range rows {\n\t\tif len(row.cols) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tinput := preprocessEscapes(row.cols[0])\n\t\texpectedStr := row.cols[1]\n\n\t\tif !strings.HasPrefix(expectedStr, \"ERROR:\") {\n\t\t\tt.Errorf(\"line %d: expected must start with ERROR:, got %q\", row.lineNo, expectedStr)\n\t\t\tcontinue\n\t\t}\n\t\texpectedCode := strings.TrimPrefix(expectedStr, \"ERROR:\")\n\n\t\t_, parseErr := j.Parse(input)\n\t\tif parseErr == nil {\n\t\t\tt.Errorf(\"line %d: Parse(%q) should error (want %s), got nil\", row.lineNo, input, expectedCode)\n\t\t\tcontinue\n\t\t}\n\t\tje, ok := parseErr.(*JsonicError)\n\t\tif !ok {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error should be *JsonicError, got %T\", row.lineNo, input, parseErr)\n\t\t\tcontinue\n\t\t}\n\t\tif je.Code != expectedCode {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error code got %q, want %q\", row.lineNo, input, je.Code, expectedCode)\n\t\t}\n\t}\n}\n\n// --- Lex error propagation tests ---\n// Verifies that lex-level errors are not masked by generic \"unexpected\"\n// in any parser state.\n\nfunc TestLexErrorsDefault(t *testing.T) {\n\trunErrorTSV(t, \"lex-errors.tsv\", Make())\n}\n\nfunc TestLexErrorsExcludeJsonicImp(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"jsonic,imp\"}})\n\trunErrorTSV(t, \"lex-errors.tsv\", j)\n}\n\nfunc TestLexErrorsExcludeJsonicImpComma(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"jsonic,imp,comma\"}})\n\trunErrorTSV(t, \"lex-errors.tsv\", j)\n}\n\n// --- Exclude group TSV tests ---\n\nfunc TestExcludeStrictJSON(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"jsonic,imp\"}})\n\trunParserTSV(t, \"exclude-strict-json.tsv\", j)\n}\n\nfunc TestExcludeStrictJSONErrors(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"jsonic,imp\"}})\n\trunErrorTSV(t, \"exclude-strict-json-errors.tsv\", j)\n}\n\nfunc TestExcludeComma(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"comma\"}})\n\trunParserTSV(t, \"exclude-comma.tsv\", j)\n}\n\nfunc TestExcludeCommaErrors(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"comma\"}})\n\trunErrorTSV(t, \"exclude-comma-errors.tsv\", j)\n}\n\n// --- Include group TSV tests (parity for rule.include) ---\n\n// TestIncludeJSON runs the shared include-json.tsv to confirm that\n// include=\"json\" produces the same strict-JSON surface as the\n// TypeScript runner.\nfunc TestIncludeJSON(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Include: \"json\"}})\n\trunParserTSV(t, \"include-json.tsv\", j)\n}\n\nfunc TestIncludeJSONErrors(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Include: \"json\"}})\n\trunErrorTSV(t, \"include-json-errors.tsv\", j)\n}\n\n// --- Comment suffix TSV tests (parity for comment.def.suffix) ---\n\n// TestFeatureCommentSuffixLine exercises the shared comment-suffix-line\n// TSV with a hash line-comment that terminates at a custom '@@' suffix.\nfunc TestFeatureCommentSuffixLine(t *testing.T) {\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\":  {Line: true, Start: \"#\", Lex: &yes, Suffix: \"@@\"},\n\t\t\t\"line\":  {Line: true, Start: \"//\", Lex: &yes},\n\t\t\t\"block\": {Line: false, Start: \"/*\", End: \"*/\", Lex: &yes},\n\t\t},\n\t}})\n\trunParserTSV(t, \"feature-comment-suffix-line.tsv\", j)\n}\n\n// TestFeatureCommentSuffixBlock exercises the shared\n// comment-suffix-block TSV with a /* */ block comment that also\n// accepts a '!!' suffix to terminate early.\nfunc TestFeatureCommentSuffixBlock(t *testing.T) {\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\":  {Line: true, Start: \"#\", Lex: &yes},\n\t\t\t\"line\":  {Line: true, Start: \"//\", Lex: &yes},\n\t\t\t\"block\": {Line: false, Start: \"/*\", End: \"*/\", Lex: &yes, Suffix: \"!!\"},\n\t\t},\n\t}})\n\trunParserTSV(t, \"feature-comment-suffix-block.tsv\", j)\n}\n\n// =====================================================================\n// Direct Go tests for option-dependent alignment features\n// =====================================================================\n\n// --- Map merge: extend=false (overwrite, no deep merge) ---\n\nfunc TestAlignmentMapExtendFalse(t *testing.T) {\n\tj := Make(Options{Map: &MapOptions{Extend: boolPtr(false)}})\n\n\t// With extend=false, duplicate keys overwrite (no deep merge).\n\tgot, err := j.Parse(\"{a:{b:1},a:{c:2}}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", m(\"c\", 2.0))\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"map.extend=false: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\n// --- Map merge: custom merge function ---\n\nfunc TestAlignmentMapMergeFunc(t *testing.T) {\n\t// Custom merge: always keep the previous value (ignore new).\n\tj := Make(Options{Map: &MapOptions{\n\t\tMerge: func(prev, val any, r *Rule, ctx *Context) any {\n\t\t\treturn prev // always keep prev\n\t\t},\n\t}})\n\n\tgot, err := j.Parse(\"{a:1,a:2}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", 1.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"map.merge func: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\n// --- safe.key on arrays: __proto__ blocked in lists ---\n\nfunc TestAlignmentSafeKeyArray(t *testing.T) {\n\tj := Make() // safe.key=true by default\n\n\t// On objects: __proto__ is allowed (Go maps have no prototypes,\n\t// same as TS's Object.create(null)).\n\tgot, err := j.Parse(\"{__proto__:1,a:2}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"__proto__\", 1.0, \"a\", 2.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"safe.key on object: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\t// On arrays with list.property: __proto__ key is blocked.\n\t// [1,2,__proto__:x] should produce [1,2] with __proto__ pair dropped.\n\tj2 := Make(Options{List: &ListOptions{Property: boolPtr(true)}})\n\tgot2, err2 := j2.Parse(\"[1,2,__proto__:3]\")\n\tif err2 != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err2)\n\t}\n\texpected2 := a(1.0, 2.0)\n\tif !valuesEqual(stripRefs(got2), expected2) {\n\t\tt.Errorf(\"safe.key on array: got %s, want %s\", formatValue(stripRefs(got2)), formatValue(expected2))\n\t}\n}\n\n// --- safe.key=false allows __proto__ on arrays ---\n\nfunc TestAlignmentSafeKeyFalse(t *testing.T) {\n\tj := Make(Options{Safe: &SafeOptions{Key: boolPtr(false)}})\n\n\tgot, err := j.Parse(\"{__proto__:1,a:2}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"__proto__\", 1.0, \"a\", 2.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"safe.key=false: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\n// --- String escape errors produce error tokens ---\n\nfunc TestAlignmentStringEscapeErrors(t *testing.T) {\n\tj := Make(Options{String: &StringOptions{AllowUnknown: boolPtr(false)}})\n\n\t// With allowUnknown=false, unknown escape like \\w should error.\n\t_, err := j.Parse(`\"\\w\"`)\n\tif err == nil {\n\t\tt.Error(`Parse(\"\\\\w\") with allowUnknown=false should error`)\n\t}\n}\n\n// --- String abandon: fallthrough on error ---\n\nfunc TestAlignmentStringAbandon(t *testing.T) {\n\tj := Make(Options{String: &StringOptions{Abandon: boolPtr(true)}})\n\n\t// With abandon=true, an unterminated string returns nil from matcher,\n\t// allowing subsequent matchers to try. This means the quote char becomes\n\t// part of text or causes a different error.\n\t// In TS: string.abandon=true means the string matcher returns undefined on\n\t// failure, falling through to subsequent matchers.\n\t// The input `\"abc` with abandon starts with `\"` which fails as string,\n\t// then text matcher picks up `\"abc` as text.\n\tgot, err := j.Parse(`\"abc`)\n\tif err != nil {\n\t\t// With abandon, the string matcher falls through. The quote char\n\t\t// may still cause an error depending on what other matchers do,\n\t\t// but it should NOT be \"unterminated_string\".\n\t\tif je, ok := err.(*JsonicError); ok && je.Code == \"unterminated_string\" {\n\t\t\tt.Errorf(\"string.abandon=true should not produce unterminated_string error, got: %v\", err)\n\t\t}\n\t}\n\t// If no error, the input was parsed as text (which is the TS behavior).\n\t_ = got\n}\n\n// --- String replace: character replacement during scanning ---\n\nfunc TestAlignmentStringReplace(t *testing.T) {\n\tj := Make(Options{String: &StringOptions{\n\t\tReplace: map[rune]string{\n\t\t\t'A': \"B\",\n\t\t\t'D': \"\",\n\t\t},\n\t}})\n\n\tgot, err := j.Parse(`\"aAc\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"aBc\" {\n\t\tt.Errorf(`string.replace: Parse(\"aAc\") got %q, want \"aBc\"`, got)\n\t}\n\n\tgot, err = j.Parse(`\"aAcDe\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"aBce\" {\n\t\tt.Errorf(`string.replace: Parse(\"aAcDe\") got %q, want \"aBce\"`, got)\n\t}\n}\n\n// --- Number exclude: reject certain number-like strings ---\n\nfunc TestAlignmentNumberExclude(t *testing.T) {\n\tj := Make(Options{Number: &NumberOptions{\n\t\tExclude: func(s string) bool {\n\t\t\t// Exclude numbers starting with \"00\"\n\t\t\treturn strings.HasPrefix(s, \"00\")\n\t\t},\n\t}})\n\n\t// \"0099\" matches exclude pattern, so it's parsed as text.\n\tgot, err := j.Parse(\"0099\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"0099\" {\n\t\tt.Errorf(`number.exclude: Parse(\"0099\") got %v (%T), want string \"0099\"`, got, got)\n\t}\n\n\t// \"99\" does not match exclude, so it's still a number.\n\tgot, err = j.Parse(\"99\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != 99.0 {\n\t\tt.Errorf(`number.exclude: Parse(\"99\") got %v (%T), want 99.0`, got, got)\n\t}\n}\n\n// --- Line single: separate token per newline ---\n\nfunc TestAlignmentLineSingle(t *testing.T) {\n\t// line.single primarily affects lexer behavior. We test via parsing since\n\t// multiple newlines between values should still parse correctly.\n\tj := Make(Options{Line: &LineOptions{Single: boolPtr(true)}})\n\n\tgot, err := j.Parse(\"a\\n\\nb\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := a(\"a\", \"b\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"line.single: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\n// --- Comment eatline: consume trailing line chars ---\n\nfunc TestAlignmentCommentEatLine(t *testing.T) {\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", EatLine: boolPtr(true)},\n\t\t\t\"line\": {Line: true, Start: \"//\"},\n\t\t\t\"block\": {Line: false, Start: \"/*\", End: \"*/\"},\n\t\t},\n\t}})\n\n\t// With eatline, the comment consumes the trailing newline too.\n\t// \"a#x\\nb\" - # comment eats the \\n, so b follows directly.\n\tgot, err := j.Parse(\"a:1#x\\nb:2\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", 1.0, \"b\", 2.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"comment.eatline: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\n// --- Text modify: transform text values ---\n\nfunc TestAlignmentTextModify(t *testing.T) {\n\tj := Make(Options{Text: &TextOptions{\n\t\tModify: []ValModifier{\n\t\t\tfunc(val any) any {\n\t\t\t\tif s, ok := val.(string); ok {\n\t\t\t\t\treturn strings.ToUpper(s)\n\t\t\t\t}\n\t\t\t\treturn val\n\t\t\t},\n\t\t},\n\t}})\n\n\tgot, err := j.Parse(\"hello\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"HELLO\" {\n\t\tt.Errorf(`text.modify: Parse(\"hello\") got %q, want \"HELLO\"`, got)\n\t}\n\n\t// Quoted strings are NOT affected by text.modify (only unquoted text).\n\tgot, err = j.Parse(`\"hello\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"hello\" {\n\t\tt.Errorf(`text.modify: Parse('\"hello\"') got %q, want \"hello\"`, got)\n\t}\n}\n\n// --- list.property guard: error when disabled ---\n\nfunc TestAlignmentListPropertyGuard(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{\n\t\tProperty: boolPtr(false),\n\t\tPair:     boolPtr(false),\n\t}})\n\n\t// [a:1] should error when list.property and list.pair are both false.\n\t_, err := j.Parse(\"[a:1]\")\n\tif err == nil {\n\t\tt.Error(\"Parse(\\\"[a:1]\\\") with list.property=false should return error\")\n\t}\n}\n\n// --- Exclude (alt.g tags): strict JSON mode ---\n\n// TestAlignmentGrammarGTags validates that every grammar alt G-tag in Go\n// matches the authoritative TypeScript grammar (dist/grammar.js).\n// The TS grammar builds rules in two phases (JSON base + jsonic extensions)\n// with append/prepend/delete/move operations. The final alt order and tags\n// must be identical.\nfunc TestAlignmentGrammarGTags(t *testing.T) {\n\tj := Make()\n\n\t// Expected G-tags per rule, per state, in order.\n\t// Source of truth: TS dist/grammar.js after JSON + jsonic extension phases.\n\ttype ruleGTags struct {\n\t\tname  string\n\t\topen  []string\n\t\tclose []string\n\t}\n\n\texpected := []ruleGTags{\n\t\t{\n\t\t\tname: \"val\",\n\t\t\topen: []string{\n\t\t\t\t\"map,json\",                  // #OB -> map\n\t\t\t\t\"list,json\",                 // #OS -> list\n\t\t\t\t\"pair,jsonic,top\",           // #KEY #CL d=0 -> map\n\t\t\t\t\"pair,jsonic\",               // #KEY #CL -> map (dive)\n\t\t\t\t\"val,json\",                  // #VAL\n\t\t\t\t\"val,imp,null,jsonic\",       // #CB|#CS impl null\n\t\t\t\t\"list,imp,jsonic\",           // #CA d=0 -> list\n\t\t\t\t\"list,val,imp,null,jsonic\",  // #CA impl null\n\t\t\t\t\"jsonic\",                    // #ZZ\n\t\t\t},\n\t\t\tclose: []string{\n\t\t\t\t\"end,json\",                  // #ZZ end\n\t\t\t\t\"val,json,close\",            // #CB|#CS close error\n\t\t\t\t\"list,val,imp,comma,jsonic\", // #CA -> list (comma)\n\t\t\t\t\"list,val,imp,space,jsonic\", // imp list (space)\n\t\t\t\t\"jsonic\",                    // #ZZ jsonic\n\t\t\t\t\"more,json\",                 // b:1 more\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"map\",\n\t\t\topen: []string{\n\t\t\t\t\"end,jsonic\",              // #OB #ZZ autoclose\n\t\t\t\t\"map,json\",                // #OB #CB empty\n\t\t\t\t\"map,json,pair\",           // #OB -> pair\n\t\t\t\t\"pair,list,val,imp,jsonic\", // #KEY #CL -> pair (imp)\n\t\t\t},\n\t\t\tclose: []string{\n\t\t\t\t\"end,json\",        // #CB n.pk<=0\n\t\t\t\t\"path,jsonic\",     // #CB path dive\n\t\t\t\t\"end,path,jsonic\", // #CA|#CS|#VAL end path\n\t\t\t\t\"end,jsonic\",      // #ZZ autoclose\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"list\",\n\t\t\topen: []string{\n\t\t\t\t\"\",                          // implist cond (no tag)\n\t\t\t\t\"list,json\",                 // #OS #CS empty\n\t\t\t\t\"list,elem,json\",            // #OS -> elem\n\t\t\t\t\"list,elem,val,imp,jsonic\",  // #CA -> elem (imp)\n\t\t\t\t\"list,elem,jsonic\",          // -> elem default\n\t\t\t},\n\t\t\tclose: []string{\n\t\t\t\t\"end,json\",   // #CS end\n\t\t\t\t\"end,jsonic\", // #ZZ autoclose\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"pair\",\n\t\t\topen: []string{\n\t\t\t\t\"map,pair,key,json\",    // #KEY #CL -> val\n\t\t\t\t\"map,pair,comma,jsonic\", // #CA comma\n\t\t\t},\n\t\t\tclose: []string{\n\t\t\t\t\"map,pair,json\",             // #CB n.pk<=0\n\t\t\t\t\"map,pair,comma,jsonic\",     // #CA #CB trailing comma\n\t\t\t\t\"end,jsonic\",                // #CA #ZZ end\n\t\t\t\t\"map,pair,json\",             // #CA n.pk<=0 -> pair\n\t\t\t\t\"map,pair,jsonic\",           // #CA n.dmap<=1 -> pair\n\t\t\t\t\"map,pair,imp,jsonic\",       // #KEY n.dmap<=1 -> pair\n\t\t\t\t\"map,pair,imp,path,jsonic\",  // #CB|#CA|#CS|#KEY n.pk>0 path\n\t\t\t\t\"end,jsonic\",                // #CS error\n\t\t\t\t\"map,pair,json\",             // #ZZ @finish\n\t\t\t\t\"map,pair,imp,jsonic\",       // -> pair (catchall)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"elem\",\n\t\t\topen: []string{\n\t\t\t\t\"list,elem,imp,null,jsonic\", // #CA #CA double comma\n\t\t\t\t\"list,elem,imp,null,jsonic\", // #CA single comma\n\t\t\t\t\"elem,pair,jsonic\",          // #KEY #CL -> val (pair)\n\t\t\t\t// Note: ListChild alt (\"elem,child,jsonic\") only present when cfg.ListChild is true.\n\t\t\t\t\"list,elem,val,json\",        // -> val default\n\t\t\t},\n\t\t\tclose: []string{\n\t\t\t\t\"list,elem,comma,jsonic\", // #CA, #CS|#ZZ trailing comma\n\t\t\t\t\"list,elem,json\",         // #CA -> elem\n\t\t\t\t\"list,elem,json\",         // #CS end\n\t\t\t\t\"list,elem,json\",         // #ZZ @finish\n\t\t\t\t\"end,jsonic\",             // #CB error\n\t\t\t\t\"list,elem,imp,jsonic\",   // -> elem (catchall)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, exp := range expected {\n\t\trs, ok := j.parser.RSM[exp.name]\n\t\tif !ok {\n\t\t\tt.Errorf(\"rule %q not found in grammar\", exp.name)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check open alts.\n\t\tif len(rs.Open) != len(exp.open) {\n\t\t\tt.Errorf(\"rule %q open: got %d alts, want %d\", exp.name, len(rs.Open), len(exp.open))\n\t\t} else {\n\t\t\tfor i, alt := range rs.Open {\n\t\t\t\tif alt.G != exp.open[i] {\n\t\t\t\t\tt.Errorf(\"rule %q open[%d]: G=%q, want %q\", exp.name, i, alt.G, exp.open[i])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check close alts.\n\t\tif len(rs.Close) != len(exp.close) {\n\t\t\tt.Errorf(\"rule %q close: got %d alts, want %d\", exp.name, len(rs.Close), len(exp.close))\n\t\t} else {\n\t\t\tfor i, alt := range rs.Close {\n\t\t\t\tif alt.G != exp.close[i] {\n\t\t\t\t\tt.Errorf(\"rule %q close[%d]: G=%q, want %q\", exp.name, i, alt.G, exp.close[i])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestAlignmentExclude(t *testing.T) {\n\t// Exclude removes alternates tagged with matching group tags.\n\t// Verify the mechanism works by checking that alternates are removed.\n\tj := Make()\n\n\t// Count alternates before and after exclude.\n\tvalSpec := j.parser.RSM[\"val\"]\n\topenBefore := len(valSpec.Open)\n\tcloseBefore := len(valSpec.Close)\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Exclude: \"jsonic\"}})\n\n\topenAfter := len(valSpec.Open)\n\tcloseAfter := len(valSpec.Close)\n\n\t// After excluding \"jsonic\", there should be fewer alternates.\n\tif openAfter >= openBefore {\n\t\tt.Errorf(\"exclude: val.Open should have fewer alts after exclude, got %d >= %d\",\n\t\t\topenAfter, openBefore)\n\t}\n\tif closeAfter >= closeBefore {\n\t\tt.Errorf(\"exclude: val.Close should have fewer alts after exclude, got %d >= %d\",\n\t\t\tcloseAfter, closeBefore)\n\t}\n\n\t// Remaining alts should not contain the \"jsonic\" tag.\n\tfor _, alt := range valSpec.Open {\n\t\tfor _, tag := range strings.Split(alt.G, \",\") {\n\t\t\tif strings.TrimSpace(tag) == \"jsonic\" {\n\t\t\t\tt.Errorf(\"exclude: val.Open alt still has jsonic tag in %q\", alt.G)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, alt := range valSpec.Close {\n\t\tfor _, tag := range strings.Split(alt.G, \",\") {\n\t\t\tif strings.TrimSpace(tag) == \"jsonic\" {\n\t\t\t\tt.Errorf(\"exclude: val.Close alt still has jsonic tag in %q\", alt.G)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- result.fail: reject specific result values ---\n\nfunc TestAlignmentResultFail(t *testing.T) {\n\tj := Make(Options{Property: &PropertyOptions{\n\t\tConfigModify: map[string]ConfigModifier{\n\t\t\t\"result-fail\": func(cfg *LexConfig, opts *Options) {\n\t\t\t\tcfg.ResultFail = []any{\"FAIL\"}\n\t\t\t},\n\t\t},\n\t}})\n\n\t// A result matching a fail sentinel should cause an error.\n\t_, err := j.Parse(\"FAIL\")\n\tif err == nil {\n\t\tt.Error(\"Parse(\\\"FAIL\\\") with result.fail=[\\\"FAIL\\\"] should return error\")\n\t}\n\n\t// Normal values should still work.\n\tgot, err := j.Parse(\"OK\")\n\tif err != nil {\n\t\tt.Fatalf(\"Parse(\\\"OK\\\") unexpected error: %v\", err)\n\t}\n\tif got != \"OK\" {\n\t\tt.Errorf(\"Parse(\\\"OK\\\") got %q, want \\\"OK\\\"\", got)\n\t}\n}\n\n// --- parse.prepare: hooks before parsing ---\n\nfunc TestAlignmentParsePrepare(t *testing.T) {\n\tprepared := false\n\tj := Make(Options{Property: &PropertyOptions{\n\t\tConfigModify: map[string]ConfigModifier{\n\t\t\t\"prepare\": func(cfg *LexConfig, opts *Options) {\n\t\t\t\tcfg.ParsePrepare = append(cfg.ParsePrepare, func(ctx *Context) {\n\t\t\t\t\tprepared = true\n\t\t\t\t})\n\t\t\t},\n\t\t},\n\t}})\n\n\t_, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !prepared {\n\t\tt.Error(\"parse.prepare hook was not called\")\n\t}\n}\n\n// --- Empty source with empty=false ---\n\nfunc TestAlignmentEmptyDisabled(t *testing.T) {\n\tj := Make(Options{Lex: &LexOptions{Empty: boolPtr(false)}})\n\n\t_, err := j.Parse(\"\")\n\tif err == nil {\n\t\tt.Error(\"Parse(\\\"\\\") with lex.empty=false should return error\")\n\t}\n}\n\n// --- Custom value definitions ---\n\nfunc TestAlignmentCustomValues(t *testing.T) {\n\tj := Make(Options{Value: &ValueOptions{\n\t\tDef: map[string]*ValueDef{\n\t\t\t\"true\":  {Val: true},\n\t\t\t\"false\": {Val: false},\n\t\t\t\"null\":  {Val: nil},\n\t\t\t\"NaN\":   {Val: \"NaN-custom\"},\n\t\t},\n\t}})\n\n\tgot, err := j.Parse(\"NaN\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"NaN-custom\" {\n\t\tt.Errorf(`custom value: Parse(\"NaN\") got %v, want \"NaN-custom\"`, got)\n\t}\n\n\t// true/false/null still work.\n\tgot, err = j.Parse(\"true\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != true {\n\t\tt.Errorf(`custom value: Parse(\"true\") got %v, want true`, got)\n\t}\n}\n\n// --- Deep merge on Options structs (reflection path) ---\n\nfunc TestAlignmentDeepOptions(t *testing.T) {\n\t// Deep should work directly on Options structs, matching TS deep() on options objects.\n\tbase := Options{\n\t\tNumber:  &NumberOptions{Lex: boolPtr(true), Hex: boolPtr(true)},\n\t\tComment: &CommentOptions{Lex: boolPtr(true)},\n\t\tError:   map[string]string{\"a\": \"1\"},\n\t}\n\tover := Options{\n\t\tNumber: &NumberOptions{Hex: boolPtr(false)},\n\t\tError:  map[string]string{\"b\": \"2\"},\n\t\tTag:    \"test\",\n\t}\n\n\tresult := Deep(base, over)\n\tmerged, ok := result.(Options)\n\tif !ok {\n\t\tt.Fatalf(\"Deep(Options, Options) returned %T, want Options\", result)\n\t}\n\n\t// Number.Lex preserved from base (zero in over).\n\tif merged.Number == nil || merged.Number.Lex == nil || !*merged.Number.Lex {\n\t\tt.Error(\"expected Number.Lex to remain true\")\n\t}\n\t// Number.Hex overridden.\n\tif merged.Number.Hex == nil || *merged.Number.Hex {\n\t\tt.Error(\"expected Number.Hex to be false\")\n\t}\n\t// Comment preserved from base (nil in over).\n\tif merged.Comment == nil || merged.Comment.Lex == nil || !*merged.Comment.Lex {\n\t\tt.Error(\"expected Comment.Lex to remain true\")\n\t}\n\t// Error maps merged.\n\tif merged.Error[\"a\"] != \"1\" || merged.Error[\"b\"] != \"2\" {\n\t\tt.Errorf(\"expected Error map merged, got %v\", merged.Error)\n\t}\n\t// Tag from over.\n\tif merged.Tag != \"test\" {\n\t\tt.Errorf(\"expected Tag 'test', got %q\", merged.Tag)\n\t}\n}\n\nfunc TestAlignmentDeepOptionsPointer(t *testing.T) {\n\t// Deep should also handle pointer-to-struct fields correctly.\n\tbase := Options{\n\t\tString: &StringOptions{\n\t\t\tLex:      boolPtr(true),\n\t\t\tChars:    \"'\\\"\",\n\t\t\tEscape:   map[string]string{\"n\": \"\\n\"},\n\t\t\tAbandon:  boolPtr(false),\n\t\t},\n\t}\n\tover := Options{\n\t\tString: &StringOptions{\n\t\t\tEscape:  map[string]string{\"t\": \"\\t\"},\n\t\t\tAbandon: boolPtr(true),\n\t\t},\n\t}\n\n\tresult := Deep(base, over).(Options)\n\n\t// Lex preserved.\n\tif result.String.Lex == nil || !*result.String.Lex {\n\t\tt.Error(\"expected String.Lex true\")\n\t}\n\t// Chars preserved.\n\tif result.String.Chars != \"'\\\"\" {\n\t\tt.Errorf(\"expected String.Chars preserved, got %q\", result.String.Chars)\n\t}\n\t// Escape maps merged.\n\tif result.String.Escape[\"n\"] != \"\\n\" || result.String.Escape[\"t\"] != \"\\t\" {\n\t\tt.Errorf(\"expected Escape merged, got %v\", result.String.Escape)\n\t}\n\t// Abandon overridden.\n\tif result.String.Abandon == nil || !*result.String.Abandon {\n\t\tt.Error(\"expected String.Abandon true\")\n\t}\n}\n\n// --- Deep merge with Undefined sentinel ---\n\nfunc TestAlignmentDeepUndefined(t *testing.T) {\n\t// Undefined in the overlay should preserve the base value.\n\tbase := map[string]any{\"a\": 1.0, \"b\": 2.0}\n\tover := map[string]any{\"a\": Undefined, \"b\": 3.0}\n\n\tresult := Deep(base, over)\n\trm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"Deep() returned %T, want map[string]any\", result)\n\t}\n\n\t// \"a\" should be preserved from base (Undefined means \"don't override\").\n\tif rm[\"a\"] != 1.0 {\n\t\tt.Errorf(\"Deep() a: got %v, want 1.0\", rm[\"a\"])\n\t}\n\t// \"b\" should be overridden.\n\tif rm[\"b\"] != 3.0 {\n\t\tt.Errorf(\"Deep() b: got %v, want 3.0\", rm[\"b\"])\n\t}\n}\n\n// --- ModList: delete-move-filter order ---\n\nfunc TestAlignmentModListOrder(t *testing.T) {\n\t// Verify that delete happens before move (TS behavior).\n\t// Input: [A, B, C, D, E] with delete=[1] (B), move=[3,0] (move index 3→0)\n\t// After delete: [A, C, D, E] (indices shift)\n\t// After move: move old-index-3 (D in original, but after delete adjustment)\n\t// This tests the order sensitivity.\n\tinput := []any{\"A\", \"B\", \"C\", \"D\", \"E\"}\n\tresult := ModList(input, &ModListOpts{\n\t\tDelete: []int{1},\n\t\tMove:   []int{3, 0},\n\t})\n\n\t// After delete [1]: [A, C, D, E]\n\t// Move [3,0] moves the item at original-index-3 (D) to position 0.\n\t// In TS: delete marks, then move operates on original indices, then filter.\n\t// Expected: [D, A, C, E]\n\texpected := []any{\"D\", \"A\", \"C\", \"E\"}\n\tif !valuesEqual(result, expected) {\n\t\tt.Errorf(\"ModList order: got %s, want %s\", formatValue(result), formatValue(expected))\n\t}\n}\n\n// --- Number followed by fixed token (subMatchFixed alignment) ---\n\nfunc TestAlignmentNumberFixedToken(t *testing.T) {\n\t// \"123}\" at top level should see 123 as a number, then } triggers error\n\t// since it's an unexpected close at top level.\n\tj := Make()\n\n\t// In a valid context: {a:123} - number followed by }\n\tgot, err := j.Parse(\"{a:123}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", 123.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"number+fixed: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\t// [1,2] - number followed by , and ]\n\tgot, err = j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(1.0, 2.0)\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"number+fixed: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\n// --- Lex subscriber receives tokens ---\n\nfunc TestAlignmentLexSubscriber(t *testing.T) {\n\tj := Make()\n\tvar tokens []Tin\n\tj.Sub(func(tk *Token, r *Rule, ctx *Context) {\n\t\ttokens = append(tokens, tk.Tin)\n\t}, nil)\n\n\t_, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif len(tokens) == 0 {\n\t\tt.Error(\"lex subscriber was not called\")\n\t}\n}\n\n// --- Rule subscriber fires before process ---\n\nfunc TestAlignmentRuleSubscriberTiming(t *testing.T) {\n\tj := Make()\n\tvar states []RuleState\n\tj.Sub(nil, func(r *Rule, ctx *Context) {\n\t\tstates = append(states, r.State)\n\t})\n\n\t_, err := j.Parse(\"1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Subscriber fires BEFORE process, so the first call should see OPEN state.\n\tif len(states) == 0 {\n\t\tt.Fatal(\"rule subscriber was not called\")\n\t}\n\tif states[0] != OPEN {\n\t\tt.Errorf(\"first rule subscriber call: state=%q, want %q (subscriber should fire before process)\",\n\t\t\tstates[0], OPEN)\n\t}\n}\n\n// --- Error propagation from alt.E ---\n\nfunc TestAlignmentErrorPropagation(t *testing.T) {\n\t// Closing brace at top level should produce an error.\n\tj := Make()\n\n\t_, err := j.Parse(\"}\")\n\tif err == nil {\n\t\tt.Fatal(\"Parse(\\\"}\\\") should return error\")\n\t}\n\tje, ok := err.(*JsonicError)\n\tif !ok {\n\t\tt.Fatalf(\"error should be *JsonicError, got %T\", err)\n\t}\n\tif je.Code != \"unexpected\" {\n\t\tt.Errorf(\"error code: got %q, want \\\"unexpected\\\"\", je.Code)\n\t}\n\n\t_, err = j.Parse(\"]\")\n\tif err == nil {\n\t\tt.Fatal(\"Parse(\\\"]\\\") should return error\")\n\t}\n\tje, ok = err.(*JsonicError)\n\tif !ok {\n\t\tt.Fatalf(\"error should be *JsonicError, got %T\", err)\n\t}\n\tif je.Code != \"unexpected\" {\n\t\tt.Errorf(\"error code: got %q, want \\\"unexpected\\\"\", je.Code)\n\t}\n}\n\n// --- Trailing content detection ---\n\nfunc TestAlignmentTrailingContent(t *testing.T) {\n\tj := Make()\n\n\t// \"a:1,2\" - after parsing a:1 as a map, the ,2 is trailing content\n\t// that can't fit into the map (TS throws unexpected).\n\t_, err := j.Parse(\"a:1,2\")\n\tif err == nil {\n\t\tt.Error(\"Parse(\\\"a:1,2\\\") should return error for unexpected trailing content\")\n\t}\n}\n\n// --- FinishRule=false: unclosed structures error ---\n\nfunc TestAlignmentFinishRuleFalse(t *testing.T) {\n\tj := Make(Options{Rule: &RuleOptions{Finish: boolPtr(false)}})\n\n\t// With finish=false, unclosed { should error instead of auto-closing.\n\t_, err := j.Parse(\"{a:1\")\n\tif err == nil {\n\t\tt.Error(\"Parse(\\\"{a:1\\\") with rule.finish=false should return error\")\n\t}\n}\n\n// --- Snip: match TS snip() behavior ---\n\nfunc TestAlignmentSnip(t *testing.T) {\n\ttests := []struct {\n\t\tinput  string\n\t\tmaxlen int\n\t\texpect string\n\t}{\n\t\t{\"hello\", 5, \"hello\"},\n\t\t{\"hello\", 3, \"hel\"},\n\t\t{\"hello\", 0, \"\"},\n\t\t{\"hello\", -1, \"\"},\n\t\t{\"a\\nb\\tc\\rd\", 10, \"a.b.c.d\"},\n\t\t{\"a\\nb\\tc\\rd\", 3, \"a.b\"},\n\t\t{\"\", 5, \"\"},\n\t\t{\"\\n\\n\\n\", 3, \"...\"},\n\t}\n\tfor _, tt := range tests {\n\t\tgot := Snip(tt.input, tt.maxlen)\n\t\tif got != tt.expect {\n\t\t\tt.Errorf(\"Snip(%q, %d) = %q, want %q\", tt.input, tt.maxlen, got, tt.expect)\n\t\t}\n\t}\n}\n\n// --- Str: match TS str() + snip() pipeline ---\n\nfunc TestAlignmentStrNil(t *testing.T) {\n\t// TS: str(null) → JSON.stringify(null) → \"null\"\n\tgot := Str(nil, 44)\n\tif got != \"null\" {\n\t\tt.Errorf(\"Str(nil, 44) = %q, want %q\", got, \"null\")\n\t}\n}\n\nfunc TestAlignmentStrWhitespace(t *testing.T) {\n\t// TS: str() calls snip() which replaces \\r\\n\\t with '.'\n\tgot := Str(\"a\\tb\\nc\", 44)\n\tif got != \"a.b.c\" {\n\t\tt.Errorf(\"Str(\\\"a\\\\tb\\\\nc\\\", 44) = %q, want %q\", got, \"a.b.c\")\n\t}\n}\n\nfunc TestAlignmentStrTruncateWhitespace(t *testing.T) {\n\t// Truncation then snip: \"12\\t45\" with maxlen 4 → truncate to \"1...\" → snip \"1...\"\n\tgot := Str(\"12\\t45\", 4)\n\tif got != \"1...\" {\n\t\tt.Errorf(\"Str(\\\"12\\\\t45\\\", 4) = %q, want %q\", got, \"1...\")\n\t}\n}\n\n// --- ModList: custom callback ---\n\nfunc TestAlignmentModListCustom(t *testing.T) {\n\t// TS modlist supports mods.custom callback.\n\tinput := []any{\"a\", \"b\", \"c\"}\n\tresult := ModList(input, &ModListOpts{\n\t\tCustom: func(list []any) []any {\n\t\t\t// Reverse the list.\n\t\t\tn := len(list)\n\t\t\treversed := make([]any, n)\n\t\t\tfor i, v := range list {\n\t\t\t\treversed[n-1-i] = v\n\t\t\t}\n\t\t\treturn reversed\n\t\t},\n\t})\n\texpected := []any{\"c\", \"b\", \"a\"}\n\tif !valuesEqual(result, expected) {\n\t\tt.Errorf(\"ModList custom: got %v, want %v\", result, expected)\n\t}\n}\n\nfunc TestAlignmentModListCustomNil(t *testing.T) {\n\t// When custom returns nil, the original list is preserved (matches TS).\n\tinput := []any{\"a\", \"b\"}\n\tresult := ModList(input, &ModListOpts{\n\t\tCustom: func(list []any) []any {\n\t\t\treturn nil\n\t\t},\n\t})\n\texpected := []any{\"a\", \"b\"}\n\tif !valuesEqual(result, expected) {\n\t\tt.Errorf(\"ModList custom nil: got %v, want %v\", result, expected)\n\t}\n}\n\nfunc TestAlignmentModListDeleteThenCustom(t *testing.T) {\n\t// Custom runs after delete+filter (matches TS order).\n\tinput := []any{\"a\", \"b\", \"c\"}\n\tvar customInput []any\n\tresult := ModList(input, &ModListOpts{\n\t\tDelete: []int{1}, // delete \"b\"\n\t\tCustom: func(list []any) []any {\n\t\t\tcustomInput = append([]any{}, list...) // capture what custom sees\n\t\t\treturn list\n\t\t},\n\t})\n\t// Custom should see [\"a\", \"c\"] (after \"b\" was deleted).\n\texpectedCustom := []any{\"a\", \"c\"}\n\tif !valuesEqual(customInput, expectedCustom) {\n\t\tt.Errorf(\"ModList custom saw %v, want %v\", customInput, expectedCustom)\n\t}\n\tif !valuesEqual(result, expectedCustom) {\n\t\tt.Errorf(\"ModList result: got %v, want %v\", result, expectedCustom)\n\t}\n}\n"
  },
  {
    "path": "go/both_ref_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// expectBothRef parses input with both MapRef and ListRef enabled and checks the result.\nfunc expectBothRef(t *testing.T, input string, expected any) {\n\tt.Helper()\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tif !bothRefEqual(got, expected) {\n\t\tt.Errorf(\"Parse(%q)\\n  got:      %#v\\n  expected: %#v\",\n\t\t\tinput, got, expected)\n\t}\n}\n\n// bothRefEqual compares values including both ListRef and MapRef structs.\nfunc bothRefEqual(a, b any) bool {\n\tif a == nil && b == nil {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\n\tswitch av := a.(type) {\n\tcase MapRef:\n\t\tbv, ok := b.(MapRef)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tif av.Implicit != bv.Implicit {\n\t\t\treturn false\n\t\t}\n\t\tif len(av.Val) != len(bv.Val) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av.Val {\n\t\t\tbval, exists := bv.Val[k]\n\t\t\tif !exists || !bothRefEqual(v, bval) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\t// Meta: treat nil and empty map as equal\n\t\tif len(av.Meta) != 0 || len(bv.Meta) != 0 {\n\t\t\tif len(av.Meta) != len(bv.Meta) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfor k, v := range av.Meta {\n\t\t\t\tbval, exists := bv.Meta[k]\n\t\t\t\tif !exists || !bothRefEqual(v, bval) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase ListRef:\n\t\tbv, ok := b.(ListRef)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tif av.Implicit != bv.Implicit {\n\t\t\treturn false\n\t\t}\n\t\tif len(av.Val) != len(bv.Val) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av.Val {\n\t\t\tif !bothRefEqual(av.Val[i], bv.Val[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\t// Meta: treat nil and empty map as equal\n\t\tif len(av.Meta) != 0 || len(bv.Meta) != 0 {\n\t\t\tif len(av.Meta) != len(bv.Meta) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfor k, v := range av.Meta {\n\t\t\t\tbval, exists := bv.Meta[k]\n\t\t\t\tif !exists || !bothRefEqual(v, bval) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase map[string]any:\n\t\tbv, ok := b.(map[string]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av {\n\t\t\tbval, exists := bv[k]\n\t\t\tif !exists || !bothRefEqual(v, bval) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase []any:\n\t\tbv, ok := b.([]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av {\n\t\t\tif !bothRefEqual(av[i], bv[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase float64:\n\t\tbv, ok := b.(float64)\n\t\treturn ok && av == bv\n\tcase bool:\n\t\tbv, ok := b.(bool)\n\t\treturn ok && av == bv\n\tcase string:\n\t\tbv, ok := b.(string)\n\t\treturn ok && av == bv\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// Shorthand helpers for combined tests.\n// blr creates a ListRef (reuses lr name pattern).\nfunc blr(implicit bool, vals ...any) ListRef {\n\tif vals == nil {\n\t\tvals = []any{}\n\t}\n\treturn ListRef{Val: vals, Implicit: implicit}\n}\n\n// bmr creates a MapRef from key-value pairs.\nfunc bmr(implicit bool, pairs ...any) MapRef {\n\tm := make(map[string]any)\n\tfor i := 0; i+1 < len(pairs); i += 2 {\n\t\tk, _ := pairs[i].(string)\n\t\tm[k] = pairs[i+1]\n\t}\n\treturn MapRef{Val: m, Implicit: implicit}\n}\n\n// --- Basic combined wrapping ---\n\nfunc TestBothRefExplicitMapExplicitList(t *testing.T) {\n\t// {a:[1,2]} → explicit MapRef wrapping explicit ListRef\n\texpectBothRef(t, \"{a:[1,2]}\", bmr(false, \"a\", blr(false, 1.0, 2.0)))\n}\n\nfunc TestBothRefImplicitMapExplicitList(t *testing.T) {\n\t// a:[1,2] → implicit MapRef wrapping explicit ListRef\n\texpectBothRef(t, \"a:[1,2]\", bmr(true, \"a\", blr(false, 1.0, 2.0)))\n}\n\nfunc TestBothRefExplicitListExplicitMaps(t *testing.T) {\n\t// [{a:1},{b:2}] → explicit ListRef of explicit MapRefs\n\texpectBothRef(t, \"[{a:1},{b:2}]\", blr(false,\n\t\tbmr(false, \"a\", 1.0),\n\t\tbmr(false, \"b\", 2.0),\n\t))\n}\n\nfunc TestBothRefImplicitListExplicitMaps(t *testing.T) {\n\t// {a:1},{b:2} → comma creates implicit ListRef of explicit MapRefs\n\texpectBothRef(t, \"{a:1},{b:2}\", blr(true,\n\t\tbmr(false, \"a\", 1.0),\n\t\tbmr(false, \"b\", 2.0),\n\t))\n}\n\nfunc TestBothRefSpaceSeparatedMaps(t *testing.T) {\n\t// {a:1} {b:2} → space creates implicit ListRef of explicit MapRefs\n\texpectBothRef(t, \"{a:1} {b:2}\", blr(true,\n\t\tbmr(false, \"a\", 1.0),\n\t\tbmr(false, \"b\", 2.0),\n\t))\n}\n\n// --- Empty structures ---\n\nfunc TestBothRefEmptyMapInList(t *testing.T) {\n\t// [{}] → explicit ListRef containing empty explicit MapRef\n\texpectBothRef(t, \"[{}]\", blr(false, bmr(false)))\n}\n\nfunc TestBothRefEmptyListInMap(t *testing.T) {\n\t// {a:[]} → explicit MapRef containing empty explicit ListRef\n\texpectBothRef(t, \"{a:[]}\", bmr(false, \"a\", blr(false)))\n}\n\nfunc TestBothRefEmptyMapAndListValues(t *testing.T) {\n\t// {a:[],b:{}} → explicit MapRef with mixed empty values\n\texpectBothRef(t, \"{a:[],b:{}}\", bmr(false, \"a\", blr(false), \"b\", bmr(false)))\n}\n\n// --- Deep nesting ---\n\nfunc TestBothRefTripleNestingMapListMap(t *testing.T) {\n\t// {a:[{b:1}]} → MapRef > ListRef > MapRef\n\texpectBothRef(t, \"{a:[{b:1}]}\", bmr(false,\n\t\t\"a\", blr(false, bmr(false, \"b\", 1.0)),\n\t))\n}\n\nfunc TestBothRefTripleNestingListMapList(t *testing.T) {\n\t// [{a:[1,2]}] → ListRef > MapRef > ListRef\n\texpectBothRef(t, \"[{a:[1,2]}]\", blr(false,\n\t\tbmr(false, \"a\", blr(false, 1.0, 2.0)),\n\t))\n}\n\nfunc TestBothRefQuadNesting(t *testing.T) {\n\t// {a:[{b:[1]}]} → MapRef > ListRef > MapRef > ListRef\n\texpectBothRef(t, \"{a:[{b:[1]}]}\", bmr(false,\n\t\t\"a\", blr(false,\n\t\t\tbmr(false, \"b\", blr(false, 1.0)),\n\t\t),\n\t))\n}\n\n// --- Path dive ---\n\nfunc TestBothRefPathDive(t *testing.T) {\n\t// a:b:1 → implicit MapRef(a: implicit MapRef(b: 1))\n\texpectBothRef(t, \"a:b:1\", bmr(true, \"a\", bmr(true, \"b\", 1.0)))\n}\n\nfunc TestBothRefPathDiveWithList(t *testing.T) {\n\t// a:b:[1,2] → implicit MapRef(a: implicit MapRef(b: explicit ListRef))\n\texpectBothRef(t, \"a:b:[1,2]\", bmr(true, \"a\", bmr(true, \"b\", blr(false, 1.0, 2.0))))\n}\n\nfunc TestBothRefExplicitPathDive(t *testing.T) {\n\t// {a:b:1} → explicit MapRef(a: implicit MapRef(b: 1))\n\texpectBothRef(t, \"{a:b:1}\", bmr(false, \"a\", bmr(true, \"b\", 1.0)))\n}\n\n// --- Deep merge with both ---\n\nfunc TestBothRefDeepMergeNestedMapLists(t *testing.T) {\n\t// a:[{b:1}],a:[{b:2}] → deep merge: ListRef arrays merge, inner MapRef maps merge\n\texpectBothRef(t, \"a:[{b:1}],a:[{b:2}]\", bmr(true,\n\t\t\"a\", blr(false, bmr(false, \"b\", 2.0)),\n\t))\n}\n\nfunc TestBothRefDeepMergeNestedMaps(t *testing.T) {\n\t// a:{b:1},a:{c:2} → deep merge: inner MapRefs merge\n\texpectBothRef(t, \"a:{b:1},a:{c:2}\", bmr(true,\n\t\t\"a\", bmr(false, \"b\", 1.0, \"c\", 2.0),\n\t))\n}\n\nfunc TestBothRefDeepMergeMapsWithListValues(t *testing.T) {\n\t// a:{b:[1]},a:{b:[2]} → deep merge: maps merge, then list values merge\n\texpectBothRef(t, \"a:{b:[1]},a:{b:[2]}\", bmr(true,\n\t\t\"a\", bmr(false, \"b\", blr(false, 2.0)),\n\t))\n}\n\n// --- Mixed content in same structure ---\n\nfunc TestBothRefMapWithMixedValues(t *testing.T) {\n\t// {a:{x:1},b:[1,2],c:3} → MapRef with MapRef, ListRef, and scalar values\n\texpectBothRef(t, \"{a:{x:1},b:[1,2],c:3}\", bmr(false,\n\t\t\"a\", bmr(false, \"x\", 1.0),\n\t\t\"b\", blr(false, 1.0, 2.0),\n\t\t\"c\", 3.0,\n\t))\n}\n\nfunc TestBothRefListWithMixedValues(t *testing.T) {\n\t// [{a:1},[1,2],3] → ListRef with MapRef, ListRef, and scalar values\n\texpectBothRef(t, \"[{a:1},[1,2],3]\", blr(false,\n\t\tbmr(false, \"a\", 1.0),\n\t\tblr(false, 1.0, 2.0),\n\t\t3.0,\n\t))\n}\n\n// --- Implicit structures combined ---\n\nfunc TestBothRefImplicitListOfImplicitListsInMaps(t *testing.T) {\n\t// {a:1} {b:2} → implicit ListRef of explicit MapRefs (already tested above, but\n\t// verify types more explicitly)\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(\"{a:1} {b:2}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\touterList, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef at top level, got %T: %#v\", got, got)\n\t}\n\tif !outerList.Implicit {\n\t\tt.Errorf(\"expected top-level ListRef to be implicit\")\n\t}\n\tif len(outerList.Val) != 2 {\n\t\tt.Fatalf(\"expected 2 elements, got %d\", len(outerList.Val))\n\t}\n\tfor i, expectedKey := range []string{\"a\", \"b\"} {\n\t\tm, ok := outerList.Val[i].(MapRef)\n\t\tif !ok {\n\t\t\tt.Errorf(\"element %d: expected MapRef, got %T\", i, outerList.Val[i])\n\t\t\tcontinue\n\t\t}\n\t\tif m.Implicit {\n\t\t\tt.Errorf(\"element %d: expected explicit MapRef (braces)\", i)\n\t\t}\n\t\tif _, exists := m.Val[expectedKey]; !exists {\n\t\t\tt.Errorf(\"element %d: expected key %q\", i, expectedKey)\n\t\t}\n\t}\n}\n\n// --- Scalars still pass through unchanged ---\n\nfunc TestBothRefScalarNumber(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif f, ok := got.(float64); !ok || f != 42.0 {\n\t\tt.Errorf(\"expected 42.0, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestBothRefScalarString(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(`\"hello\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif s, ok := got.(string); !ok || s != \"hello\" {\n\t\tt.Errorf(\"expected \\\"hello\\\", got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestBothRefScalarBool(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(\"true\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif b, ok := got.(bool); !ok || b != true {\n\t\tt.Errorf(\"expected true, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestBothRefScalarNull(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(\"null\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expected nil, got %T: %#v\", got, got)\n\t}\n}\n\n// --- Pair in list ---\n\nfunc TestBothRefPairInList(t *testing.T) {\n\t// [a:1,b:2] → explicit ListRef with key-value properties\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(\"[a:1,b:2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\touterList, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tif outerList.Implicit {\n\t\tt.Errorf(\"expected explicit ListRef (brackets)\")\n\t}\n}\n\n// --- JSON compat with both ---\n\nfunc TestBothRefStrictJSON(t *testing.T) {\n\t// Standard JSON: {\"a\": [1, 2], \"b\": {\"c\": true}}\n\texpectBothRef(t, `{\"a\": [1, 2], \"b\": {\"c\": true}}`, bmr(false,\n\t\t\"a\", blr(false, 1.0, 2.0),\n\t\t\"b\", bmr(false, \"c\", true),\n\t))\n}\n\nfunc TestBothRefNestedJSONArrays(t *testing.T) {\n\t// {\"a\": [[1], [2]]} → nested ListRefs inside MapRef\n\texpectBothRef(t, `{\"a\": [[1], [2]]}`, bmr(false,\n\t\t\"a\", blr(false, blr(false, 1.0), blr(false, 2.0)),\n\t))\n}\n\n// --- Implicit list containing maps with list values ---\n\nfunc TestBothRefImplicitListMapsWithLists(t *testing.T) {\n\t// {a:[1]} {b:[2]} → implicit ListRef of explicit MapRefs with ListRef values\n\texpectBothRef(t, \"{a:[1]} {b:[2]}\", blr(true,\n\t\tbmr(false, \"a\", blr(false, 1.0)),\n\t\tbmr(false, \"b\", blr(false, 2.0)),\n\t))\n}\n\n// --- Deep merge preserves Ref wrappers through merge ---\n\nfunc TestBothRefDeepMergePreservesMapRef(t *testing.T) {\n\t// {a:{x:1}},{a:{y:2}} → implicit list but deep merge combines the maps\n\t// Actually this is an implicit list of two maps, deep merge happens on dup keys inside each\n\t// Let me use explicit map: {a:{x:1},a:{y:2}}\n\texpectBothRef(t, \"{a:{x:1},a:{y:2}}\", bmr(false,\n\t\t\"a\", bmr(false, \"x\", 1.0, \"y\", 2.0),\n\t))\n}\n\nfunc TestBothRefDeepMergePreservesListRef(t *testing.T) {\n\t// {a:[1,2],a:[3]} → deep merge of ListRef values inside MapRef\n\texpectBothRef(t, \"{a:[1,2],a:[3]}\", bmr(false,\n\t\t\"a\", blr(false, 3.0, 2.0),\n\t))\n}\n\n// --- All three options combined ---\n\nfunc TestBothRefWithTextInfo(t *testing.T) {\n\t// All three options: MapRef + ListRef + TextInfo\n\tj := Make(Options{Info: &InfoOptions{\n\t\tMap:  boolPtr(true),\n\t\tList: boolPtr(true),\n\t\tText: boolPtr(true),\n\t}})\n\tgot, err := j.Parse(`{a:[\"x\"]}`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := got.(MapRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected MapRef, got %T: %#v\", got, got)\n\t}\n\tif m.Implicit {\n\t\tt.Errorf(\"expected explicit MapRef\")\n\t}\n\tlistVal, ok := m.Val[\"a\"].(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef for key 'a', got %T: %#v\", m.Val[\"a\"], m.Val[\"a\"])\n\t}\n\tif listVal.Implicit {\n\t\tt.Errorf(\"expected explicit ListRef\")\n\t}\n\tif len(listVal.Val) != 1 {\n\t\tt.Fatalf(\"expected 1 element, got %d\", len(listVal.Val))\n\t}\n\ttxt, ok := listVal.Val[0].(Text)\n\tif !ok {\n\t\tt.Fatalf(\"expected Text, got %T: %#v\", listVal.Val[0], listVal.Val[0])\n\t}\n\tif txt.Str != \"x\" || txt.Quote != `\"` {\n\t\tt.Errorf(\"expected Text{Quote:`\\\"`, Str:\\\"x\\\"}, got %#v\", txt)\n\t}\n}\n\n// --- Comma-separated explicit lists create implicit outer list ---\n\nfunc TestBothRefCommaSeparatedLists(t *testing.T) {\n\t// [1],[2] → implicit ListRef of explicit ListRefs\n\texpectBothRef(t, \"[1],[2]\", blr(true,\n\t\tblr(false, 1.0),\n\t\tblr(false, 2.0),\n\t))\n}\n\n// --- Null values in combined structures ---\n\nfunc TestBothRefNullMapValue(t *testing.T) {\n\t// {a:null} → explicit MapRef with null value\n\texpectBothRef(t, \"{a:null}\", bmr(false, \"a\", nil))\n}\n\nfunc TestBothRefNullListElements(t *testing.T) {\n\t// [null,{a:1}] → ListRef with null and MapRef\n\texpectBothRef(t, \"[null,{a:1}]\", blr(false, nil, bmr(false, \"a\", 1.0)))\n}\n\nfunc TestBothRefMapWithNullAndList(t *testing.T) {\n\t// {a:null,b:[1]} → MapRef with null value and ListRef value\n\texpectBothRef(t, \"{a:null,b:[1]}\", bmr(false, \"a\", nil, \"b\", blr(false, 1.0)))\n}\n\n// --- Meta map ---\n\nfunc TestMapRefMetaInitialized(t *testing.T) {\n\t// MapRef.Meta should be initialized as an empty map when MapRef is enabled.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\tgot, err := j.Parse(\"{a:1}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := got.(MapRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected MapRef, got %T: %#v\", got, got)\n\t}\n\tif m.Meta == nil {\n\t\tt.Errorf(\"expected Meta to be initialized (non-nil), got nil\")\n\t}\n\tif len(m.Meta) != 0 {\n\t\tt.Errorf(\"expected Meta to be empty, got %#v\", m.Meta)\n\t}\n}\n\nfunc TestListRefMetaInitialized(t *testing.T) {\n\t// ListRef.Meta should be initialized as an empty map when ListRef is enabled.\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(true)}})\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tif lr.Meta == nil {\n\t\tt.Errorf(\"expected Meta to be initialized (non-nil), got nil\")\n\t}\n\tif len(lr.Meta) != 0 {\n\t\tt.Errorf(\"expected Meta to be empty, got %#v\", lr.Meta)\n\t}\n}\n\nfunc TestMapRefMetaAvailableInBOPhase(t *testing.T) {\n\t// Verify that Meta is available during parsing (set in BO, accessible in BC).\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\n\t// Add a custom BO action that writes to Meta\n\tj.Rule(\"map\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AddBO(func(r *Rule, ctx *Context) {\n\t\t\tif mr, ok := r.Node.(MapRef); ok {\n\t\t\t\tmr.Meta[\"created_in\"] = \"bo\"\n\t\t\t\tr.Node = mr\n\t\t\t}\n\t\t})\n\t})\n\n\tgot, err := j.Parse(\"{a:1}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := got.(MapRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected MapRef, got %T: %#v\", got, got)\n\t}\n\tif m.Meta[\"created_in\"] != \"bo\" {\n\t\tt.Errorf(\"expected Meta[\\\"created_in\\\"] = \\\"bo\\\", got %#v\", m.Meta)\n\t}\n}\n\nfunc TestListRefMetaAvailableInBOPhase(t *testing.T) {\n\t// Verify that Meta is available during parsing (set in BO, accessible in BC).\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(true)}})\n\n\t// Add a custom BO action that writes to Meta\n\tj.Rule(\"list\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AddBO(func(r *Rule, ctx *Context) {\n\t\t\tif lr, ok := r.Node.(ListRef); ok {\n\t\t\t\tlr.Meta[\"created_in\"] = \"bo\"\n\t\t\t\tr.Node = lr\n\t\t\t}\n\t\t})\n\t})\n\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tif lr.Meta[\"created_in\"] != \"bo\" {\n\t\tt.Errorf(\"expected Meta[\\\"created_in\\\"] = \\\"bo\\\", got %#v\", lr.Meta)\n\t}\n}\n"
  },
  {
    "path": "go/color_test.go",
    "content": "package jsonic\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\n// --- Defaults: active, matches TS ---\n\nfunc TestColorDefaultsActive(t *testing.T) {\n\t// With no Color option supplied, the resolved palette is active and\n\t// uses the TS default codes.\n\tj := Make()\n\tif !j.parser.Config.Color.Active {\n\t\tt.Fatal(\"Color.Active should default to true\")\n\t}\n\tif j.parser.Config.Color.Reset != \"\\x1b[0m\" {\n\t\tt.Errorf(\"Reset: got %q, want %q\", j.parser.Config.Color.Reset, \"\\x1b[0m\")\n\t}\n\tif j.parser.Config.Color.Hi != \"\\x1b[91m\" {\n\t\tt.Errorf(\"Hi: got %q, want %q\", j.parser.Config.Color.Hi, \"\\x1b[91m\")\n\t}\n\tif j.parser.Config.Color.Lo != \"\\x1b[2m\" {\n\t\tt.Errorf(\"Lo: got %q, want %q\", j.parser.Config.Color.Lo, \"\\x1b[2m\")\n\t}\n\tif j.parser.Config.Color.Line != \"\\x1b[34m\" {\n\t\tt.Errorf(\"Line: got %q, want %q\", j.parser.Config.Color.Line, \"\\x1b[34m\")\n\t}\n}\n\n// --- Active true (default): error output carries ANSI ---\n\nfunc TestColorActiveWrapsHeader(t *testing.T) {\n\tj := Make()\n\t_, err := j.Parse(`\"unterminated`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := err.Error()\n\t// Header \"[jsonic/unterminated_string]:\" should be wrapped by the Hi\n\t// code before and Reset after.\n\twant := \"\\x1b[91m[jsonic/unterminated_string]:\\x1b[0m\"\n\tif !strings.Contains(msg, want) {\n\t\tt.Errorf(\"expected header to be colour-wrapped, got:\\n%q\", msg)\n\t}\n}\n\nfunc TestColorActiveWrapsArrow(t *testing.T) {\n\tj := Make()\n\t_, err := j.Parse(`\"unterminated`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := err.Error()\n\t// The source-location arrow gets the Line code then Reset.\n\twant := \"\\x1b[34m-->\\x1b[0m\"\n\tif !strings.Contains(msg, want) {\n\t\tt.Errorf(\"expected --> to be colour-wrapped, got:\\n%q\", msg)\n\t}\n}\n\nfunc TestColorActiveWrapsCaret(t *testing.T) {\n\tj := Make()\n\t_, err := j.Parse(`\"unterminated`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := err.Error()\n\t// The caret row starts with the Line code and ends with Reset.\n\tif !strings.Contains(msg, \"\\x1b[34m^\") {\n\t\tt.Errorf(\"caret line should start with Line ANSI code, got:\\n%q\", msg)\n\t}\n\tif !strings.Contains(msg, \"\\x1b[0m\") {\n\t\tt.Errorf(\"caret line should contain Reset, got:\\n%q\", msg)\n\t}\n}\n\n// --- Disabled: no ANSI codes anywhere ---\n\nfunc TestColorDisabledSuppressesAll(t *testing.T) {\n\tno := false\n\tj := Make(Options{Color: &ColorOptions{Active: &no}})\n\t_, err := j.Parse(`\"unterminated`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := err.Error()\n\tif strings.Contains(msg, \"\\x1b[\") {\n\t\tt.Errorf(\"expected no ANSI escapes, got:\\n%q\", msg)\n\t}\n\t// Plain text still reaches the caller.\n\tif !strings.Contains(msg, \"[jsonic/unterminated_string]:\") {\n\t\tt.Errorf(\"expected plain header, got:\\n%q\", msg)\n\t}\n\tif !strings.Contains(msg, \"-->\") {\n\t\tt.Errorf(\"expected plain arrow, got:\\n%q\", msg)\n\t}\n}\n\n// --- Custom codes override defaults ---\n\nfunc TestColorCustomOverrides(t *testing.T) {\n\tyes := true\n\tj := Make(Options{Color: &ColorOptions{\n\t\tActive: &yes,\n\t\tReset:  \"<R>\",\n\t\tHi:     \"<HI>\",\n\t\tLine:   \"<LN>\",\n\t}})\n\t_, err := j.Parse(`\"unterminated`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := err.Error()\n\tif !strings.Contains(msg, \"<HI>[jsonic/\") {\n\t\tt.Errorf(\"expected custom Hi, got:\\n%q\", msg)\n\t}\n\tif !strings.Contains(msg, \"<LN>-->\") {\n\t\tt.Errorf(\"expected custom Line, got:\\n%q\", msg)\n\t}\n\tif !strings.Contains(msg, \"]:<R>\") {\n\t\tt.Errorf(\"expected custom Reset, got:\\n%q\", msg)\n\t}\n\t// Lo defaults to the TS ANSI code since we didn't override it;\n\t// but Lo is not used in the current Go formatter's output lines,\n\t// so just verify the explicitly-set codes work without asserting Lo.\n}\n\n// --- SetOptions path re-applies colour ---\n\nfunc TestColorToggleViaSetOptions(t *testing.T) {\n\t// Start with defaults (active); flip off via SetOptions.\n\tj := Make()\n\tno := false\n\tj.SetOptions(Options{Color: &ColorOptions{Active: &no}})\n\n\t_, err := j.Parse(`\"unterminated`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tif strings.Contains(err.Error(), \"\\x1b[\") {\n\t\tt.Errorf(\"SetOptions didn't disable colour, got:\\n%q\", err.Error())\n\t}\n}\n\n// --- Text round-trip ---\n\nfunc TestColorFromTextDisable(t *testing.T) {\n\tj, err := Make().SetOptionsText(`color: { active: false }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, perr := j.Parse(`\"unterminated`)\n\tif perr == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tif strings.Contains(perr.Error(), \"\\x1b[\") {\n\t\tt.Errorf(\"text-form disable failed, got:\\n%q\", perr.Error())\n\t}\n}\n\nfunc TestColorFromTextCustomCodes(t *testing.T) {\n\t// Use non-ANSI placeholders so the assertion is unambiguous.\n\tj, err := Make().SetOptionsText(`color: {\n\t\tactive: true,\n\t\thi: \"<HI>\",\n\t\tlo: \"<LO>\",\n\t\tline: \"<LN>\",\n\t\treset: \"<R>\"\n\t}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, perr := j.Parse(`\"unterminated`)\n\tif perr == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := perr.Error()\n\tif !strings.Contains(msg, \"<HI>[jsonic/\") || !strings.Contains(msg, \"]:<R>\") {\n\t\tt.Errorf(\"custom codes not applied, got:\\n%q\", msg)\n\t}\n\tif !strings.Contains(msg, \"<LN>-->\") {\n\t\tt.Errorf(\"line colour not applied, got:\\n%q\", msg)\n\t}\n}\n\n// --- Caret colour applies to lexer-path errors too ---\n\nfunc TestColorAppliesToLexerError(t *testing.T) {\n\t// \"unterminated_string\" comes from the lexer. We verify the ANSI\n\t// codes appear on that path too (not just parser-generated errors).\n\tj := Make()\n\t_, err := j.Parse(`a:\"abc`)\n\tif err == nil {\n\t\tt.Fatal(\"expected parse error\")\n\t}\n\tmsg := err.Error()\n\tif !strings.Contains(msg, \"\\x1b[91m[jsonic/unterminated_string]:\\x1b[0m\") {\n\t\tt.Errorf(\"lex-path error missing ANSI, got:\\n%q\", msg)\n\t}\n}\n"
  },
  {
    "path": "go/comment_suffix_test.go",
    "content": "package jsonic\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\n// --- Line comment suffixes ---\n\nfunc TestCommentLineSuffixSingleString(t *testing.T) {\n\t// Suffix `@@` terminates the comment and is consumed. The following\n\t// `b:2` is then parsed as a normal pair.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash-inline\": {Line: true, Start: \"#\", Lex: &yes, Suffix: \"@@\"},\n\t\t},\n\t}})\n\n\tout, err := j.Parse(`a:1,# mid @@b:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"a: got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"b: got %v (suffix didn't terminate the comment)\", m[\"b\"])\n\t}\n}\n\nfunc TestCommentLineSuffixMultiple(t *testing.T) {\n\t// Any of the listed suffixes terminates the comment. The suffix is\n\t// consumed, so the input after STOP parses cleanly as b:2.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", Lex: &yes, Suffix: []string{\"END\", \"STOP\"}},\n\t\t},\n\t}})\n\n\tout, err := j.Parse(`a:1,# noise STOPb:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"expected STOP to terminate and be consumed, got %v\", m)\n\t}\n}\n\nfunc TestCommentLineSuffixPreferredLongestFirst(t *testing.T) {\n\t// Given overlapping suffixes, the longer match wins and is fully\n\t// consumed — so only `b:2` remains after the comment.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", Lex: &yes, Suffix: []string{\"@\", \"@@\"}},\n\t\t},\n\t}})\n\n\tout, err := j.Parse(`a:1,# stop@@b:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"expected '@@' to terminate and consume, got %v\", m)\n\t}\n\t// `@` alone must not survive either, or it would become text.\n\tif _, stray := m[\"@\"]; stray {\n\t\tt.Errorf(\"stray '@' key: %v\", m)\n\t}\n}\n\nfunc TestCommentLineSuffixIsConsumed(t *testing.T) {\n\t// The suffix is eaten by the comment, so the key's value is the\n\t// implicit-null of a bare `a:` (no text follows).\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", Lex: &yes, Suffix: \"@@\"},\n\t\t},\n\t}})\n\n\t// Suffix runs until `@@`, which is consumed — nothing follows.\n\tout, err := j.Parse(`a:# noise @@`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif _, has := m[\"a\"]; !has {\n\t\tt.Errorf(\"expected 'a' key to exist with implicit value, got %v\", m)\n\t}\n}\n\nfunc TestCommentLineSuffixFallsBackToNewline(t *testing.T) {\n\t// When no suffix marker appears, the line-char still terminates.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", Lex: &yes, Suffix: \"END\"},\n\t\t},\n\t}})\n\n\tout, err := j.Parse(\"a:1\\n# no-marker\\nb:2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"a\"] != float64(1) || m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"line fallback broken: %v\", m)\n\t}\n}\n\n// --- EatLine interaction ---\n\nfunc TestCommentLineSuffixBeatsEatLine(t *testing.T) {\n\t// EatLine only runs when termination came from a line-char.\n\t// Suffix-terminated comments leave newlines for the next matcher.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {\n\t\t\t\tLine:    true,\n\t\t\t\tStart:   \"#\",\n\t\t\t\tLex:     &yes,\n\t\t\t\tEatLine: &yes,\n\t\t\t\tSuffix:  \"@@\",\n\t\t\t},\n\t\t},\n\t}})\n\n\t// `@@` consumes the suffix; the following newline stays in input so\n\t// `b:2` on the next line is parsed as its own pair.\n\tout, err := j.Parse(\"a:1,# note @@\\nb:2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"a\"] != float64(1) || m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"eatline-with-suffix misbehaved: %v\", m)\n\t}\n}\n\n// --- Block comment suffixes ---\n\nfunc TestCommentBlockSuffixEarlyTermination(t *testing.T) {\n\t// A suffix inside a block comment terminates and is consumed, so a\n\t// missing End marker is no longer an error.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"block\": {\n\t\t\t\tLine:   false,\n\t\t\t\tStart:  \"/*\",\n\t\t\t\tEnd:    \"*/\",\n\t\t\t\tLex:    &yes,\n\t\t\t\tSuffix: \"!!\",\n\t\t\t},\n\t\t},\n\t}})\n\n\t// No `*/` at all; `!!` ends the comment and is consumed.\n\tout, err := j.Parse(`a:/* note !!1,b:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"a\"] != float64(1) || m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"block suffix early-termination failed: %v\", m)\n\t}\n}\n\nfunc TestCommentBlockSuffixStillHonoursEnd(t *testing.T) {\n\t// When neither suffix nor end is present, unterminated is still bad.\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"block\": {\n\t\t\t\tLine:   false,\n\t\t\t\tStart:  \"/*\",\n\t\t\t\tEnd:    \"*/\",\n\t\t\t\tLex:    &yes,\n\t\t\t\tSuffix: \"!!\",\n\t\t\t},\n\t\t},\n\t}})\n\n\tif _, err := j.Parse(`a:/* never ends`); err == nil {\n\t\tt.Error(\"expected unterminated_comment error\")\n\t} else if !strings.Contains(err.Error(), \"unterminated\") {\n\t\tt.Errorf(\"wrong error: %v\", err)\n\t}\n}\n\nfunc TestCommentBlockSuffixLosesToEndWhenCloser(t *testing.T) {\n\t// If End appears before any suffix, End wins (and is consumed).\n\tyes := true\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"block\": {\n\t\t\t\tLine:   false,\n\t\t\t\tStart:  \"/*\",\n\t\t\t\tEnd:    \"*/\",\n\t\t\t\tLex:    &yes,\n\t\t\t\tSuffix: \"!!\",\n\t\t\t},\n\t\t},\n\t}})\n\n\t// End arrives first — comment consumes through `*/`.\n\tout, err := j.Parse(`a:/* note */1,b:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"a\"] != float64(1) || m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"end-first path broke: %v\", m)\n\t}\n}\n\n// --- LexMatcher-form suffix ---\n\nfunc TestCommentSuffixLexMatcherTerminates(t *testing.T) {\n\t// A LexMatcher that returns a 2-char token at the `!!` boundary\n\t// terminates the comment body and consumes 2 chars.\n\tyes := true\n\tmatcher := LexMatcher(func(lex *Lex, _ *Rule) *Token {\n\t\tif lex.pnt.SI+2 <= len(lex.Src) && lex.Src[lex.pnt.SI:lex.pnt.SI+2] == \"!!\" {\n\t\t\treturn &Token{Src: \"!!\"}\n\t\t}\n\t\treturn nil\n\t})\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", Lex: &yes, Suffix: matcher},\n\t\t},\n\t}})\n\n\t// `!!` fires the matcher and is consumed; `b` is the value of `a`.\n\tout, err := j.Parse(`a:# noise !!b`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif m := out.(map[string]any); m[\"a\"] != \"b\" {\n\t\tt.Errorf(\"expected suffix matcher to consume !!, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestCommentSuffixLexMatcherCannotAdvance(t *testing.T) {\n\t// A misbehaving suffix matcher that tries to advance the point must\n\t// be prevented from doing so — the point is snapshotted/restored.\n\t// This matcher returns an empty-Src token, so len(Src)==0 means the\n\t// probe does not terminate, and parsing simply proceeds to EOL.\n\tyes := true\n\tmatcher := LexMatcher(func(lex *Lex, _ *Rule) *Token {\n\t\tlex.pnt.SI += 100  // malicious advance\n\t\treturn &Token{Src: \"\"}\n\t})\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\": {Line: true, Start: \"#\", Lex: &yes, Suffix: matcher},\n\t\t},\n\t}})\n\n\t// The comment runs to end-of-input safely. `a` is an implicit-null\n\t// binding.\n\tout, err := j.Parse(`a:#noisy`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif _, has := m[\"a\"]; !has {\n\t\tt.Errorf(\"expected 'a' key to survive, got %v\", m)\n\t}\n}\n\n// --- Text round-trip ---\n\nfunc TestCommentSuffixFromTextString(t *testing.T) {\n\t// Use `!!` so ResolveFuncRefs (which collapses `@@x` → `@x`) does not\n\t// rewrite our suffix.\n\tj := Make()\n\tif err := j.GrammarText(`options: {\n\t\tcomment: {\n\t\t\tdef: { hash: { line: true, start: \"#\", lex: true, suffix: \"!!\" } }\n\t\t}\n\t}`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tout, err := j.Parse(`a:1,# note !!b:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"text-form suffix ignored: %v\", m)\n\t}\n}\n\nfunc TestCommentSuffixFromTextArray(t *testing.T) {\n\tj := Make()\n\tif err := j.GrammarText(`options: {\n\t\tcomment: {\n\t\t\tdef: { hash: { line: true, start: \"#\", lex: true, suffix: [\"!!\", \"?!\"] } }\n\t\t}\n\t}`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// `?!` ends the comment and is consumed; `c:3` survives.\n\tout, err := j.Parse(`a:1,# note ?!c:3`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := out.(map[string]any)\n\tif m[\"c\"] != float64(3) {\n\t\tt.Errorf(\"array-form suffix ignored: %v\", m)\n\t}\n}\n\n// --- Config-level normalization ---\n\nfunc TestNormalizeCommentSuffixStringForms(t *testing.T) {\n\tstrs, fn := normalizeCommentSuffix(\"abc\")\n\tif len(strs) != 1 || strs[0] != \"abc\" || fn != nil {\n\t\tt.Errorf(\"string form: got %v fn=%v\", strs, fn != nil)\n\t}\n\n\tstrs, fn = normalizeCommentSuffix([]string{\"a\", \"bbb\", \"cc\"})\n\t// Sorted longest-first, then lexicographic.\n\tif len(strs) != 3 || strs[0] != \"bbb\" || strs[1] != \"cc\" || strs[2] != \"a\" {\n\t\tt.Errorf(\"longest-first sort broken: %v\", strs)\n\t}\n\tif fn != nil {\n\t\tt.Errorf(\"unexpected fn for string-slice form\")\n\t}\n}\n\nfunc TestNormalizeCommentSuffixNilAndEmpty(t *testing.T) {\n\tstrs, fn := normalizeCommentSuffix(nil)\n\tif len(strs) != 0 || fn != nil {\n\t\tt.Errorf(\"nil should yield empty: %v %v\", strs, fn != nil)\n\t}\n\tstrs, fn = normalizeCommentSuffix(\"\")\n\tif len(strs) != 0 || fn != nil {\n\t\tt.Errorf(\"empty string should yield empty: %v %v\", strs, fn != nil)\n\t}\n}\n"
  },
  {
    "path": "go/coverage.html",
    "content": "\n<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t\t<title>go: Go Coverage Report</title>\n\t\t<style>\n\t\t\tbody {\n\t\t\t\tbackground: black;\n\t\t\t\tcolor: rgb(80, 80, 80);\n\t\t\t}\n\t\t\tbody, pre, #legend span {\n\t\t\t\tfont-family: Menlo, monospace;\n\t\t\t\tfont-weight: bold;\n\t\t\t}\n\t\t\t#topbar {\n\t\t\t\tbackground: black;\n\t\t\t\tposition: fixed;\n\t\t\t\ttop: 0; left: 0; right: 0;\n\t\t\t\theight: 42px;\n\t\t\t\tborder-bottom: 1px solid rgb(80, 80, 80);\n\t\t\t}\n\t\t\t#content {\n\t\t\t\tmargin-top: 50px;\n\t\t\t}\n\t\t\t#nav, #legend {\n\t\t\t\tfloat: left;\n\t\t\t\tmargin-left: 10px;\n\t\t\t}\n\t\t\t#legend {\n\t\t\t\tmargin-top: 12px;\n\t\t\t}\n\t\t\t#nav {\n\t\t\t\tmargin-top: 10px;\n\t\t\t}\n\t\t\t#legend span {\n\t\t\t\tmargin: 0 5px;\n\t\t\t}\n\t\t\t.cov0 { color: rgb(192, 0, 0) }\n.cov1 { color: rgb(128, 128, 128) }\n.cov2 { color: rgb(116, 140, 131) }\n.cov3 { color: rgb(104, 152, 134) }\n.cov4 { color: rgb(92, 164, 137) }\n.cov5 { color: rgb(80, 176, 140) }\n.cov6 { color: rgb(68, 188, 143) }\n.cov7 { color: rgb(56, 200, 146) }\n.cov8 { color: rgb(44, 212, 149) }\n.cov9 { color: rgb(32, 224, 152) }\n.cov10 { color: rgb(20, 236, 155) }\n\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div id=\"topbar\">\n\t\t\t<div id=\"nav\">\n\t\t\t\t<select id=\"files\">\n\t\t\t\t\n\t\t\t\t<option value=\"file0\">github.com/jsonicjs/jsonic/go/debug.go (85.2%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file1\">github.com/jsonicjs/jsonic/go/grammar.go (85.3%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file2\">github.com/jsonicjs/jsonic/go/grammarspec.go (65.1%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file3\">github.com/jsonicjs/jsonic/go/jsonic.go (90.0%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file4\">github.com/jsonicjs/jsonic/go/lexer.go (82.9%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file5\">github.com/jsonicjs/jsonic/go/options.go (92.5%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file6\">github.com/jsonicjs/jsonic/go/parser.go (85.6%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file7\">github.com/jsonicjs/jsonic/go/plugin.go (92.4%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file8\">github.com/jsonicjs/jsonic/go/rule.go (80.9%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file9\">github.com/jsonicjs/jsonic/go/token.go (100.0%)</option>\n\t\t\t\t\n\t\t\t\t<option value=\"file10\">github.com/jsonicjs/jsonic/go/utility.go (80.4%)</option>\n\t\t\t\t\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div id=\"legend\">\n\t\t\t\t<span>not tracked</span>\n\t\t\t\n\t\t\t\t<span class=\"cov0\">not covered</span>\n\t\t\t\t<span class=\"cov8\">covered</span>\n\t\t\t\n\t\t\t</div>\n\t\t</div>\n\t\t<div id=\"content\">\n\t\t\n\t\t<pre class=\"file\" id=\"file0\" style=\"display: none\">package jsonic\n\nimport (\n        \"fmt\"\n        \"sort\"\n        \"strings\"\n)\n\n// Debug is a plugin that provides introspection and tracing capabilities.\n// It matches the TypeScript Debug plugin functionality.\n//\n// Usage:\n//\n//        j := jsonic.Make()\n//        j.Use(jsonic.Debug, map[string]any{\"trace\": true})\n//        fmt.Println(jsonic.Describe(j))\nvar Debug Plugin = func(j *Jsonic, opts map[string]any) <span class=\"cov8\" title=\"1\">{\n        if opts != nil </span><span class=\"cov0\" title=\"0\">{\n                if trace, ok := opts[\"trace\"]; ok </span><span class=\"cov0\" title=\"0\">{\n                        if traceBool, ok := trace.(bool); ok &amp;&amp; traceBool </span><span class=\"cov0\" title=\"0\">{\n                                addTrace(j)\n                        }</span>\n                }\n        }\n}\n\n// addTrace installs lex and rule subscribers that log each step.\nfunc addTrace(j *Jsonic) <span class=\"cov0\" title=\"0\">{\n        j.Sub(\n                func(tkn *Token, rule *Rule, ctx *Context) </span><span class=\"cov0\" title=\"0\">{\n                        fmt.Printf(\"[lex] %s tin=%d src=%q val=%v at %d:%d\\n\",\n                                tkn.Name, tkn.Tin, tkn.Src, tkn.Val, tkn.RI, tkn.CI)\n                }</span>,\n                func(rule *Rule, ctx *Context) <span class=\"cov0\" title=\"0\">{\n                        fmt.Printf(\"[rule] %s state=%s node=%v ki=%d\\n\",\n                                rule.Name, rule.State, rule.Node, ctx.KI)\n                }</span>,\n        )\n}\n\n// Describe returns a human-readable description of a Jsonic instance's configuration.\n// It lists tokens, fixed tokens, rules, matchers, plugins, and key config settings.\nfunc Describe(j *Jsonic) string <span class=\"cov8\" title=\"1\">{\n        var b strings.Builder\n\n        b.WriteString(\"=== Jsonic Instance ===\\n\")\n        if j.options != nil &amp;&amp; j.options.Tag != \"\" </span><span class=\"cov8\" title=\"1\">{\n                b.WriteString(fmt.Sprintf(\"Tag: %s\\n\", j.options.Tag))\n        }</span>\n\n        // Tokens\n        <span class=\"cov8\" title=\"1\">b.WriteString(\"\\n--- Tokens ---\\n\")\n        names := make([]string, 0, len(j.tinByName))\n        for name := range j.tinByName </span><span class=\"cov8\" title=\"1\">{\n                names = append(names, name)\n        }</span>\n        <span class=\"cov8\" title=\"1\">sort.Strings(names)\n        for _, name := range names </span><span class=\"cov8\" title=\"1\">{\n                tin := j.tinByName[name]\n                b.WriteString(fmt.Sprintf(\"  %s = %d\\n\", name, tin))\n        }</span>\n\n        // Fixed tokens\n        <span class=\"cov8\" title=\"1\">b.WriteString(\"\\n--- Fixed Tokens ---\\n\")\n        cfg := j.Config()\n        if cfg.FixedTokens != nil </span><span class=\"cov8\" title=\"1\">{\n                ftKeys := make([]string, 0, len(cfg.FixedTokens))\n                for k := range cfg.FixedTokens </span><span class=\"cov8\" title=\"1\">{\n                        ftKeys = append(ftKeys, k)\n                }</span>\n                <span class=\"cov8\" title=\"1\">sort.Strings(ftKeys)\n                for _, k := range ftKeys </span><span class=\"cov8\" title=\"1\">{\n                        tin := cfg.FixedTokens[k]\n                        name := j.TinName(tin)\n                        b.WriteString(fmt.Sprintf(\"  %q -&gt; %s (%d)\\n\", k, name, tin))\n                }</span>\n        }\n\n        // Rules\n        <span class=\"cov8\" title=\"1\">b.WriteString(\"\\n--- Rules ---\\n\")\n        ruleNames := make([]string, 0, len(j.parser.RSM))\n        for name := range j.parser.RSM </span><span class=\"cov8\" title=\"1\">{\n                ruleNames = append(ruleNames, name)\n        }</span>\n        <span class=\"cov8\" title=\"1\">sort.Strings(ruleNames)\n        for _, name := range ruleNames </span><span class=\"cov8\" title=\"1\">{\n                rs := j.parser.RSM[name]\n                b.WriteString(fmt.Sprintf(\"  %s: open=%d close=%d bo=%d ao=%d bc=%d ac=%d\\n\",\n                        name, len(rs.Open), len(rs.Close), len(rs.BO), len(rs.AO), len(rs.BC), len(rs.AC)))\n        }</span>\n\n        // Custom matchers\n        <span class=\"cov8\" title=\"1\">if len(cfg.CustomMatchers) &gt; 0 </span><span class=\"cov0\" title=\"0\">{\n                b.WriteString(\"\\n--- Custom Matchers ---\\n\")\n                for _, m := range cfg.CustomMatchers </span><span class=\"cov0\" title=\"0\">{\n                        b.WriteString(fmt.Sprintf(\"  %s (priority=%d)\\n\", m.Name, m.Priority))\n                }</span>\n        }\n\n        // Plugins\n        <span class=\"cov8\" title=\"1\">b.WriteString(fmt.Sprintf(\"\\n--- Plugins: %d ---\\n\", len(j.plugins)))\n\n        // Subscriptions\n        b.WriteString(fmt.Sprintf(\"\\n--- Subscriptions ---\\n\"))\n        b.WriteString(fmt.Sprintf(\"  Lex subscribers: %d\\n\", len(j.lexSubs)))\n        b.WriteString(fmt.Sprintf(\"  Rule subscribers: %d\\n\", len(j.ruleSubs)))\n\n        // Config summary\n        b.WriteString(\"\\n--- Config ---\\n\")\n        b.WriteString(fmt.Sprintf(\"  FixedLex: %v\\n\", cfg.FixedLex))\n        b.WriteString(fmt.Sprintf(\"  SpaceLex: %v\\n\", cfg.SpaceLex))\n        b.WriteString(fmt.Sprintf(\"  LineLex: %v\\n\", cfg.LineLex))\n        b.WriteString(fmt.Sprintf(\"  TextLex: %v\\n\", cfg.TextLex))\n        b.WriteString(fmt.Sprintf(\"  NumberLex: %v\\n\", cfg.NumberLex))\n        b.WriteString(fmt.Sprintf(\"  CommentLex: %v\\n\", cfg.CommentLex))\n        b.WriteString(fmt.Sprintf(\"  StringLex: %v\\n\", cfg.StringLex))\n        b.WriteString(fmt.Sprintf(\"  ValueLex: %v\\n\", cfg.ValueLex))\n        b.WriteString(fmt.Sprintf(\"  MapExtend: %v\\n\", cfg.MapExtend))\n        b.WriteString(fmt.Sprintf(\"  ListProperty: %v\\n\", cfg.ListProperty))\n        b.WriteString(fmt.Sprintf(\"  SafeKey: %v\\n\", cfg.SafeKey))\n        b.WriteString(fmt.Sprintf(\"  FinishRule: %v\\n\", cfg.FinishRule))\n        b.WriteString(fmt.Sprintf(\"  RuleStart: %s\\n\", cfg.RuleStart))\n\n        return b.String()</span>\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file1\" style=\"display: none\">package jsonic\n\n// buildGrammar populates the default jsonic grammar rules using declarative GrammarAltSpec.\n// This is a faithful port of grammar.ts, matching the exact alternate orderings\n// produced by the JSON phase followed by the Jsonic extension phase.\nfunc buildGrammar(rsm map[string]*RuleSpec, cfg *LexConfig) <span class=\"cov8\" title=\"1\">{\n        // Named function references for the grammar.\n        // These closures capture cfg for runtime configuration access.\n        ref := map[FuncRef]any{\n                \"@finish\": AltError(func(r *Rule, ctx *Context) *Token </span><span class=\"cov8\" title=\"1\">{\n                        if !cfg.FinishRule </span><span class=\"cov8\" title=\"1\">{\n                                return ctx.T0\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">return nil</span>\n                }),\n\n                \"@pairkey\": AltAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        keyToken := r.O0\n                        var key string\n                        if keyToken.Tin == TinST || keyToken.Tin == TinTX </span><span class=\"cov8\" title=\"1\">{\n                                key, _ = keyToken.Val.(string)\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                key = keyToken.Src\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">r.U[\"key\"] = key</span>\n                }),\n\n                \"@pairval\": AltAction(func(r *Rule, ctx *Context) <span class=\"cov0\" title=\"0\">{\n                        key, _ := r.U[\"key\"].(string)\n                        val := r.Child.Node\n                        if IsUndefined(val) </span><span class=\"cov0\" title=\"0\">{\n                                val = nil\n                        }</span>\n                        <span class=\"cov0\" title=\"0\">if cfg.SafeKey &amp;&amp; r.U[\"list\"] == true </span><span class=\"cov0\" title=\"0\">{\n                                if key == \"__proto__\" || key == \"constructor\" </span><span class=\"cov0\" title=\"0\">{\n                                        return\n                                }</span>\n                        }\n                        <span class=\"cov0\" title=\"0\">prev := r.U[\"prev\"]\n                        if prev == nil </span><span class=\"cov0\" title=\"0\">{\n                                nodeMapSet(r.Node, key, val)\n                        }</span> else<span class=\"cov0\" title=\"0\"> if cfg.MapMerge != nil </span><span class=\"cov0\" title=\"0\">{\n                                nodeMapSet(r.Node, key, cfg.MapMerge(prev, val, r, ctx))\n                        }</span> else<span class=\"cov0\" title=\"0\"> if cfg.MapExtend </span><span class=\"cov0\" title=\"0\">{\n                                nodeMapSet(r.Node, key, Deep(prev, val))\n                        }</span> else<span class=\"cov0\" title=\"0\"> {\n                                nodeMapSet(r.Node, key, val)\n                        }</span>\n                }),\n\n                // val rule state actions\n                \"@val-bo\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        r.Node = Undefined\n                }</span>),\n\n                \"@val-bc\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if IsUndefined(r.Node) </span><span class=\"cov8\" title=\"1\">{\n                                if IsUndefined(r.Child.Node) </span><span class=\"cov8\" title=\"1\">{\n                                        if r.OS == 0 </span><span class=\"cov0\" title=\"0\">{\n                                                r.Node = Undefined\n                                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                                val := r.O0.ResolveVal()\n                                                if cfg.TextInfo &amp;&amp; (r.O0.Tin == TinST || r.O0.Tin == TinTX) </span><span class=\"cov8\" title=\"1\">{\n                                                        quote := \"\"\n                                                        if r.O0.Tin == TinST &amp;&amp; len(r.O0.Src) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                                                quote = string(r.O0.Src[0])\n                                                        }</span>\n                                                        <span class=\"cov8\" title=\"1\">str, _ := val.(string)\n                                                        val = Text{Quote: quote, Str: str}</span>\n                                                }\n                                                <span class=\"cov8\" title=\"1\">r.Node = val</span>\n                                        }\n                                } else<span class=\"cov8\" title=\"1\"> {\n                                        r.Node = r.Child.Node\n                                }</span>\n                        }\n                }),\n\n                // map rule state actions\n                \"@map-bo\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if cfg.MapRef </span><span class=\"cov8\" title=\"1\">{\n                                r.Node = MapRef{\n                                        Val:  make(map[string]any),\n                                        Meta: make(map[string]any),\n                                }\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                r.Node = make(map[string]any)\n                        }</span>\n                }),\n\n                \"@map-bo-jsonic\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if v, ok := r.N[\"dmap\"]; ok </span><span class=\"cov8\" title=\"1\">{\n                                r.N[\"dmap\"] = v + 1\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                r.N[\"dmap\"] = 1\n                        }</span>\n                }),\n\n                \"@map-bc\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if cfg.MapRef </span><span class=\"cov8\" title=\"1\">{\n                                implicit := !(r.O0 != NoToken &amp;&amp; r.O0.Tin == TinOB)\n                                if mr, ok := r.Node.(MapRef); ok </span><span class=\"cov8\" title=\"1\">{\n                                        mr.Implicit = implicit\n                                        r.Node = mr\n                                }</span>\n                        }\n                }),\n\n                // list rule state actions\n                \"@list-bo\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if cfg.ListRef </span><span class=\"cov8\" title=\"1\">{\n                                r.Node = ListRef{\n                                        Val:  make([]any, 0),\n                                        Meta: make(map[string]any),\n                                }\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                r.Node = make([]any, 0)\n                        }</span>\n                }),\n\n                \"@list-bo-jsonic\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if v, ok := r.N[\"dlist\"]; ok </span><span class=\"cov8\" title=\"1\">{\n                                r.N[\"dlist\"] = v + 1\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                r.N[\"dlist\"] = 1\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">if r.Prev != NoRule &amp;&amp; r.Prev != nil </span><span class=\"cov8\" title=\"1\">{\n                                if implist, ok := r.Prev.U[\"implist\"]; ok &amp;&amp; implist == true </span><span class=\"cov8\" title=\"1\">{\n                                        prevNode := r.Prev.Node\n                                        if IsUndefined(prevNode) </span><span class=\"cov0\" title=\"0\">{\n                                                prevNode = nil\n                                        }</span>\n                                        <span class=\"cov8\" title=\"1\">r.Node = nodeListAppend(r.Node, prevNode)\n                                        r.Prev.Node = r.Node</span>\n                                }\n                        }\n                }),\n\n                \"@list-bc\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if cfg.ListRef </span><span class=\"cov8\" title=\"1\">{\n                                implicit := !(r.O0 != NoToken &amp;&amp; r.O0.Tin == TinOS)\n                                if lr, ok := r.Node.(ListRef); ok </span><span class=\"cov8\" title=\"1\">{\n                                        lr.Implicit = implicit\n                                        if c, ok := r.U[\"child$\"]; ok </span><span class=\"cov8\" title=\"1\">{\n                                                lr.Child = c\n                                        }</span>\n                                        <span class=\"cov8\" title=\"1\">r.Node = lr</span>\n                                }\n                        }\n                }),\n\n                // pair rule state actions\n                \"@pair-bc-json\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        if _, ok := r.U[\"pair\"]; ok </span><span class=\"cov8\" title=\"1\">{\n                                key, _ := r.U[\"key\"].(string)\n                                if cfg.SafeKey &amp;&amp; r.U[\"list\"] == true &amp;&amp; (key == \"__proto__\" || key == \"constructor\") </span><span class=\"cov0\" title=\"0\">{\n                                        return\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">r.U[\"prev\"] = nodeMapGetVal(r.Node, r.U[\"key\"])\n                                nodeMapSet(r.Node, key, r.Child.Node)</span>\n                        }\n                }),\n\n                \"@pair-bc-jsonic\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        if _, ok := r.U[\"pair\"]; ok </span><span class=\"cov8\" title=\"1\">{\n                                key, _ := r.U[\"key\"].(string)\n                                val := r.Child.Node\n                                if IsUndefined(val) </span><span class=\"cov8\" title=\"1\">{\n                                        val = nil\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">if cfg.SafeKey &amp;&amp; r.U[\"list\"] == true </span><span class=\"cov0\" title=\"0\">{\n                                        if key == \"__proto__\" || key == \"constructor\" </span><span class=\"cov0\" title=\"0\">{\n                                                return\n                                        }</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">prev := r.U[\"prev\"]\n                                if prev == nil </span><span class=\"cov8\" title=\"1\">{\n                                        nodeMapSet(r.Node, key, val)\n                                }</span> else<span class=\"cov8\" title=\"1\"> if cfg.MapMerge != nil </span><span class=\"cov8\" title=\"1\">{\n                                        nodeMapSet(r.Node, key, cfg.MapMerge(prev, val, r, ctx))\n                                }</span> else<span class=\"cov8\" title=\"1\"> if cfg.MapExtend </span><span class=\"cov8\" title=\"1\">{\n                                        nodeMapSet(r.Node, key, Deep(prev, val))\n                                }</span> else<span class=\"cov8\" title=\"1\"> {\n                                        nodeMapSet(r.Node, key, val)\n                                }</span>\n                        }\n                }),\n\n                \"@pair-bc-child\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        if childFlag, ok := r.U[\"child\"]; !ok || childFlag != true </span><span class=\"cov8\" title=\"1\">{\n                                return\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">val := r.Child.Node\n                        if IsUndefined(val) </span><span class=\"cov0\" title=\"0\">{\n                                val = nil\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">prev, hasPrev := nodeMapGet(r.Node, \"child$\")\n                        if !hasPrev </span><span class=\"cov8\" title=\"1\">{\n                                nodeMapSet(r.Node, \"child$\", val)\n                        }</span> else<span class=\"cov8\" title=\"1\"> if cfg.MapMerge != nil </span><span class=\"cov0\" title=\"0\">{\n                                nodeMapSet(r.Node, \"child$\", cfg.MapMerge(prev, val, r, ctx))\n                        }</span> else<span class=\"cov8\" title=\"1\"> if cfg.MapExtend </span><span class=\"cov8\" title=\"1\">{\n                                nodeMapSet(r.Node, \"child$\", Deep(prev, val))\n                        }</span> else<span class=\"cov0\" title=\"0\"> {\n                                nodeMapSet(r.Node, \"child$\", val)\n                        }</span>\n                }),\n\n                // elem rule state actions\n                \"@elem-bc-json\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        done, _ := r.U[\"done\"].(bool)\n                        if !done &amp;&amp; !IsUndefined(r.Child.Node) </span><span class=\"cov8\" title=\"1\">{\n                                if _, ok := nodeListVal(r.Node); ok </span><span class=\"cov8\" title=\"1\">{\n                                        r.Node = nodeListAppend(r.Node, r.Child.Node)\n                                        if r.Parent != NoRule &amp;&amp; r.Parent != nil </span><span class=\"cov8\" title=\"1\">{\n                                                r.Parent.Node = r.Node\n                                        }</span>\n                                }\n                        }\n                }),\n\n                \"@elem-bc-pair\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        if pair, ok := r.U[\"pair\"]; !ok || pair != true </span><span class=\"cov8\" title=\"1\">{\n                                return\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">if cfg.ListPair </span><span class=\"cov8\" title=\"1\">{\n                                key := r.U[\"key\"].(string)\n                                val := r.Child.Node\n                                if IsUndefined(val) </span><span class=\"cov0\" title=\"0\">{\n                                        val = nil\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">pairObj := map[string]any{key: val}\n                                if _, ok := nodeListVal(r.Node); ok </span><span class=\"cov8\" title=\"1\">{\n                                        r.Node = nodeListAppend(r.Node, pairObj)\n                                        if r.Parent != NoRule &amp;&amp; r.Parent != nil </span><span class=\"cov8\" title=\"1\">{\n                                                r.Parent.Node = r.Node\n                                        }</span>\n                                }\n                        } else<span class=\"cov8\" title=\"1\"> {\n                                r.U[\"prev\"] = nodeMapGetVal(r.Node, r.U[\"key\"])\n                                key, _ := r.U[\"key\"].(string)\n                                val := r.Child.Node\n                                if IsUndefined(val) </span><span class=\"cov0\" title=\"0\">{\n                                        val = nil\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">if cfg.SafeKey &amp;&amp; r.U[\"list\"] == true </span><span class=\"cov8\" title=\"1\">{\n                                        if key == \"__proto__\" || key == \"constructor\" </span><span class=\"cov8\" title=\"1\">{\n                                                return\n                                        }</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">prev := r.U[\"prev\"]\n                                if prev == nil </span><span class=\"cov8\" title=\"1\">{\n                                        nodeMapSet(r.Node, key, val)\n                                }</span> else<span class=\"cov0\" title=\"0\"> if cfg.MapMerge != nil </span><span class=\"cov0\" title=\"0\">{\n                                        nodeMapSet(r.Node, key, cfg.MapMerge(prev, val, r, ctx))\n                                }</span> else<span class=\"cov0\" title=\"0\"> if cfg.MapExtend </span><span class=\"cov0\" title=\"0\">{\n                                        nodeMapSet(r.Node, key, Deep(prev, val))\n                                }</span> else<span class=\"cov0\" title=\"0\"> {\n                                        nodeMapSet(r.Node, key, val)\n                                }</span>\n                        }\n                }),\n\n                \"@elem-bc-child\": StateAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if childFlag, ok := r.U[\"child\"]; !ok || childFlag != true </span><span class=\"cov8\" title=\"1\">{\n                                return\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">val := r.Child.Node\n                        if IsUndefined(val) </span><span class=\"cov0\" title=\"0\">{\n                                val = nil\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">if r.Parent != NoRule &amp;&amp; r.Parent != nil </span><span class=\"cov8\" title=\"1\">{\n                                prev, hasPrev := r.Parent.U[\"child$\"]\n                                if !hasPrev </span><span class=\"cov8\" title=\"1\">{\n                                        r.Parent.U[\"child$\"] = val\n                                }</span> else<span class=\"cov8\" title=\"1\"> if cfg.MapExtend </span><span class=\"cov8\" title=\"1\">{\n                                        r.Parent.U[\"child$\"] = Deep(prev, val)\n                                }</span> else<span class=\"cov8\" title=\"1\"> {\n                                        r.Parent.U[\"child$\"] = val\n                                }</span>\n                        }\n                }),\n\n                // Inline actions used in alts\n                \"@val-close-err\": AltError(func(r *Rule, ctx *Context) *Token <span class=\"cov8\" title=\"1\">{\n                        if r.D == 0 </span><span class=\"cov0\" title=\"0\">{\n                                return ctx.T0\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">return nil</span>\n                }),\n\n                \"@implist-cond\": AltCond(func(r *Rule, ctx *Context) bool <span class=\"cov8\" title=\"1\">{\n                        return r.Prev != NoRule &amp;&amp; r.Prev != nil &amp;&amp; r.Prev.U[\"implist\"] == true\n                }</span>),\n\n                \"@elem-double-comma\": AltAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if _, ok := nodeListVal(r.Node); ok </span><span class=\"cov8\" title=\"1\">{\n                                r.Node = nodeListAppend(r.Node, nil)\n                                if r.Parent != NoRule &amp;&amp; r.Parent != nil </span><span class=\"cov8\" title=\"1\">{\n                                        r.Parent.Node = r.Node\n                                }</span>\n                        }\n                }),\n\n                \"@elem-single-comma\": AltAction(func(r *Rule, ctx *Context) <span class=\"cov8\" title=\"1\">{\n                        _ = ctx\n                        if _, ok := nodeListVal(r.Node); ok </span><span class=\"cov8\" title=\"1\">{\n                                r.Node = nodeListAppend(r.Node, nil)\n                                if r.Parent != NoRule &amp;&amp; r.Parent != nil </span><span class=\"cov8\" title=\"1\">{\n                                        r.Parent.Node = r.Node\n                                }</span>\n                        }\n                }),\n\n                \"@elem-pair-err\": AltError(func(r *Rule, ctx *Context) *Token <span class=\"cov8\" title=\"1\">{\n                        if cfg.ListProperty || cfg.ListPair </span><span class=\"cov8\" title=\"1\">{\n                                return nil\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">return ctx.T0</span>\n                }),\n\n                \"@elem-close-err\": AltError(func(r *Rule, ctx *Context) *Token <span class=\"cov0\" title=\"0\">{\n                        return r.C0\n                }</span>),\n        }\n\n        // Helper to resolve a GrammarAltSpec slice to []*AltSpec.\n        <span class=\"cov8\" title=\"1\">resolve := func(gas []*GrammarAltSpec) []*AltSpec </span><span class=\"cov8\" title=\"1\">{\n                alts := make([]*AltSpec, len(gas))\n                for i, ga := range gas </span><span class=\"cov8\" title=\"1\">{\n                        alts[i] = resolveGrammarAltStatic(ga, ref)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return alts</span>\n        }\n\n        // ====== VAL rule ======\n        <span class=\"cov8\" title=\"1\">valSpec := &amp;RuleSpec{Name: \"val\"}\n\n        valSpec.BO = []StateAction{ref[\"@val-bo\"].(StateAction)}\n        valSpec.BC = []StateAction{ref[\"@val-bc\"].(StateAction)}\n\n        valOpen := resolve([]*GrammarAltSpec{\n                {S: \"#OB\", P: \"map\", B: 1, G: \"map,json\"},\n                {S: \"#OS\", P: \"list\", B: 1, G: \"list,json\"},\n                {S: \"#KEY #CL\", C: map[string]any{\"d\": 0}, P: \"map\", B: 2, G: \"pair,jsonic,top\"},\n                {S: \"#KEY #CL\", P: \"map\", B: 2, N: map[string]int{\"pk\": 1}, G: \"pair,jsonic\"},\n                {S: \"#VAL\", G: \"val,json\"},\n        })\n        // CB|CS in single slot:\n        valOpen = append(valOpen, resolveGrammarAltStatic(\n                &amp;GrammarAltSpec{S: []string{\"#CB #CS\"}, C: map[string]any{\"d\": CGt(0)}, B: 1, G: \"val,imp,null,jsonic\"}, ref))\n        valOpen = append(valOpen, resolve([]*GrammarAltSpec{\n                {S: \"#CA\", C: map[string]any{\"d\": 0}, P: \"list\", B: 1, G: \"list,imp,jsonic\"},\n                {S: \"#CA\", B: 1, G: \"list,val,imp,null,jsonic\"},\n                {S: \"#ZZ\", G: \"jsonic\"},\n        })...)\n        valSpec.Open = valOpen\n\n        valClose := resolve([]*GrammarAltSpec{\n                {S: \"#ZZ\", G: \"end,json\"},\n        })\n        // CB|CS in single slot:\n        valClose = append(valClose, resolveGrammarAltStatic(\n                &amp;GrammarAltSpec{S: []string{\"#CB #CS\"}, B: 1, E: \"@val-close-err\", G: \"val,json,close\"}, ref))\n        valClose = append(valClose, resolve([]*GrammarAltSpec{\n                {S: \"#CA\", C: map[string]any{\"n.dlist\": CLte(0), \"n.dmap\": CLte(0)},\n                        R: \"list\", U: map[string]any{\"implist\": true}, G: \"list,val,imp,comma,jsonic\"},\n                {C: map[string]any{\"n.dlist\": CLte(0), \"n.dmap\": CLte(0)},\n                        R: \"list\", U: map[string]any{\"implist\": true}, B: 1, G: \"list,val,imp,space,jsonic\"},\n                {S: \"#ZZ\", G: \"jsonic\"},\n                {B: 1, G: \"more,json\"},\n        })...)\n        valSpec.Close = valClose\n\n        // ====== MAP rule ======\n        mapSpec := &amp;RuleSpec{Name: \"map\"}\n\n        mapSpec.BO = []StateAction{\n                ref[\"@map-bo\"].(StateAction),\n                ref[\"@map-bo-jsonic\"].(StateAction),\n        }\n        mapSpec.BC = []StateAction{ref[\"@map-bc\"].(StateAction)}\n\n        mapSpec.Open = resolve([]*GrammarAltSpec{\n                {S: \"#OB #ZZ\", B: 1, E: \"@finish\", G: \"end,jsonic\"},\n                {S: \"#OB #CB\", B: 1, N: map[string]int{\"pk\": 0}, G: \"map,json\"},\n                {S: \"#OB\", P: \"pair\", N: map[string]int{\"pk\": 0}, G: \"map,json,pair\"},\n                {S: \"#KEY #CL\", P: \"pair\", B: 2, G: \"pair,list,val,imp,jsonic\"},\n        })\n\n        // For map.Close, we need to merge token sets for the third alt.\n        // \"#CA #CS\" + VAL tokens in a single slot → need raw AltSpec for that one.\n        mapClose := resolve([]*GrammarAltSpec{\n                {S: \"#CB\", C: map[string]any{\"n.pk\": CLte(0)}, G: \"end,json\"},\n                {S: \"#CB\", B: 1, G: \"path,jsonic\"},\n                // slot 0 = merge(CA, CS, VAL) — handled below\n        })\n        // Third alt: CA|CS|VAL tokens in single slot\n        mapClose = append(mapClose, resolveGrammarAltStatic(\n                &amp;GrammarAltSpec{S: []string{\"#CA #CS #VAL\"}, B: 1, G: \"end,path,jsonic\"}, ref))\n        mapClose = append(mapClose, resolveGrammarAltStatic(\n                &amp;GrammarAltSpec{S: \"#ZZ\", E: \"@finish\", G: \"end,jsonic\"}, ref))\n        mapSpec.Close = mapClose\n\n        // ====== LIST rule ======\n        listSpec := &amp;RuleSpec{Name: \"list\"}\n\n        listSpec.BO = []StateAction{\n                ref[\"@list-bo\"].(StateAction),\n                ref[\"@list-bo-jsonic\"].(StateAction),\n        }\n        listSpec.BC = []StateAction{ref[\"@list-bc\"].(StateAction)}\n\n        // First alt uses a condition function directly (not declarative).\n        listOpen := []*AltSpec{\n                resolveGrammarAltStatic(&amp;GrammarAltSpec{C: \"@implist-cond\", P: \"elem\"}, ref),\n        }\n        listOpen = append(listOpen, resolve([]*GrammarAltSpec{\n                {S: \"#OS #CS\", B: 1, G: \"list,json\"},\n                {S: \"#OS\", P: \"elem\", G: \"list,elem,json\"},\n                {S: \"#CA\", P: \"elem\", B: 1, G: \"list,elem,val,imp,jsonic\"},\n                {P: \"elem\", G: \"list,elem,jsonic\"},\n        })...)\n        listSpec.Open = listOpen\n\n        listSpec.Close = resolve([]*GrammarAltSpec{\n                {S: \"#CS\", G: \"end,json\"},\n                {S: \"#ZZ\", E: \"@finish\", G: \"end,jsonic\"},\n        })\n\n        // ====== PAIR rule ======\n        pairSpec := &amp;RuleSpec{Name: \"pair\"}\n\n        pairSpec.BC = []StateAction{\n                ref[\"@pair-bc-json\"].(StateAction),\n                ref[\"@pair-bc-jsonic\"].(StateAction),\n                ref[\"@pair-bc-child\"].(StateAction),\n        }\n\n        pairOpen := resolve([]*GrammarAltSpec{\n                {S: \"#KEY #CL\", P: \"val\", U: map[string]any{\"pair\": true}, A: \"@pairkey\", G: \"map,pair,key,json\"},\n                {S: \"#CA\", G: \"map,pair,comma,jsonic\"},\n        })\n        if cfg.MapChild </span><span class=\"cov8\" title=\"1\">{\n                pairOpen = append(pairOpen, resolveGrammarAltStatic(\n                        &amp;GrammarAltSpec{S: \"#CL\", P: \"val\",\n                                U: map[string]any{\"done\": true, \"child\": true}}, ref))\n        }</span>\n        <span class=\"cov8\" title=\"1\">pairSpec.Open = pairOpen\n\n        pairSpec.Close = resolve([]*GrammarAltSpec{\n                {S: \"#CB\", C: map[string]any{\"n.pk\": CLte(0)}, B: 1, G: \"map,pair,json\"},\n                {S: \"#CA #CB\", C: map[string]any{\"n.pk\": CLte(0)}, B: 1, G: \"map,pair,comma,jsonic\"},\n                {S: \"#CA #ZZ\", G: \"end,jsonic\"},\n                {S: \"#CA\", C: map[string]any{\"n.pk\": CLte(0)}, R: \"pair\", G: \"map,pair,json\"},\n                {S: \"#CA\", C: map[string]any{\"n.dmap\": CLte(1)}, R: \"pair\", G: \"map,pair,jsonic\"},\n                {S: \"#KEY\", C: map[string]any{\"n.dmap\": CLte(1)}, R: \"pair\", B: 1, G: \"map,pair,imp,jsonic\"},\n        })\n\n        // CB|CA|CS|KEY in single slot\n        pairSpec.Close = append(pairSpec.Close, resolveGrammarAltStatic(\n                &amp;GrammarAltSpec{S: []string{\"#CB #CA #CS #KEY\"}, C: map[string]any{\"n.pk\": CGt(0)},\n                        B: 1, G: \"map,pair,imp,path,jsonic\"}, ref))\n        // Remaining pair close alts.\n        pairSpec.Close = append(pairSpec.Close, resolve([]*GrammarAltSpec{\n                {S: \"#CS\", E: \"@elem-close-err\", G: \"end,jsonic\"},\n                {S: \"#ZZ\", E: \"@finish\", G: \"map,pair,json\"},\n                {R: \"pair\", B: 1, G: \"map,pair,imp,jsonic\"},\n        })...)\n\n        // ====== ELEM rule ======\n        elemSpec := &amp;RuleSpec{Name: \"elem\"}\n\n        elemSpec.BC = []StateAction{\n                ref[\"@elem-bc-json\"].(StateAction),\n                ref[\"@elem-bc-pair\"].(StateAction),\n                ref[\"@elem-bc-child\"].(StateAction),\n        }\n\n        elemOpen := resolve([]*GrammarAltSpec{\n                {S: \"#CA #CA\", B: 2, U: map[string]any{\"done\": true}, A: \"@elem-double-comma\",\n                        G: \"list,elem,imp,null,jsonic\"},\n                {S: \"#CA\", U: map[string]any{\"done\": true}, A: \"@elem-single-comma\",\n                        G: \"list,elem,imp,null,jsonic\"},\n                {S: \"#KEY #CL\", P: \"val\",\n                        N: map[string]int{\"pk\": 1, \"dmap\": 1},\n                        U: map[string]any{\"done\": true, \"pair\": true, \"list\": true},\n                        A: \"@pairkey\", E: \"@elem-pair-err\", G: \"elem,pair,jsonic\"},\n        })\n        if cfg.ListChild </span><span class=\"cov8\" title=\"1\">{\n                elemOpen = append(elemOpen, resolveGrammarAltStatic(\n                        &amp;GrammarAltSpec{S: \"#CL\", P: \"val\",\n                                U: map[string]any{\"done\": true, \"child\": true, \"list\": true},\n                                G: \"elem,child,jsonic\"}, ref))\n        }</span>\n        <span class=\"cov8\" title=\"1\">elemOpen = append(elemOpen, resolveGrammarAltStatic(\n                &amp;GrammarAltSpec{P: \"val\", G: \"list,elem,val,json\"}, ref))\n        elemSpec.Open = elemOpen\n\n        elemClose := []*AltSpec{\n                // CA in slot 0, CS|ZZ in slot 1:\n                resolveGrammarAltStatic(&amp;GrammarAltSpec{S: []string{\"#CA\", \"#CS #ZZ\"}, B: 1, G: \"list,elem,comma,jsonic\"}, ref),\n        }\n        elemClose = append(elemClose, resolve([]*GrammarAltSpec{\n                {S: \"#CA\", R: \"elem\", G: \"list,elem,json\"},\n                {S: \"#CS\", B: 1, G: \"list,elem,json\"},\n                {S: \"#ZZ\", E: \"@finish\", G: \"list,elem,json\"},\n                {S: \"#CB\", E: \"@elem-close-err\", G: \"end,jsonic\"},\n                {R: \"elem\", B: 1, G: \"list,elem,imp,jsonic\"},\n        })...)\n        elemSpec.Close = elemClose\n\n        rsm[\"val\"] = valSpec\n        rsm[\"map\"] = mapSpec\n        rsm[\"list\"] = listSpec\n        rsm[\"pair\"] = pairSpec\n        rsm[\"elem\"] = elemSpec</span>\n}\n\n// nodeListAppend appends a value to a list node (plain []any or ListRef).\nfunc nodeListAppend(node any, val any) any <span class=\"cov8\" title=\"1\">{\n        if lr, ok := node.(ListRef); ok </span><span class=\"cov8\" title=\"1\">{\n                lr.Val = append(lr.Val, val)\n                return lr\n        }</span>\n        <span class=\"cov8\" title=\"1\">if arr, ok := node.([]any); ok </span><span class=\"cov8\" title=\"1\">{\n                return append(arr, val)\n        }</span>\n        <span class=\"cov0\" title=\"0\">return node</span>\n}\n\n// nodeListVal extracts the []any from a list node.\nfunc nodeListVal(node any) ([]any, bool) <span class=\"cov8\" title=\"1\">{\n        if lr, ok := node.(ListRef); ok </span><span class=\"cov8\" title=\"1\">{\n                return lr.Val, true\n        }</span>\n        <span class=\"cov8\" title=\"1\">if arr, ok := node.([]any); ok </span><span class=\"cov8\" title=\"1\">{\n                return arr, true\n        }</span>\n        <span class=\"cov0\" title=\"0\">return nil, false</span>\n}\n\n// nodeListSetVal updates the []any inside a list node.\nfunc nodeListSetVal(node any, arr []any) any <span class=\"cov0\" title=\"0\">{\n        if lr, ok := node.(ListRef); ok </span><span class=\"cov0\" title=\"0\">{\n                lr.Val = arr\n                return lr\n        }</span>\n        <span class=\"cov0\" title=\"0\">return arr</span>\n}\n\n// nodeMapSet sets a key on a map node.\nfunc nodeMapSet(node any, key any, val any) <span class=\"cov8\" title=\"1\">{\n        k, _ := key.(string)\n        if m, ok := node.(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                m[k] = val\n        }</span> else<span class=\"cov8\" title=\"1\"> if mr, ok := node.(MapRef); ok </span><span class=\"cov8\" title=\"1\">{\n                mr.Val[k] = val\n        }</span>\n}\n\n// nodeMapGet gets a value from a map node.\nfunc nodeMapGet(node any, key any) (any, bool) <span class=\"cov8\" title=\"1\">{\n        k, _ := key.(string)\n        if m, ok := node.(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                v, exists := m[k]\n                return v, exists\n        }</span>\n        <span class=\"cov8\" title=\"1\">if mr, ok := node.(MapRef); ok </span><span class=\"cov8\" title=\"1\">{\n                v, exists := mr.Val[k]\n                return v, exists\n        }</span>\n        <span class=\"cov8\" title=\"1\">return nil, false</span>\n}\n\n// nodeMapGetVal returns the value or nil.\nfunc nodeMapGetVal(node any, key any) any <span class=\"cov8\" title=\"1\">{\n        v, _ := nodeMapGet(node, key)\n        return v\n}</span>\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file2\" style=\"display: none\">package jsonic\n\nimport (\n        \"fmt\"\n        \"strings\"\n)\n\n// FuncRef is a string starting with \"@\" that references a function in a Ref map.\ntype FuncRef = string\n\n// GrammarSpec defines a declarative grammar specification.\n// Mirrors the TypeScript GrammarSpec type.\ntype GrammarSpec struct {\n        // Ref maps FuncRef strings (like \"@finish\") to Go functions.\n        Ref map[FuncRef]any\n\n        // Options to merge into the Jsonic instance before processing rules.\n        // Applied via SetOptions.\n        Options *Options\n\n        // OptionsMap is an alternative to Options that accepts a map[string]any.\n        // FuncRef strings in values are resolved via Ref before applying.\n        // Use for JSON-serializable grammars where function fields use \"@name\" refs.\n        OptionsMap map[string]any\n\n        // Rule defines open/close alternates per rule name.\n        Rule map[string]*GrammarRuleSpec\n}\n\n// GrammarRuleSpec defines open and close alternates for a single rule.\n// Open and Close can be a plain []*GrammarAltSpec or a *GrammarAltListSpec\n// with inject modifiers (append, delete, move).\ntype GrammarRuleSpec struct {\n        Open  any // []*GrammarAltSpec or *GrammarAltListSpec\n        Close any // []*GrammarAltSpec or *GrammarAltListSpec\n}\n\n// GrammarAltListSpec wraps alt specs with injection modifiers.\ntype GrammarAltListSpec struct {\n        Alts   []*GrammarAltSpec\n        Inject *GrammarInjectSpec\n}\n\n// GrammarInjectSpec controls how alts are merged into existing rule alternates.\ntype GrammarInjectSpec struct {\n        Append bool  // If true, append; if false, prepend (default).\n        Delete []int // Indices to delete (supports negative).\n        Move   []int // Pairs: [from, to, from, to, ...].\n}\n\n// GrammarAltSpec is a declarative alternate specification using string references.\n// Token fields use \"#NAME\" strings resolved via Token/TokenSet lookup.\n// Function fields use \"@name\" FuncRef strings resolved via the Ref map.\ntype GrammarAltSpec struct {\n        // Token spec. Either a string or []string.\n        //   string:   \"#OB\", \"#KEY #CL\" — each space-separated name is a slot.\n        //   []string: [\"#CB #CS\"] — each element is a slot; within an element,\n        //             space-separated names are alternatives for that slot.\n        S any\n        B any             // Backtrack: int or FuncRef string\n        P string          // Push rule name or FuncRef\n        R string          // Replace rule name or FuncRef\n        A FuncRef         // Action function ref\n        E FuncRef         // Error function ref\n        H FuncRef         // Modifier function ref\n        C any             // Condition: FuncRef string or map[string]any for declarative\n        N map[string]int  // Counter increments\n        U map[string]any  // Custom props\n        K map[string]any  // Propagated custom props\n        G string          // Group tags (comma-separated)\n}\n\n// Grammar applies a declarative grammar specification to this Jsonic instance.\n// Options are applied first, then rules are processed.\n// Returns an error if any FuncRef is missing or has the wrong type.\nfunc (j *Jsonic) Grammar(gs *GrammarSpec) error <span class=\"cov8\" title=\"1\">{\n        // Apply typed Options directly.\n        if gs.Options != nil </span><span class=\"cov8\" title=\"1\">{\n                j.SetOptions(*gs.Options)\n        }</span>\n\n        // Apply OptionsMap with FuncRef resolution.\n        <span class=\"cov8\" title=\"1\">if gs.OptionsMap != nil </span><span class=\"cov8\" title=\"1\">{\n                resolved := ResolveFuncRefs(gs.OptionsMap, gs.Ref)\n                if resolvedMap, ok := resolved.(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                        opts := MapToOptions(resolvedMap)\n                        j.SetOptions(opts)\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if gs.Rule != nil </span><span class=\"cov8\" title=\"1\">{\n                for rulename, rulespec := range gs.Rule </span><span class=\"cov8\" title=\"1\">{\n                        ref := gs.Ref\n                        var resolveErr error\n                        j.Rule(rulename, func(rs *RuleSpec) </span><span class=\"cov8\" title=\"1\">{\n                                // Process Open alts.\n                                if rulespec.Open != nil </span><span class=\"cov8\" title=\"1\">{\n                                        if err := applyGrammarAlts(j, rs, rulespec.Open, ref, true); err != nil </span><span class=\"cov0\" title=\"0\">{\n                                                resolveErr = err\n                                                return\n                                        }</span>\n                                }\n\n                                // Process Close alts.\n                                <span class=\"cov8\" title=\"1\">if rulespec.Close != nil </span><span class=\"cov8\" title=\"1\">{\n                                        if err := applyGrammarAlts(j, rs, rulespec.Close, ref, false); err != nil </span><span class=\"cov8\" title=\"1\">{\n                                                resolveErr = err\n                                                return\n                                        }</span>\n                                }\n\n                                // Auto-wire reserved FuncRef names for state actions.\n                                <span class=\"cov8\" title=\"1\">if ref != nil </span><span class=\"cov8\" title=\"1\">{\n                                        wireStateActions(rs, ref)\n                                }</span>\n                        })\n                        <span class=\"cov8\" title=\"1\">if resolveErr != nil </span><span class=\"cov8\" title=\"1\">{\n                                return resolveErr\n                        }</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">return nil</span>\n}\n\n// applyGrammarAlts resolves and applies grammar alts to a rule spec.\n// Handles both plain []*GrammarAltSpec and *GrammarAltListSpec with inject.\nfunc applyGrammarAlts(j *Jsonic, rs *RuleSpec, spec any, ref map[FuncRef]any, isOpen bool) error <span class=\"cov8\" title=\"1\">{\n        var gas []*GrammarAltSpec\n        var inject *GrammarInjectSpec\n\n        switch v := spec.(type) </span>{\n        case []*GrammarAltSpec:<span class=\"cov8\" title=\"1\">\n                gas = v</span>\n        case *GrammarAltListSpec:<span class=\"cov8\" title=\"1\">\n                gas = v.Alts\n                inject = v.Inject</span>\n        default:<span class=\"cov0\" title=\"0\">\n                return nil</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">resolved, err := j.resolveGrammarAlts(gas, ref)\n        if err != nil </span><span class=\"cov8\" title=\"1\">{\n                return err\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">dest := &amp;rs.Close\n        if isOpen </span><span class=\"cov8\" title=\"1\">{\n                dest = &amp;rs.Open\n        }</span>\n\n        // Apply inject modifiers (delete, move) to existing alts first.\n        <span class=\"cov8\" title=\"1\">if inject != nil &amp;&amp; (len(inject.Delete) &gt; 0 || len(inject.Move) &gt; 0) </span><span class=\"cov0\" title=\"0\">{\n                *dest = modifyAltList(*dest, &amp;AltModListOpts{\n                        Delete: inject.Delete,\n                        Move:   inject.Move,\n                })\n        }</span>\n\n        // Insert resolved alts: append or prepend (default: prepend).\n        <span class=\"cov8\" title=\"1\">if inject != nil &amp;&amp; inject.Append </span><span class=\"cov8\" title=\"1\">{\n                *dest = append(*dest, resolved...)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                *dest = append(resolved, *dest...)\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return nil</span>\n}\n\n// resolveGrammarAlts converts a slice of GrammarAltSpec to concrete AltSpec.\nfunc (j *Jsonic) resolveGrammarAlts(gas []*GrammarAltSpec, ref map[FuncRef]any) ([]*AltSpec, error) <span class=\"cov8\" title=\"1\">{\n        alts := make([]*AltSpec, 0, len(gas))\n        for _, ga := range gas </span><span class=\"cov8\" title=\"1\">{\n                alt, err := j.resolveGrammarAlt(ga, ref)\n                if err != nil </span><span class=\"cov8\" title=\"1\">{\n                        return nil, err\n                }</span>\n                <span class=\"cov8\" title=\"1\">alts = append(alts, alt)</span>\n        }\n        <span class=\"cov8\" title=\"1\">return alts, nil</span>\n}\n\n// resolveGrammarAlt converts a single GrammarAltSpec to a concrete AltSpec.\nfunc (j *Jsonic) resolveGrammarAlt(ga *GrammarAltSpec, ref map[FuncRef]any) (*AltSpec, error) <span class=\"cov8\" title=\"1\">{\n        alt := &amp;AltSpec{}\n\n        // Resolve S (token spec: string or []string → [][]Tin)\n        if ga.S != nil </span><span class=\"cov8\" title=\"1\">{\n                alt.S = j.resolveTokenField(ga.S)\n        }</span>\n\n        // Resolve B (backtrack: int or FuncRef)\n        <span class=\"cov8\" title=\"1\">switch v := ga.B.(type) </span>{\n        case int:<span class=\"cov0\" title=\"0\">\n                alt.B = v</span>\n        case float64:<span class=\"cov0\" title=\"0\">\n                alt.B = int(v)</span>\n        case string:<span class=\"cov0\" title=\"0\">\n                fn, err := RequireRef(ref, v, \"backtrack\")\n                if err != nil </span><span class=\"cov0\" title=\"0\">{\n                        return nil, err\n                }</span>\n                <span class=\"cov0\" title=\"0\">if bf, ok := fn.(func(*Rule, *Context) int); ok </span><span class=\"cov0\" title=\"0\">{\n                        alt.BF = bf\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        return nil, fmt.Errorf(\"Grammar: ref %q is not a backtrack function\", v)\n                }</span>\n        }\n\n        // Resolve P (push: rule name or FuncRef)\n        <span class=\"cov8\" title=\"1\">if ga.P != \"\" </span><span class=\"cov0\" title=\"0\">{\n                if IsFuncRef(ga.P) </span><span class=\"cov0\" title=\"0\">{\n                        fn, err := RequireRef(ref, ga.P, \"push\")\n                        if err != nil </span><span class=\"cov0\" title=\"0\">{\n                                return nil, err\n                        }</span>\n                        <span class=\"cov0\" title=\"0\">if pf, ok := fn.(func(*Rule, *Context) string); ok </span><span class=\"cov0\" title=\"0\">{\n                                alt.PF = pf\n                        }</span> else<span class=\"cov0\" title=\"0\"> {\n                                return nil, fmt.Errorf(\"Grammar: ref %q is not a push function\", ga.P)\n                        }</span>\n                } else<span class=\"cov0\" title=\"0\"> {\n                        alt.P = ga.P\n                }</span>\n        }\n\n        // Resolve R (replace: rule name or FuncRef)\n        <span class=\"cov8\" title=\"1\">if ga.R != \"\" </span><span class=\"cov0\" title=\"0\">{\n                if IsFuncRef(ga.R) </span><span class=\"cov0\" title=\"0\">{\n                        fn, err := RequireRef(ref, ga.R, \"replace\")\n                        if err != nil </span><span class=\"cov0\" title=\"0\">{\n                                return nil, err\n                        }</span>\n                        <span class=\"cov0\" title=\"0\">if rf, ok := fn.(func(*Rule, *Context) string); ok </span><span class=\"cov0\" title=\"0\">{\n                                alt.RF = rf\n                        }</span> else<span class=\"cov0\" title=\"0\"> {\n                                return nil, fmt.Errorf(\"Grammar: ref %q is not a replace function\", ga.R)\n                        }</span>\n                } else<span class=\"cov0\" title=\"0\"> {\n                        alt.R = ga.R\n                }</span>\n        }\n\n        // Resolve A (action)\n        <span class=\"cov8\" title=\"1\">if ga.A != \"\" </span><span class=\"cov8\" title=\"1\">{\n                fn, err := RequireRef(ref, ga.A, \"action\")\n                if err != nil </span><span class=\"cov8\" title=\"1\">{\n                        return nil, err\n                }</span>\n                <span class=\"cov8\" title=\"1\">if af, ok := fn.(AltAction); ok </span><span class=\"cov8\" title=\"1\">{\n                        alt.A = af\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        return nil, fmt.Errorf(\"Grammar: ref %q is not an AltAction\", ga.A)\n                }</span>\n        }\n\n        // Resolve E (error)\n        <span class=\"cov8\" title=\"1\">if ga.E != \"\" </span><span class=\"cov0\" title=\"0\">{\n                fn, err := RequireRef(ref, ga.E, \"error\")\n                if err != nil </span><span class=\"cov0\" title=\"0\">{\n                        return nil, err\n                }</span>\n                <span class=\"cov0\" title=\"0\">if ef, ok := fn.(AltError); ok </span><span class=\"cov0\" title=\"0\">{\n                        alt.E = ef\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        return nil, fmt.Errorf(\"Grammar: ref %q is not an AltError\", ga.E)\n                }</span>\n        }\n\n        // Resolve H (modifier)\n        <span class=\"cov8\" title=\"1\">if ga.H != \"\" </span><span class=\"cov0\" title=\"0\">{\n                fn, err := RequireRef(ref, ga.H, \"modifier\")\n                if err != nil </span><span class=\"cov0\" title=\"0\">{\n                        return nil, err\n                }</span>\n                <span class=\"cov0\" title=\"0\">if hf, ok := fn.(AltModifier); ok </span><span class=\"cov0\" title=\"0\">{\n                        alt.H = hf\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        return nil, fmt.Errorf(\"Grammar: ref %q is not an AltModifier\", ga.H)\n                }</span>\n        }\n\n        // Resolve C (condition: FuncRef or declarative map)\n        <span class=\"cov8\" title=\"1\">switch cv := ga.C.(type) </span>{\n        case string:<span class=\"cov8\" title=\"1\">\n                fn, err := RequireRef(ref, cv, \"condition\")\n                if err != nil </span><span class=\"cov0\" title=\"0\">{\n                        return nil, err\n                }</span>\n                <span class=\"cov8\" title=\"1\">if cf, ok := fn.(AltCond); ok </span><span class=\"cov8\" title=\"1\">{\n                        alt.C = cf\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        return nil, fmt.Errorf(\"Grammar: ref %q is not an AltCond\", cv)\n                }</span>\n        case map[string]any:<span class=\"cov8\" title=\"1\">\n                alt.CD = cv</span>\n        }\n\n        // Copy simple fields\n        <span class=\"cov8\" title=\"1\">if ga.N != nil </span><span class=\"cov0\" title=\"0\">{\n                alt.N = make(map[string]int, len(ga.N))\n                for k, v := range ga.N </span><span class=\"cov0\" title=\"0\">{\n                        alt.N[k] = v\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if ga.U != nil </span><span class=\"cov0\" title=\"0\">{\n                alt.U = make(map[string]any, len(ga.U))\n                for k, v := range ga.U </span><span class=\"cov0\" title=\"0\">{\n                        alt.U[k] = v\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if ga.K != nil </span><span class=\"cov0\" title=\"0\">{\n                alt.K = make(map[string]any, len(ga.K))\n                for k, v := range ga.K </span><span class=\"cov0\" title=\"0\">{\n                        alt.K[k] = v\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">alt.G = ga.G\n\n        // Normalize declarative conditions\n        NormAlt(alt)\n\n        return alt, nil</span>\n}\n\n// resolveTokenField resolves the S field of a GrammarAltSpec.\n// Accepts string or []string.\n//\n//        string:     \"#KEY #CL\" — each space-separated name is a separate slot.\n//        []string:   [\"#CB #CS\"] — each element is a slot; within an element,\n//                    space-separated names are alternatives for that slot.\nfunc (j *Jsonic) resolveTokenField(s any) [][]Tin <span class=\"cov8\" title=\"1\">{\n        switch v := s.(type) </span>{\n        case string:<span class=\"cov8\" title=\"1\">\n                if v == \"\" </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">return j.resolveTokenSpec(v)</span>\n        case []string:<span class=\"cov0\" title=\"0\">\n                result := make([][]Tin, len(v))\n                for i, slot := range v </span><span class=\"cov0\" title=\"0\">{\n                        var tins []Tin\n                        for _, name := range strings.Fields(slot) </span><span class=\"cov0\" title=\"0\">{\n                                tins = append(tins, j.resolveTokenName(name)...)\n                        }</span>\n                        <span class=\"cov0\" title=\"0\">result[i] = tins</span>\n                }\n                <span class=\"cov0\" title=\"0\">return result</span>\n        }\n        <span class=\"cov0\" title=\"0\">return nil</span>\n}\n\n// resolveTokenSpec resolves a token spec string into [][]Tin.\n// Each space-separated name becomes a separate slot.\nfunc (j *Jsonic) resolveTokenSpec(s string) [][]Tin <span class=\"cov8\" title=\"1\">{\n        parts := strings.Fields(s)\n        if len(parts) == 0 </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">result := make([][]Tin, len(parts))\n        for i, part := range parts </span><span class=\"cov8\" title=\"1\">{\n                result[i] = j.resolveTokenName(part)\n        }</span>\n        <span class=\"cov8\" title=\"1\">return result</span>\n}\n\n// resolveTokenName resolves a single token name (like \"#OB\" or \"#KEY\") to a []Tin.\nfunc (j *Jsonic) resolveTokenName(name string) []Tin <span class=\"cov8\" title=\"1\">{\n        setName := strings.TrimPrefix(name, \"#\")\n        if tins := j.TokenSet(setName); tins != nil </span><span class=\"cov0\" title=\"0\">{\n                return tins\n        }</span>\n        <span class=\"cov8\" title=\"1\">tin := j.Token(name)\n        return []Tin{tin}</span>\n}\n\n// wireStateActions auto-wires reserved FuncRef names to state action slices.\n// Names: @{rulename}-bo, @{rulename}-ao, @{rulename}-bc, @{rulename}-ac\n// Variants: /prepend prepends, /append or plain appends.\nfunc wireStateActions(rs *RuleSpec, ref map[FuncRef]any) <span class=\"cov8\" title=\"1\">{\n        type target struct {\n                suffix string\n                dest   *[]StateAction\n        }\n        targets := []target{\n                {\"bo\", &amp;rs.BO},\n                {\"ao\", &amp;rs.AO},\n                {\"bc\", &amp;rs.BC},\n                {\"ac\", &amp;rs.AC},\n        }\n        for _, t := range targets </span><span class=\"cov8\" title=\"1\">{\n                base := \"@\" + rs.Name + \"-\" + t.suffix\n                append_ := true\n                fn := ref[base+\"/prepend\"]\n                if fn != nil </span><span class=\"cov0\" title=\"0\">{\n                        append_ = false\n                }</span> else<span class=\"cov8\" title=\"1\"> {\n                        fn = ref[base+\"/append\"]\n                        if fn == nil </span><span class=\"cov8\" title=\"1\">{\n                                fn = ref[base]\n                        }</span>\n                }\n                <span class=\"cov8\" title=\"1\">if fn != nil </span><span class=\"cov8\" title=\"1\">{\n                        if sa, ok := fn.(StateAction); ok </span><span class=\"cov8\" title=\"1\">{\n                                if append_ </span><span class=\"cov8\" title=\"1\">{\n                                        *t.dest = append(*t.dest, sa)\n                                }</span> else<span class=\"cov0\" title=\"0\"> {\n                                        *t.dest = append([]StateAction{sa}, *t.dest...)\n                                }</span>\n                        }\n                }\n        }\n}\n\n// builtinTins maps standard token names to their Tin values.\nvar builtinTins = map[string]Tin{\n        \"#BD\": TinBD, \"#ZZ\": TinZZ, \"#UK\": TinUK, \"#AA\": TinAA,\n        \"#SP\": TinSP, \"#LN\": TinLN, \"#CM\": TinCM, \"#NR\": TinNR,\n        \"#ST\": TinST, \"#TX\": TinTX, \"#VL\": TinVL, \"#OB\": TinOB,\n        \"#CB\": TinCB, \"#OS\": TinOS, \"#CS\": TinCS, \"#CL\": TinCL,\n        \"#CA\": TinCA,\n}\n\n// builtinTokenSets maps standard token set names to their Tin slices.\nvar builtinTokenSets = map[string][]Tin{\n        \"VAL\": TinSetVAL,\n        \"KEY\": TinSetKEY,\n}\n\n// resolveTokenFieldStatic resolves a string or []string S field using built-in tokens.\nfunc resolveTokenFieldStatic(s any) [][]Tin <span class=\"cov8\" title=\"1\">{\n        switch v := s.(type) </span>{\n        case string:<span class=\"cov8\" title=\"1\">\n                if v == \"\" </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">return resolveTokenSpecStatic(v)</span>\n        case []string:<span class=\"cov8\" title=\"1\">\n                result := make([][]Tin, len(v))\n                for i, slot := range v </span><span class=\"cov8\" title=\"1\">{\n                        var tins []Tin\n                        for _, name := range strings.Fields(slot) </span><span class=\"cov8\" title=\"1\">{\n                                tins = append(tins, resolveTokenNameStatic(name)...)\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">result[i] = tins</span>\n                }\n                <span class=\"cov8\" title=\"1\">return result</span>\n        }\n        <span class=\"cov0\" title=\"0\">return nil</span>\n}\n\n// resolveTokenSpecStatic resolves a token spec string using built-in tokens only.\nfunc resolveTokenSpecStatic(s string) [][]Tin <span class=\"cov8\" title=\"1\">{\n        parts := strings.Fields(s)\n        if len(parts) == 0 </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">result := make([][]Tin, len(parts))\n        for i, part := range parts </span><span class=\"cov8\" title=\"1\">{\n                result[i] = resolveTokenNameStatic(part)\n        }</span>\n        <span class=\"cov8\" title=\"1\">return result</span>\n}\n\nfunc resolveTokenNameStatic(name string) []Tin <span class=\"cov8\" title=\"1\">{\n        setName := strings.TrimPrefix(name, \"#\")\n        if tins, ok := builtinTokenSets[setName]; ok </span><span class=\"cov8\" title=\"1\">{\n                result := make([]Tin, len(tins))\n                copy(result, tins)\n                return result\n        }</span>\n        <span class=\"cov8\" title=\"1\">if tin, ok := builtinTins[name]; ok </span><span class=\"cov8\" title=\"1\">{\n                return []Tin{tin}\n        }</span>\n        // Unknown tokens in static context are programming errors in the internal grammar.\n        // Return empty slice rather than panicking.\n        <span class=\"cov0\" title=\"0\">return nil</span>\n}\n\n// resolveGrammarAltStatic converts a GrammarAltSpec to a concrete AltSpec\n// using only built-in token resolution. Used by the internal Grammar().\n// Errors cause the returned alt to have nil fields (best-effort).\nfunc resolveGrammarAltStatic(ga *GrammarAltSpec, ref map[FuncRef]any) *AltSpec <span class=\"cov8\" title=\"1\">{\n        alt := &amp;AltSpec{}\n\n        if ga.S != nil </span><span class=\"cov8\" title=\"1\">{\n                alt.S = resolveTokenFieldStatic(ga.S)\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">switch v := ga.B.(type) </span>{\n        case int:<span class=\"cov8\" title=\"1\">\n                alt.B = v</span>\n        case float64:<span class=\"cov0\" title=\"0\">\n                alt.B = int(v)</span>\n        case string:<span class=\"cov0\" title=\"0\">\n                if fn := LookupRef(ref, v); fn != nil </span><span class=\"cov0\" title=\"0\">{\n                        if bf, ok := fn.(func(*Rule, *Context) int); ok </span><span class=\"cov0\" title=\"0\">{\n                                alt.BF = bf\n                        }</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">if ga.P != \"\" </span><span class=\"cov8\" title=\"1\">{\n                if IsFuncRef(ga.P) </span><span class=\"cov0\" title=\"0\">{\n                        if fn := LookupRef(ref, ga.P); fn != nil </span><span class=\"cov0\" title=\"0\">{\n                                if pf, ok := fn.(func(*Rule, *Context) string); ok </span><span class=\"cov0\" title=\"0\">{\n                                        alt.PF = pf\n                                }</span>\n                        }\n                } else<span class=\"cov8\" title=\"1\"> {\n                        alt.P = ga.P\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if ga.R != \"\" </span><span class=\"cov8\" title=\"1\">{\n                if IsFuncRef(ga.R) </span><span class=\"cov0\" title=\"0\">{\n                        if fn := LookupRef(ref, ga.R); fn != nil </span><span class=\"cov0\" title=\"0\">{\n                                if rf, ok := fn.(func(*Rule, *Context) string); ok </span><span class=\"cov0\" title=\"0\">{\n                                        alt.RF = rf\n                                }</span>\n                        }\n                } else<span class=\"cov8\" title=\"1\"> {\n                        alt.R = ga.R\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if ga.A != \"\" </span><span class=\"cov8\" title=\"1\">{\n                if fn := LookupRef(ref, ga.A); fn != nil </span><span class=\"cov8\" title=\"1\">{\n                        alt.A = fn.(AltAction)\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if ga.E != \"\" </span><span class=\"cov8\" title=\"1\">{\n                if fn := LookupRef(ref, ga.E); fn != nil </span><span class=\"cov8\" title=\"1\">{\n                        alt.E = fn.(AltError)\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if ga.H != \"\" </span><span class=\"cov0\" title=\"0\">{\n                if fn := LookupRef(ref, ga.H); fn != nil </span><span class=\"cov0\" title=\"0\">{\n                        alt.H = fn.(AltModifier)\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">switch cv := ga.C.(type) </span>{\n        case string:<span class=\"cov8\" title=\"1\">\n                if fn := LookupRef(ref, cv); fn != nil </span><span class=\"cov8\" title=\"1\">{\n                        alt.C = fn.(AltCond)\n                }</span>\n        case map[string]any:<span class=\"cov8\" title=\"1\">\n                alt.CD = cv</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if ga.N != nil </span><span class=\"cov8\" title=\"1\">{\n                alt.N = ga.N\n        }</span>\n        <span class=\"cov8\" title=\"1\">if ga.U != nil </span><span class=\"cov8\" title=\"1\">{\n                alt.U = ga.U\n        }</span>\n        <span class=\"cov8\" title=\"1\">if ga.K != nil </span><span class=\"cov0\" title=\"0\">{\n                alt.K = ga.K\n        }</span>\n        <span class=\"cov8\" title=\"1\">alt.G = ga.G\n\n        NormAlt(alt)\n        return alt</span>\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file3\" style=\"display: none\">// Package jsonic provides a lenient JSON parser that supports relaxed syntax\n// including unquoted keys, implicit objects/arrays, comments, trailing commas,\n// single-quoted strings, path diving (nested object shorthand), and more.\n//\n// It is a Go port of the jsonic TypeScript library, faithfully implementing\n// the same matcher-based lexer and rule-based parser architecture.\npackage jsonic\n\nimport (\n        \"strconv\"\n        \"strings\"\n)\n\n// Version is the current version of the jsonic Go module.\nconst Version = \"0.1.15\"\n\n// Error message templates matching TypeScript defaults.\nvar errorMessages = map[string]string{\n        \"unexpected\":           \"unexpected character(s): \",\n        \"unterminated_string\":  \"unterminated string: \",\n        \"unterminated_comment\": \"unterminated comment: \",\n        \"unknown\":              \"unknown error: \",\n}\n\n// JsonicError is the error type returned by Parse when parsing fails.\n// It includes structured details about the error location and cause.\ntype JsonicError struct {\n        Code   string // Error code: \"unterminated_string\", \"unterminated_comment\", \"unexpected\"\n        Detail string // Human-readable detail message (e.g. \"unterminated string: \\\"abc\")\n        Pos    int    // 0-based character position in source\n        Row    int    // 1-based line number\n        Col    int    // 1-based column number\n        Src    string // Source fragment at the error (the token text)\n        Hint   string // Additional explanatory text for this error code\n\n        fullSource string // Complete input source (for generating site extract)\n        tag        string // Custom error tag name (TS: errmsg.name), defaults to \"jsonic\"\n}\n\n// Error returns a formatted error message matching the TypeScript JsonicError format:\n//\n//        [jsonic/&lt;code&gt;]: &lt;message&gt;\n//          --&gt; &lt;row&gt;:&lt;col&gt;\n//         &lt;line-2&gt; | &lt;source&gt;\n//         &lt;line-1&gt; | &lt;source&gt;\n//            &lt;line&gt; | &lt;source with error&gt;\n//                     ^^^^ &lt;message&gt;\n//         &lt;line+1&gt; | &lt;source&gt;\n//         &lt;line+2&gt; | &lt;source&gt;\nfunc (e *JsonicError) Error() string <span class=\"cov8\" title=\"1\">{\n        msg := e.Detail\n\n        var b strings.Builder\n\n        // Line 1: [tag/&lt;code&gt;]: &lt;message&gt;\n        tag := e.tag\n        if tag == \"\" </span><span class=\"cov8\" title=\"1\">{\n                tag = \"jsonic\"\n        }</span>\n        <span class=\"cov8\" title=\"1\">b.WriteString(\"[\")\n        b.WriteString(tag)\n        b.WriteString(\"/\")\n        b.WriteString(e.Code)\n        b.WriteString(\"]: \")\n        b.WriteString(msg)\n\n        // Line 2: --&gt; &lt;row&gt;:&lt;col&gt;\n        b.WriteString(\"\\n  --&gt; \")\n        b.WriteString(strconv.Itoa(e.Row))\n        b.WriteString(\":\")\n        b.WriteString(strconv.Itoa(e.Col))\n\n        // Source site extract\n        if e.fullSource != \"\" </span><span class=\"cov8\" title=\"1\">{\n                site := errsite(e.fullSource, e.Src, msg, e.Row, e.Col)\n                if site != \"\" </span><span class=\"cov8\" title=\"1\">{\n                        b.WriteString(\"\\n\")\n                        b.WriteString(site)\n                }</span>\n        }\n\n        // Hint\n        <span class=\"cov8\" title=\"1\">if e.Hint != \"\" </span><span class=\"cov8\" title=\"1\">{\n                b.WriteString(\"\\n  Hint: \")\n                b.WriteString(e.Hint)\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return b.String()</span>\n}\n\n// errsite generates a source code extract showing the error location,\n// matching the TypeScript errsite() function output format.\nfunc errsite(src, sub, msg string, row, col int) string <span class=\"cov8\" title=\"1\">{\n        if row &lt; 1 </span><span class=\"cov0\" title=\"0\">{\n                row = 1\n        }</span>\n        <span class=\"cov8\" title=\"1\">if col &lt; 1 </span><span class=\"cov0\" title=\"0\">{\n                col = 1\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">lines := strings.Split(src, \"\\n\")\n\n        // row is 1-based, convert to 0-based index\n        lineIdx := row - 1\n        if lineIdx &gt;= len(lines) </span><span class=\"cov0\" title=\"0\">{\n                lineIdx = len(lines) - 1\n        }</span>\n\n        // Determine padding width based on largest line number shown\n        <span class=\"cov8\" title=\"1\">maxLineNum := row + 2\n        pad := len(strconv.Itoa(maxLineNum)) + 2\n\n        // Build context lines: 2 before, error line, caret line, 2 after\n        var result []string\n\n        ln := func(num int, text string) string </span><span class=\"cov8\" title=\"1\">{\n                numStr := strconv.Itoa(num)\n                return strings.Repeat(\" \", pad-len(numStr)) + numStr + \" | \" + text\n        }</span>\n\n        // 2 lines before\n        <span class=\"cov8\" title=\"1\">if lineIdx-2 &gt;= 0 </span><span class=\"cov8\" title=\"1\">{\n                result = append(result, ln(row-2, lines[lineIdx-2]))\n        }</span>\n        <span class=\"cov8\" title=\"1\">if lineIdx-1 &gt;= 0 </span><span class=\"cov8\" title=\"1\">{\n                result = append(result, ln(row-1, lines[lineIdx-1]))\n        }</span>\n\n        // Error line\n        <span class=\"cov8\" title=\"1\">if lineIdx &gt;= 0 &amp;&amp; lineIdx &lt; len(lines) </span><span class=\"cov8\" title=\"1\">{\n                result = append(result, ln(row, lines[lineIdx]))\n        }</span>\n\n        // Caret line\n        <span class=\"cov8\" title=\"1\">caretCount := len(sub)\n        if caretCount &lt; 1 </span><span class=\"cov0\" title=\"0\">{\n                caretCount = 1\n        }</span>\n        <span class=\"cov8\" title=\"1\">indent := strings.Repeat(\" \", pad) + \"   \" + strings.Repeat(\" \", col-1)\n        result = append(result, indent+strings.Repeat(\"^\", caretCount)+\" \"+msg)\n\n        // 2 lines after\n        if lineIdx+1 &lt; len(lines) </span><span class=\"cov8\" title=\"1\">{\n                result = append(result, ln(row+1, lines[lineIdx+1]))\n        }</span>\n        <span class=\"cov8\" title=\"1\">if lineIdx+2 &lt; len(lines) </span><span class=\"cov0\" title=\"0\">{\n                result = append(result, ln(row+2, lines[lineIdx+2]))\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return strings.Join(result, \"\\n\")</span>\n}\n\n// makeJsonicError creates a JsonicError with the proper Detail message.\nfunc makeJsonicError(code, src, fullSource string, pos, row, col int) *JsonicError <span class=\"cov8\" title=\"1\">{\n        tmpl, ok := errorMessages[code]\n        if !ok </span><span class=\"cov0\" title=\"0\">{\n                tmpl = errorMessages[\"unknown\"]\n        }</span>\n        <span class=\"cov8\" title=\"1\">detail := tmpl + src\n\n        return &amp;JsonicError{\n                Code:       code,\n                Detail:     detail,\n                Pos:        pos,\n                Row:        row,\n                Col:        col,\n                Src:        src,\n                fullSource: fullSource,\n        }</span>\n}\n\n// Parse parses a jsonic string and returns the resulting Go value.\n// The returned value can be:\n//   - map[string]any for objects\n//   - []any for arrays\n//   - float64 for numbers\n//   - string for strings\n//   - bool for booleans\n//   - nil for null or empty input\n//\n// Returns a *JsonicError if the input contains a syntax error.\nfunc Parse(src string) (any, error) <span class=\"cov8\" title=\"1\">{\n        p := NewParser()\n        return p.Start(src)\n}</span>\n\n// preprocessEscapes replaces literal backslash-n sequences with real newlines, etc.\n// This handles the case where TSV test files contain literal \"\\n\" in the input.\nfunc preprocessEscapes(s string) string <span class=\"cov8\" title=\"1\">{\n        if len(s) == 0 </span><span class=\"cov8\" title=\"1\">{\n                return s\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">runes := []rune(s)\n        var out []rune\n        i := 0\n        for i &lt; len(runes) </span><span class=\"cov8\" title=\"1\">{\n                if runes[i] == '\\\\' &amp;&amp; i+1 &lt; len(runes) </span><span class=\"cov8\" title=\"1\">{\n                        switch runes[i+1] </span>{\n                        case 'n':<span class=\"cov8\" title=\"1\">\n                                out = append(out, '\\n')\n                                i += 2</span>\n                        case 'r':<span class=\"cov8\" title=\"1\">\n                                out = append(out, '\\r')\n                                i += 2</span>\n                        case 't':<span class=\"cov0\" title=\"0\">\n                                out = append(out, '\\t')\n                                i += 2</span>\n                        default:<span class=\"cov8\" title=\"1\">\n                                out = append(out, runes[i])\n                                i++</span>\n                        }\n                } else<span class=\"cov8\" title=\"1\"> {\n                        out = append(out, runes[i])\n                        i++\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return string(out)</span>\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file4\" style=\"display: none\">package jsonic\n\nimport (\n        \"sort\"\n        \"strings\"\n)\n\n// Lex is the lexer that produces tokens from source text.\ntype Lex struct {\n        Src    string\n        Ctx    *Context  // Parse context (includes Ctx.Rule for context-sensitive lexing)\n        pnt    Point\n        end    *Token    // End-of-source token (cached)\n        tokens []*Token  // Lookahead token queue\n        Config *LexConfig\n        Err    error     // First error encountered during lexing\n}\n\n// LexConfig holds lexer configuration.\ntype LexConfig struct {\n        // Lex enable/disable flags (matching TS options.*.lex)\n        FixedLex   bool // Enable fixed token recognition. Default: true.\n        SpaceLex   bool // Enable space lexing. Default: true.\n        LineLex    bool // Enable line lexing. Default: true.\n        TextLex    bool // Enable text matching. Default: true.\n        NumberLex  bool // Enable number matching. Default: true.\n        CommentLex bool // Enable comment matching. Default: true.\n        StringLex  bool // Enable string matching. Default: true.\n        ValueLex   bool // Enable value keyword matching. Default: true.\n\n        StringChars  map[rune]bool // Quote characters\n        MultiChars   map[rune]bool // Multiline quote characters\n        EscapeChar   rune\n        EscapeMap    map[string]string // Custom escape mappings, e.g. {\"n\": \"\\n\"}.\n        SpaceChars   map[rune]bool\n        LineChars    map[rune]bool\n        RowChars     map[rune]bool\n        CommentLine  []string // Line comment starters: \"#\", \"//\"\n        CommentBlock [][2]string // Block comment: [start, end] pairs\n        NumberHex    bool\n        NumberOct    bool\n        NumberBin    bool\n        NumberSep    rune // Separator char (underscore)\n        AllowUnknownEscape bool\n        StringAbandon      bool            // On string error, return nil instead of bad token.\n        StringReplace      map[rune]string // Character replacements during string scanning.\n\n        // Value definitions: keyword → value (e.g. \"true\" → true)\n        // If nil, uses built-in defaults (true, false, null).\n        ValueDef map[string]any\n\n        // Number options\n        NumberExclude func(string) bool // Exclude certain number-like strings.\n\n        // Line options\n        LineSingle bool // Generate separate tokens per newline.\n\n        // Text options\n        TextModify []ValModifier // Pipeline of text value modifiers.\n\n        // Comment options (per-def eatline stored on CommentDef)\n        CommentLineEatLine  map[string]bool // Line comment starter → eatline flag.\n        CommentBlockEatLine map[string]bool // Block comment start → eatline flag.\n\n        // Map/List options\n        MapExtend    bool         // Deep-merge duplicate keys. Default: true.\n        MapMerge     MapMergeFunc // Custom merge function for duplicate keys.\n        MapChild     bool         // Parse bare colon in maps as child$ key. Default: false.\n        ListProperty bool         // Allow named properties in arrays. Default: true.\n        ListPair     bool         // Push pairs as object elements in arrays. Default: false.\n\n        // Safe options\n        SafeKey bool // Prevent __proto__ keys. Default: true.\n\n        // Rule options\n        FinishRule bool // Auto-close unclosed structures at EOF\n        RuleStart  string // Starting rule name. Default: \"val\".\n\n        // EnderChars lists additional characters that end text and number tokens.\n        EnderChars map[rune]bool\n\n        // Per-instance fixed token map (cloned from global FixedTokens).\n        // Plugins can add custom fixed tokens here. Supports multi-char keys.\n        FixedTokens map[string]Tin\n\n        // FixedSorted is the list of fixed token strings sorted by length (longest first).\n        // Rebuilt by SortFixedTokens() after adding custom tokens.\n        FixedSorted []string\n\n        // Custom token names: Tin → name for plugin-defined tokens.\n        TinNames map[Tin]string\n\n        // Custom lexer matchers added by plugins, sorted by priority.\n        CustomMatchers []*MatcherEntry\n\n        // TextInfo wraps string/text output values in Text structs.\n        TextInfo bool\n\n        // ListRef wraps list output values in ListRef structs.\n        ListRef bool\n\n        // ListChild enables bare colon (:value) syntax in lists to set a child value.\n        ListChild bool\n\n        // MapRef wraps map output values in MapRef structs.\n        MapRef bool\n\n        // IgnoreSet is the per-instance set of token Tins to skip during lexing.\n        // Matches TS cfg.tokenSetTins.IGNORE. Plugins can customize this per-instance.\n        IgnoreSet map[Tin]bool\n\n        // ValSet is the per-instance VAL token set (text, number, string, value).\n        // Matches TS cfg.tokenSet.VAL. Plugins can customize this per-instance.\n        ValSet []Tin\n\n        // KeySet is the per-instance KEY token set (text, number, string, value).\n        // Matches TS cfg.tokenSet.KEY. Plugins can customize this per-instance.\n        KeySet []Tin\n\n        // ParsePrepare hooks called before parsing begins.\n        ParsePrepare []func(ctx *Context)\n\n        // ResultFail is a list of values that are treated as parse failures.\n        ResultFail []any\n\n        // LexCheck callbacks allow plugins to intercept and override matchers.\n        // Each returns nil to continue normal matching, or a LexCheckResult to short-circuit.\n        FixedCheck   LexCheck\n        SpaceCheck   LexCheck\n        LineCheck    LexCheck\n        StringCheck  LexCheck\n        CommentCheck LexCheck\n        NumberCheck  LexCheck\n        TextCheck    LexCheck\n}\n\n// LexCheck is a function that can intercept a matcher before it runs.\n// Return nil to continue normal matching, or a LexCheckResult to override.\ntype LexCheck func(lex *Lex) *LexCheckResult\n\n// LexCheckResult controls matcher behavior from a LexCheck callback.\ntype LexCheckResult struct {\n        Done  bool   // If true, use Token as the match result (even if nil).\n        Token *Token // The token to return (nil means \"no match\").\n}\n\n// DefaultLexConfig returns the default lexer configuration matching jsonic defaults.\nfunc DefaultLexConfig() *LexConfig <span class=\"cov8\" title=\"1\">{\n        return &amp;LexConfig{\n                FixedLex:     true,\n                SpaceLex:     true,\n                LineLex:      true,\n                TextLex:      true,\n                NumberLex:    true,\n                CommentLex:   true,\n                StringLex:    true,\n                ValueLex:     true,\n\n                StringChars:        map[rune]bool{'\\'': true, '\"': true, '`': true},\n                MultiChars:         map[rune]bool{'`': true},\n                EscapeChar:         '\\\\',\n                SpaceChars:         map[rune]bool{' ': true, '\\t': true},\n                LineChars:          map[rune]bool{'\\r': true, '\\n': true},\n                RowChars:           map[rune]bool{'\\n': true},\n                CommentLine:        []string{\"#\", \"//\"},\n                CommentBlock:       [][2]string{{\"/*\", \"*/\"}},\n                NumberHex:          true,\n                NumberOct:          true,\n                NumberBin:          true,\n                NumberSep:          '_',\n                AllowUnknownEscape: true,\n\n                MapExtend:    true,\n                ListProperty: true,\n                SafeKey:      true,\n\n                FinishRule: true,\n                RuleStart:  \"val\",\n\n                IgnoreSet: map[Tin]bool{TinSP: true, TinLN: true, TinCM: true},\n                ValSet:    []Tin{TinTX, TinNR, TinST, TinVL},\n                KeySet:    []Tin{TinTX, TinNR, TinST, TinVL},\n\n                FixedTokens: map[string]Tin{\n                        \"{\": TinOB, \"}\": TinCB,\n                        \"[\": TinOS, \"]\": TinCS,\n                        \":\": TinCL, \",\": TinCA,\n                },\n                FixedSorted: []string{\"{\", \"}\", \"[\", \"]\", \":\", \",\"},\n        }\n}</span>\n\n// SortFixedTokens rebuilds FixedSorted from FixedTokens, sorted by length descending.\n// Call this after adding multi-char fixed tokens to ensure longest-match-first behavior.\nfunc (cfg *LexConfig) SortFixedTokens() <span class=\"cov8\" title=\"1\">{\n        sorted := make([]string, 0, len(cfg.FixedTokens))\n        for k := range cfg.FixedTokens </span><span class=\"cov8\" title=\"1\">{\n                sorted = append(sorted, k)\n        }</span>\n        <span class=\"cov8\" title=\"1\">sort.Slice(sorted, func(i, j int) bool </span><span class=\"cov8\" title=\"1\">{\n                if len(sorted[i]) != len(sorted[j]) </span><span class=\"cov8\" title=\"1\">{\n                        return len(sorted[i]) &gt; len(sorted[j]) // longer first\n                }</span>\n                <span class=\"cov8\" title=\"1\">return sorted[i] &lt; sorted[j]</span> // stable tie-break\n        })\n        <span class=\"cov8\" title=\"1\">cfg.FixedSorted = sorted</span>\n}\n\n// NewLex creates a new lexer for the given source.\nfunc NewLex(src string, cfg *LexConfig) *Lex <span class=\"cov8\" title=\"1\">{\n        return &amp;Lex{\n                Src:    src,\n                pnt:    Point{Len: len(src), SI: 0, RI: 1, CI: 1},\n                Config: cfg,\n        }\n}</span>\n\n// Cursor returns a pointer to the lexer's current position.\n// Custom matchers use this to read and advance the position.\nfunc (l *Lex) Cursor() *Point <span class=\"cov8\" title=\"1\">{\n        return &amp;l.pnt\n}</span>\n\n// Token creates a new token at the current point.\nfunc (l *Lex) Token(name string, tin Tin, val any, src string) *Token <span class=\"cov8\" title=\"1\">{\n        return MakeToken(name, tin, val, src, l.pnt)\n}</span>\n\n// Fwd returns a forward-looking substring from the current position.\n// maxlen limits the length of the returned string.\n// Matches TS lex.fwd.\nfunc (l *Lex) Fwd(maxlen int) string <span class=\"cov8\" title=\"1\">{\n        si := l.pnt.SI\n        end := si + maxlen\n        if end &gt; l.pnt.Len </span><span class=\"cov8\" title=\"1\">{\n                end = l.pnt.Len\n        }</span>\n        <span class=\"cov8\" title=\"1\">if si &gt;= end </span><span class=\"cov0\" title=\"0\">{\n                return \"\"\n        }</span>\n        <span class=\"cov8\" title=\"1\">return l.Src[si:end]</span>\n}\n\n// Bad creates an error token at the current position.\n// Matches TS lex.bad(why, pstart, pend).\nfunc (l *Lex) Bad(why string) *Token <span class=\"cov8\" title=\"1\">{\n        tkn := MakeToken(\"#BD\", TinBD, nil, \"\", l.pnt)\n        tkn.Why = why\n        tkn.Err = why\n        return tkn\n}</span>\n\n// Next returns the next non-IGNORE token, passing the current parsing rule\n// to custom matchers for context-sensitive lexing.\n// On error (unterminated string, unterminated comment, unexpected character),\n// the error is stored in l.Err and a ZZ (end) token is returned to allow\n// the parser to wind down gracefully.\nfunc (l *Lex) Next(rule ...*Rule) *Token <span class=\"cov8\" title=\"1\">{\n        var r *Rule\n        if len(rule) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                r = rule[0]\n        }</span>\n        <span class=\"cov8\" title=\"1\">if l.Ctx != nil &amp;&amp; r != nil </span><span class=\"cov8\" title=\"1\">{\n                l.Ctx.Rule = r\n        }</span>\n        <span class=\"cov8\" title=\"1\">for </span><span class=\"cov8\" title=\"1\">{\n                // If an error has already occurred, return end-of-source to stop parsing\n                if l.Err != nil </span><span class=\"cov8\" title=\"1\">{\n                        return &amp;Token{Name: \"#ZZ\", Tin: TinZZ, Val: Undefined, SI: l.pnt.SI, RI: l.pnt.RI, CI: l.pnt.CI}\n                }</span>\n\n                <span class=\"cov8\" title=\"1\">tkn := l.nextRaw(r)\n                if tkn == nil </span><span class=\"cov8\" title=\"1\">{\n                        src := \"\"\n                        if l.pnt.SI &lt; len(l.Src) </span><span class=\"cov8\" title=\"1\">{\n                                src = string(l.Src[l.pnt.SI])\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">l.Err = makeJsonicError(\"unexpected\", src, l.Src, l.pnt.SI, l.pnt.RI, l.pnt.CI)\n                        return &amp;Token{Name: \"#ZZ\", Tin: TinZZ, Val: Undefined, SI: l.pnt.SI, RI: l.pnt.RI, CI: l.pnt.CI}</span>\n                }\n                // Bad token → store error and return end-of-source\n                <span class=\"cov8\" title=\"1\">if tkn.Tin == TinBD </span><span class=\"cov8\" title=\"1\">{\n                        l.Err = makeJsonicError(tkn.Why, tkn.Src, l.Src, tkn.SI, tkn.RI, tkn.CI)\n                        return &amp;Token{Name: \"#ZZ\", Tin: TinZZ, Val: Undefined, SI: tkn.SI, RI: tkn.RI, CI: tkn.CI}\n                }</span>\n                // Skip IGNORE tokens (per-instance set, matching TS cfg.tokenSetTins.IGNORE)\n                <span class=\"cov8\" title=\"1\">if l.Config.IgnoreSet[tkn.Tin] </span><span class=\"cov8\" title=\"1\">{\n                        continue</span>\n                }\n                <span class=\"cov8\" title=\"1\">return tkn</span>\n        }\n}\n\n// nextRaw returns the next raw token (including IGNORE tokens).\n// The rule parameter is passed to custom matchers for context-sensitive lexing.\nfunc (l *Lex) nextRaw(rule *Rule) *Token <span class=\"cov8\" title=\"1\">{\n        // Return cached end token\n        if l.end != nil </span><span class=\"cov8\" title=\"1\">{\n                return l.end\n        }</span>\n\n        // Return queued lookahead tokens\n        <span class=\"cov8\" title=\"1\">if len(l.tokens) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                tkn := l.tokens[0]\n                l.tokens = l.tokens[1:]\n                return tkn\n        }</span>\n\n        // End of source\n        <span class=\"cov8\" title=\"1\">if l.pnt.SI &gt;= l.pnt.Len </span><span class=\"cov8\" title=\"1\">{\n                l.end = l.Token(\"#ZZ\", TinZZ, Undefined, \"\")\n                return l.end\n        }</span>\n\n        // Try matchers in order (matching TS lex.match ordering):\n        // custom(&lt;2e6), fixed(2e6), space(3e6), line(4e6), string(5e6), comment(6e6), number(7e6), text(8e6)\n\n        // Run custom matchers with priority &lt; 2000000 (before fixed).\n        <span class=\"cov8\" title=\"1\">for _, m := range l.Config.CustomMatchers </span><span class=\"cov8\" title=\"1\">{\n                if m.Priority &gt;= 2000000 </span><span class=\"cov0\" title=\"0\">{\n                        break</span>\n                }\n                <span class=\"cov8\" title=\"1\">if tkn := m.Match(l, rule); tkn != nil </span><span class=\"cov8\" title=\"1\">{\n                        return tkn\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if l.Config.FixedLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.FixedCheck != nil </span><span class=\"cov8\" title=\"1\">{\n                        if cr := l.Config.FixedCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov8\" title=\"1\">{\n                                if cr.Token != nil </span><span class=\"cov8\" title=\"1\">{ return cr.Token }</span>\n                        } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchFixed(); tkn != nil </span><span class=\"cov0\" title=\"0\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchFixed(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if l.Config.SpaceLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.SpaceCheck != nil </span><span class=\"cov0\" title=\"0\">{\n                        if cr := l.Config.SpaceCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov0\" title=\"0\">{\n                                if cr.Token != nil </span><span class=\"cov0\" title=\"0\">{ return cr.Token }</span>\n                        } else<span class=\"cov0\" title=\"0\"> if tkn := l.matchSpace(); tkn != nil </span><span class=\"cov0\" title=\"0\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchSpace(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if l.Config.LineLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.LineCheck != nil </span><span class=\"cov0\" title=\"0\">{\n                        if cr := l.Config.LineCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov0\" title=\"0\">{\n                                if cr.Token != nil </span><span class=\"cov0\" title=\"0\">{ return cr.Token }</span>\n                        } else<span class=\"cov0\" title=\"0\"> if tkn := l.matchLine(); tkn != nil </span><span class=\"cov0\" title=\"0\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchLine(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if l.Config.StringLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.StringCheck != nil </span><span class=\"cov0\" title=\"0\">{\n                        if cr := l.Config.StringCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov0\" title=\"0\">{\n                                if cr.Token != nil </span><span class=\"cov0\" title=\"0\">{ return cr.Token }</span>\n                        } else<span class=\"cov0\" title=\"0\"> if tkn := l.matchString(); tkn != nil </span><span class=\"cov0\" title=\"0\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchString(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if l.Config.CommentLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.CommentCheck != nil </span><span class=\"cov0\" title=\"0\">{\n                        if cr := l.Config.CommentCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov0\" title=\"0\">{\n                                if cr.Token != nil </span><span class=\"cov0\" title=\"0\">{ return cr.Token }</span>\n                        } else<span class=\"cov0\" title=\"0\"> if tkn := l.matchComment(); tkn != nil </span><span class=\"cov0\" title=\"0\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchComment(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if l.Config.NumberLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.NumberCheck != nil </span><span class=\"cov8\" title=\"1\">{\n                        if cr := l.Config.NumberCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov8\" title=\"1\">{\n                                if cr.Token != nil </span><span class=\"cov0\" title=\"0\">{ return cr.Token }</span>\n                        } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchNumber(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchNumber(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if l.Config.TextLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.TextCheck != nil </span><span class=\"cov0\" title=\"0\">{\n                        if cr := l.Config.TextCheck(l); cr != nil &amp;&amp; cr.Done </span><span class=\"cov0\" title=\"0\">{\n                                if cr.Token != nil </span><span class=\"cov0\" title=\"0\">{ return cr.Token }</span>\n                        } else<span class=\"cov0\" title=\"0\"> if tkn := l.matchText(); tkn != nil </span><span class=\"cov0\" title=\"0\">{ return tkn }</span>\n                } else<span class=\"cov8\" title=\"1\"> if tkn := l.matchText(); tkn != nil </span><span class=\"cov8\" title=\"1\">{ return tkn }</span>\n        }\n\n        // Run custom matchers with priority &gt;= 8000000 (after text).\n        <span class=\"cov8\" title=\"1\">for _, m := range l.Config.CustomMatchers </span><span class=\"cov0\" title=\"0\">{\n                if m.Priority &lt; 8000000 </span><span class=\"cov0\" title=\"0\">{\n                        continue</span>\n                }\n                <span class=\"cov0\" title=\"0\">if tkn := m.Match(l, rule); tkn != nil </span><span class=\"cov0\" title=\"0\">{\n                        return tkn\n                }</span>\n        }\n\n        // No matcher matched\n        <span class=\"cov8\" title=\"1\">return nil</span>\n}\n\nfunc (l *Lex) bad(why string, pstart, pend int) *Token <span class=\"cov8\" title=\"1\">{\n        src := \"\"\n        if pstart &gt;= 0 &amp;&amp; pstart &lt; len(l.Src) &amp;&amp; pend &lt;= len(l.Src) </span><span class=\"cov8\" title=\"1\">{\n                src = l.Src[pstart:pend]\n        }</span> else<span class=\"cov8\" title=\"1\"> if l.pnt.SI &lt; len(l.Src) </span><span class=\"cov8\" title=\"1\">{\n                src = string(l.Src[l.pnt.SI])\n        }</span>\n        <span class=\"cov8\" title=\"1\">tkn := l.Token(\"#BD\", TinBD, nil, src)\n        tkn.Why = why\n        return tkn</span>\n}\n\n// matchFixed matches fixed tokens, including multi-character tokens.\n// Tokens are tried longest-first to ensure greedy matching (e.g. \"=&gt;\" before \"=\").\nfunc (l *Lex) matchFixed() *Token <span class=\"cov8\" title=\"1\">{\n        if l.pnt.SI &gt;= l.pnt.Len </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">remaining := l.Src[l.pnt.SI:]\n\n        // Use sorted list for longest-match-first. Fall back to single-char lookup\n        // if no sorted list (e.g. standalone lexer without Jsonic).\n        if len(l.Config.FixedSorted) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, fs := range l.Config.FixedSorted </span><span class=\"cov8\" title=\"1\">{\n                        if strings.HasPrefix(remaining, fs) </span><span class=\"cov8\" title=\"1\">{\n                                tin := l.Config.FixedTokens[fs]\n                                tkn := l.Token(l.tinNameFor(tin), tin, nil, fs)\n                                l.pnt.SI += len(fs)\n                                l.pnt.CI += len(fs)\n                                return tkn\n                        }</span>\n                }\n                <span class=\"cov8\" title=\"1\">return nil</span>\n        }\n\n        // Fallback: single-char lookup.\n        <span class=\"cov0\" title=\"0\">src := string(l.Src[l.pnt.SI])\n        tin, ok := l.Config.FixedTokens[src]\n        if !ok </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov0\" title=\"0\">tkn := l.Token(l.tinNameFor(tin), tin, nil, src)\n        l.pnt.SI++\n        l.pnt.CI++\n        return tkn</span>\n}\n\n// matchSpace matches space and tab characters.\nfunc (l *Lex) matchSpace() *Token <span class=\"cov8\" title=\"1\">{\n        sI := l.pnt.SI\n        cI := l.pnt.CI\n        for sI &lt; l.pnt.Len &amp;&amp; l.Config.SpaceChars[rune(l.Src[sI])] </span><span class=\"cov8\" title=\"1\">{\n                sI++\n                cI++\n        }</span>\n        <span class=\"cov8\" title=\"1\">if sI &gt; l.pnt.SI </span><span class=\"cov8\" title=\"1\">{\n                src := l.Src[l.pnt.SI:sI]\n                tkn := l.Token(\"#SP\", TinSP, nil, src)\n                l.pnt.SI = sI\n                l.pnt.CI = cI\n                return tkn\n        }</span>\n        <span class=\"cov8\" title=\"1\">return nil</span>\n}\n\n// matchLine matches line ending characters (\\r, \\n).\n// When LineSingle is true, generates separate tokens for each newline sequence.\nfunc (l *Lex) matchLine() *Token <span class=\"cov8\" title=\"1\">{\n        sI := l.pnt.SI\n        if sI &gt;= l.pnt.Len || !l.Config.LineChars[rune(l.Src[sI])] </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">rI := l.pnt.RI\n\n        if l.Config.LineSingle </span><span class=\"cov8\" title=\"1\">{\n                // Single mode: consume one newline sequence (\\r\\n or \\n or \\r)\n                ch := l.Src[sI]\n                sI++\n                if l.Config.RowChars[rune(ch)] </span><span class=\"cov8\" title=\"1\">{\n                        rI++\n                }</span>\n                // Handle \\r\\n as a single sequence\n                <span class=\"cov8\" title=\"1\">if ch == '\\r' &amp;&amp; sI &lt; l.pnt.Len &amp;&amp; l.Src[sI] == '\\n' </span><span class=\"cov0\" title=\"0\">{\n                        if l.Config.RowChars['\\n'] </span>{<span class=\"cov0\" title=\"0\">\n                                // \\r\\n counts as one row\n                        }</span>\n                        <span class=\"cov0\" title=\"0\">sI++</span>\n                }\n                <span class=\"cov8\" title=\"1\">src := l.Src[l.pnt.SI:sI]\n                tkn := l.Token(\"#LN\", TinLN, nil, src)\n                l.pnt.SI = sI\n                l.pnt.RI = rI\n                l.pnt.CI = 1\n                return tkn</span>\n        }\n\n        // Default: consume all consecutive line characters into one token\n        <span class=\"cov8\" title=\"1\">for sI &lt; l.pnt.Len &amp;&amp; l.Config.LineChars[rune(l.Src[sI])] </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.RowChars[rune(l.Src[sI])] </span><span class=\"cov8\" title=\"1\">{\n                        rI++\n                }</span>\n                <span class=\"cov8\" title=\"1\">sI++</span>\n        }\n        <span class=\"cov8\" title=\"1\">src := l.Src[l.pnt.SI:sI]\n        tkn := l.Token(\"#LN\", TinLN, nil, src)\n        l.pnt.SI = sI\n        l.pnt.RI = rI\n        l.pnt.CI = 1\n        return tkn</span>\n}\n\n// matchComment matches line comments (# //) and block comments (/* */).\nfunc (l *Lex) matchComment() *Token <span class=\"cov8\" title=\"1\">{\n        fwd := l.Src[l.pnt.SI:]\n\n        // Line comments\n        for _, start := range l.Config.CommentLine </span><span class=\"cov8\" title=\"1\">{\n                if strings.HasPrefix(fwd, start) </span><span class=\"cov8\" title=\"1\">{\n                        fI := len(start)\n                        cI := l.pnt.CI + len(start)\n                        for fI &lt; len(fwd) &amp;&amp; !l.Config.LineChars[rune(fwd[fI])] </span><span class=\"cov8\" title=\"1\">{\n                                cI++\n                                fI++\n                        }</span>\n                        // EatLine: also consume trailing line characters\n                        <span class=\"cov8\" title=\"1\">if l.Config.CommentLineEatLine[start] </span><span class=\"cov8\" title=\"1\">{\n                                rI := l.pnt.RI\n                                for fI &lt; len(fwd) &amp;&amp; l.Config.LineChars[rune(fwd[fI])] </span><span class=\"cov8\" title=\"1\">{\n                                        if l.Config.RowChars[rune(fwd[fI])] </span><span class=\"cov8\" title=\"1\">{\n                                                rI++\n                                        }</span>\n                                        <span class=\"cov8\" title=\"1\">fI++</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">src := fwd[:fI]\n                                tkn := l.Token(\"#CM\", TinCM, nil, src)\n                                l.pnt.SI += len(src)\n                                l.pnt.RI = rI\n                                l.pnt.CI = 1\n                                return tkn</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">src := fwd[:fI]\n                        tkn := l.Token(\"#CM\", TinCM, nil, src)\n                        l.pnt.SI += len(src)\n                        l.pnt.CI = cI\n                        return tkn</span>\n                }\n        }\n\n        // Block comments\n        <span class=\"cov8\" title=\"1\">for _, pair := range l.Config.CommentBlock </span><span class=\"cov8\" title=\"1\">{\n                start, end := pair[0], pair[1]\n                if strings.HasPrefix(fwd, start) </span><span class=\"cov8\" title=\"1\">{\n                        rI := l.pnt.RI\n                        cI := l.pnt.CI + len(start)\n                        fI := len(start)\n                        for fI &lt; len(fwd) &amp;&amp; !strings.HasPrefix(fwd[fI:], end) </span><span class=\"cov8\" title=\"1\">{\n                                if l.Config.RowChars[rune(fwd[fI])] </span><span class=\"cov8\" title=\"1\">{\n                                        rI++\n                                        cI = 0\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">cI++\n                                fI++</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">if strings.HasPrefix(fwd[fI:], end) </span><span class=\"cov8\" title=\"1\">{\n                                cI += len(end)\n                                fI += len(end)\n                                // EatLine: also consume trailing line characters\n                                if l.Config.CommentBlockEatLine[start] </span><span class=\"cov0\" title=\"0\">{\n                                        for fI &lt; len(fwd) &amp;&amp; l.Config.LineChars[rune(fwd[fI])] </span><span class=\"cov0\" title=\"0\">{\n                                                if l.Config.RowChars[rune(fwd[fI])] </span><span class=\"cov0\" title=\"0\">{\n                                                        rI++\n                                                }</span>\n                                                <span class=\"cov0\" title=\"0\">fI++</span>\n                                        }\n                                        <span class=\"cov0\" title=\"0\">cI = 1</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">src := fwd[:fI]\n                                tkn := l.Token(\"#CM\", TinCM, nil, src)\n                                l.pnt.SI += len(src)\n                                l.pnt.RI = rI\n                                l.pnt.CI = cI\n                                return tkn</span>\n                        }\n                        // Unterminated comment - return bad token\n                        <span class=\"cov8\" title=\"1\">return l.bad(\"unterminated_comment\", l.pnt.SI, l.pnt.SI+len(start)*9)</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">return nil</span>\n}\n\n// matchString matches quoted strings: \"...\", '...', `...`\nfunc (l *Lex) matchString() *Token <span class=\"cov8\" title=\"1\">{\n        if l.pnt.SI &gt;= l.pnt.Len </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">q := rune(l.Src[l.pnt.SI])\n        if !l.Config.StringChars[q] </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">isMultiLine := l.Config.MultiChars[q]\n        src := l.Src\n        sI := l.pnt.SI + 1\n        rI := l.pnt.RI\n        cI := l.pnt.CI + 1\n\n        var sb strings.Builder\n        srclen := len(src)\n        foundClose := false\n\n        for sI &lt; srclen </span><span class=\"cov8\" title=\"1\">{\n                cI++\n                c := rune(src[sI])\n\n                // End quote\n                if c == q </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                        foundClose = true\n                        break</span>\n                }\n\n                // Escape character (all string types process escapes)\n                <span class=\"cov8\" title=\"1\">if c == l.Config.EscapeChar </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                        cI++\n                        if sI &gt;= srclen </span><span class=\"cov0\" title=\"0\">{\n                                break</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">esc := src[sI]\n\n                        // Check custom escape map first.\n                        if l.Config.EscapeMap != nil </span><span class=\"cov8\" title=\"1\">{\n                                if rep, ok := l.Config.EscapeMap[string(esc)]; ok </span><span class=\"cov8\" title=\"1\">{\n                                        sb.WriteString(rep)\n                                        sI++\n                                        continue</span>\n                                }\n                        }\n\n                        <span class=\"cov8\" title=\"1\">switch esc </span>{\n                        case 'b':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\\b')</span>\n                        case 'f':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\\f')</span>\n                        case 'n':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\\n')</span>\n                        case 'r':<span class=\"cov0\" title=\"0\">\n                                sb.WriteByte('\\r')</span>\n                        case 't':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\\t')</span>\n                        case 'v':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\\v')</span>\n                        case '\"':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\"')</span>\n                        case '\\'':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('\\'')</span>\n                        case '`':<span class=\"cov8\" title=\"1\">\n                                sb.WriteByte('`')</span>\n                        case '\\\\':<span class=\"cov0\" title=\"0\">\n                                sb.WriteByte('\\\\')</span>\n                        case '/':<span class=\"cov0\" title=\"0\">\n                                sb.WriteByte('/')</span>\n                        case 'x':<span class=\"cov8\" title=\"1\">\n                                // ASCII escape \\x**\n                                sI++\n                                if sI+2 &lt;= srclen </span><span class=\"cov8\" title=\"1\">{\n                                        cc := parseHexInt(src[sI : sI+2])\n                                        if cc &gt;= 0 </span><span class=\"cov8\" title=\"1\">{\n                                                sb.WriteRune(rune(cc))\n                                                sI += 1 // loop will increment\n                                                cI += 2\n                                        }</span> else<span class=\"cov0\" title=\"0\"> {\n                                                if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                        return nil\n                                                }</span>\n                                                <span class=\"cov0\" title=\"0\">return l.bad(\"invalid_ascii\", l.pnt.SI, sI+2)</span>\n                                        }\n                                } else<span class=\"cov0\" title=\"0\"> {\n                                        if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                return nil\n                                        }</span>\n                                        <span class=\"cov0\" title=\"0\">return l.bad(\"invalid_ascii\", l.pnt.SI, sI)</span>\n                                }\n                        case 'u':<span class=\"cov8\" title=\"1\">\n                                // Unicode escape \\u**** or \\u{*****}\n                                sI++\n                                if sI &lt; srclen &amp;&amp; src[sI] == '{' </span><span class=\"cov0\" title=\"0\">{\n                                        sI++\n                                        endI := strings.IndexByte(src[sI:], '}')\n                                        if endI &gt;= 0 </span><span class=\"cov0\" title=\"0\">{\n                                                cc := parseHexInt(src[sI : sI+endI])\n                                                if cc &gt;= 0 </span><span class=\"cov0\" title=\"0\">{\n                                                        sb.WriteRune(rune(cc))\n                                                        sI += endI // skip past digits, loop handles +1\n                                                        cI += endI + 2\n                                                }</span> else<span class=\"cov0\" title=\"0\"> {\n                                                        if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                                return nil\n                                                        }</span>\n                                                        <span class=\"cov0\" title=\"0\">return l.bad(\"invalid_unicode\", l.pnt.SI, sI+endI+1)</span>\n                                                }\n                                        } else<span class=\"cov0\" title=\"0\"> {\n                                                if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                        return nil\n                                                }</span>\n                                                <span class=\"cov0\" title=\"0\">return l.bad(\"invalid_unicode\", l.pnt.SI, sI)</span>\n                                        }\n                                } else<span class=\"cov8\" title=\"1\"> if sI+4 &lt;= srclen </span><span class=\"cov8\" title=\"1\">{\n                                        cc := parseHexInt(src[sI : sI+4])\n                                        if cc &gt;= 0 </span><span class=\"cov8\" title=\"1\">{\n                                                sb.WriteRune(rune(cc))\n                                                sI += 3\n                                                cI += 4\n                                        }</span> else<span class=\"cov0\" title=\"0\"> {\n                                                if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                        return nil\n                                                }</span>\n                                                <span class=\"cov0\" title=\"0\">return l.bad(\"invalid_unicode\", l.pnt.SI, sI+4)</span>\n                                        }\n                                } else<span class=\"cov0\" title=\"0\"> {\n                                        if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                return nil\n                                        }</span>\n                                        <span class=\"cov0\" title=\"0\">return l.bad(\"invalid_unicode\", l.pnt.SI, sI)</span>\n                                }\n                        default:<span class=\"cov8\" title=\"1\">\n                                if l.Config.AllowUnknownEscape </span><span class=\"cov8\" title=\"1\">{\n                                        sb.WriteByte(esc)\n                                }</span> else<span class=\"cov8\" title=\"1\"> {\n                                        if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                                return nil\n                                        }</span>\n                                        <span class=\"cov8\" title=\"1\">return l.bad(\"unexpected\", l.pnt.SI, sI+1)</span>\n                                }\n                        }\n                        <span class=\"cov8\" title=\"1\">sI++\n                        continue</span>\n                }\n\n                // Check for unprintable / multiline\n                <span class=\"cov8\" title=\"1\">if c &lt; 32 </span><span class=\"cov8\" title=\"1\">{\n                        if isMultiLine &amp;&amp; l.Config.LineChars[c] </span><span class=\"cov8\" title=\"1\">{\n                                if l.Config.RowChars[c] </span><span class=\"cov8\" title=\"1\">{\n                                        rI++\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">cI = 1\n                                sb.WriteByte(src[sI])\n                                sI++\n                                continue</span>\n                        }\n                        // Non-multiline unprintable - bad\n                        <span class=\"cov8\" title=\"1\">if l.Config.StringAbandon </span><span class=\"cov0\" title=\"0\">{\n                                return nil\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">break</span>\n                }\n\n                // Normal body - fast scan\n                <span class=\"cov8\" title=\"1\">bI := sI\n                if len(l.Config.StringReplace) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                        // Char-by-char with replacement support\n                        for sI &lt; srclen </span><span class=\"cov8\" title=\"1\">{\n                                cc := rune(src[sI])\n                                if cc &lt; 32 || cc == q || cc == rune(l.Config.EscapeChar) </span><span class=\"cov8\" title=\"1\">{\n                                        break</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">if rep, ok := l.Config.StringReplace[cc]; ok </span><span class=\"cov8\" title=\"1\">{\n                                        // Flush pending plain chars\n                                        if sI &gt; bI </span><span class=\"cov8\" title=\"1\">{\n                                                sb.WriteString(src[bI:sI])\n                                        }</span>\n                                        <span class=\"cov8\" title=\"1\">sb.WriteString(rep)\n                                        sI++\n                                        cI++\n                                        bI = sI\n                                        continue</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">sI++\n                                cI++</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">if sI &gt; bI </span><span class=\"cov8\" title=\"1\">{\n                                sb.WriteString(src[bI:sI])\n                        }</span>\n                } else<span class=\"cov8\" title=\"1\"> {\n                        for sI &lt; srclen </span><span class=\"cov8\" title=\"1\">{\n                                cc := rune(src[sI])\n                                if cc &lt; 32 || cc == q || cc == rune(l.Config.EscapeChar) </span><span class=\"cov8\" title=\"1\">{\n                                        break</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">sI++\n                                cI++</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">sb.WriteString(src[bI:sI])</span>\n                }\n                <span class=\"cov8\" title=\"1\">cI-- // loop will re-increment\n                continue</span>\n        }\n\n        // Check for unterminated string\n        <span class=\"cov8\" title=\"1\">if !foundClose </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.StringAbandon </span><span class=\"cov8\" title=\"1\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">return l.bad(\"unterminated_string\", l.pnt.SI, sI)</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">val := sb.String()\n        ssrc := src[l.pnt.SI:sI]\n        tkn := l.Token(\"#ST\", TinST, val, ssrc)\n        l.pnt.SI = sI\n        l.pnt.RI = rI\n        l.pnt.CI = cI\n        return tkn</span>\n}\n\n// matchNumber matches numeric literals: decimal, hex (0x), octal (0o), binary (0b).\n// Returns nil if the text at current position is not a valid number (lets text matcher try).\nfunc (l *Lex) matchNumber() *Token <span class=\"cov8\" title=\"1\">{\n        if l.pnt.SI &gt;= l.pnt.Len </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">src := l.Src\n        sI := l.pnt.SI\n        ch := src[sI]\n\n        // Must start with digit, +, -, or .\n        if !isDigit(ch) &amp;&amp; ch != '-' &amp;&amp; ch != '+' &amp;&amp; ch != '.' </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n\n        // Save start position for backtracking\n        <span class=\"cov8\" title=\"1\">start := sI\n\n        // Handle sign\n        hasSign := false\n        if ch == '-' || ch == '+' </span><span class=\"cov8\" title=\"1\">{\n                hasSign = true\n                sI++\n                if sI &gt;= len(src) </span><span class=\"cov8\" title=\"1\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">ch = src[sI]</span>\n        }\n\n        // Hex: 0x...\n        <span class=\"cov8\" title=\"1\">if ch == '0' &amp;&amp; sI+1 &lt; len(src) &amp;&amp; (src[sI+1] == 'x' || src[sI+1] == 'X') &amp;&amp; l.Config.NumberHex </span><span class=\"cov8\" title=\"1\">{\n                sI += 2\n                hexStart := sI\n                for sI &lt; len(src) &amp;&amp; (isHexDigitByte(src[sI]) || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                }</span>\n                <span class=\"cov8\" title=\"1\">if sI == hexStart </span><span class=\"cov8\" title=\"1\">{\n                        // \"0x\" with no hex digits → let text matcher handle\n                        return nil\n                }</span>\n                // Check trailing text\n                <span class=\"cov8\" title=\"1\">if l.isFollowingText(sI) </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">msrc := src[start:sI]\n                nstr := msrc\n                if l.Config.NumberSep != 0 </span><span class=\"cov8\" title=\"1\">{\n                        nstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n                }</span>\n                <span class=\"cov8\" title=\"1\">num := parseNumericString(nstr)\n                if num != num </span><span class=\"cov0\" title=\"0\">{ // NaN check\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">tkn := l.Token(\"#NR\", TinNR, num, msrc)\n                l.pnt.SI = sI\n                l.pnt.CI += sI - start\n                return tkn</span>\n        }\n\n        // Octal: 0o...\n        <span class=\"cov8\" title=\"1\">if ch == '0' &amp;&amp; sI+1 &lt; len(src) &amp;&amp; (src[sI+1] == 'o' || src[sI+1] == 'O') &amp;&amp; l.Config.NumberOct </span><span class=\"cov8\" title=\"1\">{\n                sI += 2\n                octStart := sI\n                for sI &lt; len(src) &amp;&amp; ((src[sI] &gt;= '0' &amp;&amp; src[sI] &lt;= '7') || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                }</span>\n                <span class=\"cov8\" title=\"1\">if sI == octStart </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">if l.isFollowingText(sI) </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">msrc := src[start:sI]\n                nstr := msrc\n                if l.Config.NumberSep != 0 </span><span class=\"cov8\" title=\"1\">{\n                        nstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n                }</span>\n                <span class=\"cov8\" title=\"1\">num := parseNumericString(nstr)\n                if num != num </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">tkn := l.Token(\"#NR\", TinNR, num, msrc)\n                l.pnt.SI = sI\n                l.pnt.CI += sI - start\n                return tkn</span>\n        }\n\n        // Binary: 0b...\n        <span class=\"cov8\" title=\"1\">if ch == '0' &amp;&amp; sI+1 &lt; len(src) &amp;&amp; (src[sI+1] == 'b' || src[sI+1] == 'B') &amp;&amp; l.Config.NumberBin </span><span class=\"cov8\" title=\"1\">{\n                sI += 2\n                binStart := sI\n                for sI &lt; len(src) &amp;&amp; ((src[sI] == '0' || src[sI] == '1') || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                }</span>\n                <span class=\"cov8\" title=\"1\">if sI == binStart </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">if l.isFollowingText(sI) </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">msrc := src[start:sI]\n                nstr := msrc\n                if l.Config.NumberSep != 0 </span><span class=\"cov8\" title=\"1\">{\n                        nstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n                }</span>\n                <span class=\"cov8\" title=\"1\">num := parseNumericString(nstr)\n                if num != num </span><span class=\"cov0\" title=\"0\">{\n                        return nil\n                }</span>\n                <span class=\"cov8\" title=\"1\">tkn := l.Token(\"#NR\", TinNR, num, msrc)\n                l.pnt.SI = sI\n                l.pnt.CI += sI - start\n                return tkn</span>\n        }\n\n        // Decimal number: optional leading dot, digits, decimal, exponent\n        // Pattern: \\.?[0-9]+([0-9_]*[0-9])? (\\.[0-9]?([0-9_]*[0-9])?)? ([eE][-+]?[0-9]+([0-9_]*[0-9])?)?\n        <span class=\"cov8\" title=\"1\">hasDigits := false\n\n        // Leading dot\n        if ch == '.' </span><span class=\"cov8\" title=\"1\">{\n                if sI+1 &gt;= len(src) || !isDigit(src[sI+1]) </span><span class=\"cov0\" title=\"0\">{\n                        return nil // Just a dot, not a number\n                }</span>\n                <span class=\"cov8\" title=\"1\">sI++ // consume dot\n                for sI &lt; len(src) &amp;&amp; (isDigit(src[sI]) || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                        hasDigits = true\n                }</span>\n        } else<span class=\"cov8\" title=\"1\"> {\n                // Integer part\n                for sI &lt; len(src) &amp;&amp; (isDigit(src[sI]) || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                        hasDigits = true\n                        sI++\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if !hasDigits </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n\n        // Decimal point\n        <span class=\"cov8\" title=\"1\">if sI &lt; len(src) &amp;&amp; src[sI] == '.' </span><span class=\"cov8\" title=\"1\">{\n                // Check what follows the dot\n                if sI+1 &lt; len(src) &amp;&amp; isDigit(src[sI+1]) </span><span class=\"cov8\" title=\"1\">{\n                        sI++ // consume dot\n                        for sI &lt; len(src) &amp;&amp; (isDigit(src[sI]) || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                                sI++\n                        }</span>\n                } else<span class=\"cov8\" title=\"1\"> if sI+1 &lt; len(src) &amp;&amp; l.isFollowingText(sI+1) &amp;&amp; src[sI+1] != '.' </span><span class=\"cov8\" title=\"1\">{\n                        // \"0.a\" → not a number, let text handle it\n                        return nil\n                }</span> else<span class=\"cov8\" title=\"1\"> {\n                        // Trailing dot: \"0.\" at end or before delimiter\n                        sI++ // consume dot\n                }</span>\n        }\n\n        // Exponent\n        <span class=\"cov8\" title=\"1\">if sI &lt; len(src) &amp;&amp; (src[sI] == 'e' || src[sI] == 'E') </span><span class=\"cov8\" title=\"1\">{\n                eSI := sI\n                sI++ // consume e\n                if sI &lt; len(src) &amp;&amp; (src[sI] == '+' || src[sI] == '-') </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                }</span>\n                <span class=\"cov8\" title=\"1\">expStart := sI\n                for sI &lt; len(src) &amp;&amp; (isDigit(src[sI]) || (l.Config.NumberSep != 0 &amp;&amp; rune(src[sI]) == l.Config.NumberSep)) </span><span class=\"cov8\" title=\"1\">{\n                        sI++\n                }</span>\n                <span class=\"cov8\" title=\"1\">if sI == expStart </span><span class=\"cov8\" title=\"1\">{\n                        // No exponent digits - check if trailing makes it text\n                        if l.isFollowingText(sI) </span><span class=\"cov0\" title=\"0\">{\n                                return nil\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">sI = eSI</span> // backtrack, 'e' is not part of number\n                }\n                // Check for trailing text after exponent\n                <span class=\"cov8\" title=\"1\">if l.isFollowingText(sI) </span><span class=\"cov8\" title=\"1\">{\n                        return nil\n                }</span>\n        }\n\n        // Check for trailing alpha/text that would make this text\n        <span class=\"cov8\" title=\"1\">if l.isFollowingText(sI) </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">msrc := src[start:sI]\n        if len(msrc) == 0 || (hasSign &amp;&amp; len(msrc) == 1) </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n\n        // Check number.exclude\n        <span class=\"cov8\" title=\"1\">if l.Config.NumberExclude != nil &amp;&amp; l.Config.NumberExclude(msrc) </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">nstr := msrc\n        if l.Config.NumberSep != 0 </span><span class=\"cov8\" title=\"1\">{\n                nstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">num := parseNumericString(nstr)\n        if num != num </span><span class=\"cov0\" title=\"0\">{ // NaN\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">tkn := l.Token(\"#NR\", TinNR, num, msrc)\n        l.pnt.SI = sI\n        l.pnt.CI += sI - start\n\n        // subMatchFixed: push trailing fixed token as lookahead (matching TS)\n        if l.pnt.SI &lt; l.pnt.Len </span><span class=\"cov8\" title=\"1\">{\n                remaining := src[l.pnt.SI:]\n                for _, fs := range l.Config.FixedSorted </span><span class=\"cov8\" title=\"1\">{\n                        if strings.HasPrefix(remaining, fs) </span><span class=\"cov8\" title=\"1\">{\n                                tin := l.Config.FixedTokens[fs]\n                                fixTkn := l.Token(l.tinNameFor(tin), tin, nil, fs)\n                                l.pnt.SI += len(fs)\n                                l.pnt.CI += len(fs)\n                                l.tokens = append(l.tokens, fixTkn)\n                                break</span>\n                        }\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">return tkn</span>\n}\n\n// matchText matches unquoted text and checks for value keywords (true, false, null).\n// Text is terminated by fixed tokens, whitespace, quotes, and comment starters.\nfunc (l *Lex) matchText() *Token <span class=\"cov8\" title=\"1\">{\n        if l.pnt.SI &gt;= l.pnt.Len </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">src := l.Src\n        sI := l.pnt.SI\n        start := sI\n\n        for sI &lt; len(src) </span><span class=\"cov8\" title=\"1\">{\n                ch := rune(src[sI])\n                // Stop at characters whose lexers are enabled, plus ender chars.\n                // When a lexer is disabled, its characters are consumed as text\n                // (matching TS behavior where enderRE is built conditionally).\n                if (l.Config.SpaceLex &amp;&amp; l.Config.SpaceChars[ch]) ||\n                        (l.Config.LineLex &amp;&amp; l.Config.LineChars[ch]) ||\n                        (l.Config.StringLex &amp;&amp; l.Config.StringChars[ch]) ||\n                        l.Config.EnderChars[ch] </span><span class=\"cov8\" title=\"1\">{\n                        break</span>\n                }\n                // Stop at fixed tokens (check multi-char first, then single-char)\n                <span class=\"cov8\" title=\"1\">rest := src[sI:]\n                isFixed := false\n                for _, fs := range l.Config.FixedSorted </span><span class=\"cov8\" title=\"1\">{\n                        if strings.HasPrefix(rest, fs) </span><span class=\"cov8\" title=\"1\">{\n                                isFixed = true\n                                break</span>\n                        }\n                }\n                <span class=\"cov8\" title=\"1\">if !isFixed &amp;&amp; len(l.Config.FixedSorted) == 0 </span><span class=\"cov0\" title=\"0\">{\n                        // Fallback for standalone lexer without sorted list\n                        if ch == '{' || ch == '}' || ch == '[' || ch == ']' ||\n                                ch == ':' || ch == ',' </span><span class=\"cov0\" title=\"0\">{\n                                isFixed = true\n                        }</span>\n                }\n                <span class=\"cov8\" title=\"1\">if isFixed </span><span class=\"cov8\" title=\"1\">{\n                        break</span>\n                }\n                // Comment starters (only stop text if comment lexing is enabled)\n                <span class=\"cov8\" title=\"1\">if l.Config.CommentLex </span><span class=\"cov8\" title=\"1\">{\n                        isComment := false\n                        for _, cs := range l.Config.CommentLine </span><span class=\"cov8\" title=\"1\">{\n                                if strings.HasPrefix(rest, cs) </span><span class=\"cov8\" title=\"1\">{\n                                        isComment = true\n                                        break</span>\n                                }\n                        }\n                        <span class=\"cov8\" title=\"1\">if !isComment </span><span class=\"cov8\" title=\"1\">{\n                                for _, cb := range l.Config.CommentBlock </span><span class=\"cov8\" title=\"1\">{\n                                        if strings.HasPrefix(rest, cb[0]) </span><span class=\"cov8\" title=\"1\">{\n                                                isComment = true\n                                                break</span>\n                                        }\n                                }\n                        }\n                        <span class=\"cov8\" title=\"1\">if isComment </span><span class=\"cov8\" title=\"1\">{\n                                break</span>\n                        }\n                }\n                <span class=\"cov8\" title=\"1\">sI++</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if sI == start </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">msrc := src[start:sI]\n        mlen := len(msrc)\n\n        // Check for value keywords\n        if l.Config.ValueLex </span><span class=\"cov8\" title=\"1\">{\n                if l.Config.ValueDef != nil </span><span class=\"cov8\" title=\"1\">{\n                        // Custom value definitions\n                        if val, ok := l.Config.ValueDef[msrc]; ok </span><span class=\"cov8\" title=\"1\">{\n                                tkn := l.Token(\"#VL\", TinVL, val, msrc)\n                                l.pnt.SI += mlen\n                                l.pnt.CI += mlen\n                                return tkn\n                        }</span>\n                } else<span class=\"cov8\" title=\"1\"> {\n                        // Default value keywords (matching TS: true, false, null only)\n                        switch msrc </span>{\n                        case \"true\":<span class=\"cov8\" title=\"1\">\n                                tkn := l.Token(\"#VL\", TinVL, true, msrc)\n                                l.pnt.SI += mlen\n                                l.pnt.CI += mlen\n                                return tkn</span>\n                        case \"false\":<span class=\"cov8\" title=\"1\">\n                                tkn := l.Token(\"#VL\", TinVL, false, msrc)\n                                l.pnt.SI += mlen\n                                l.pnt.CI += mlen\n                                return tkn</span>\n                        case \"null\":<span class=\"cov8\" title=\"1\">\n                                tkn := l.Token(\"#VL\", TinVL, nil, msrc)\n                                l.pnt.SI += mlen\n                                l.pnt.CI += mlen\n                                return tkn</span>\n                        }\n                }\n        }\n\n        // Plain text\n        <span class=\"cov8\" title=\"1\">var textVal any = msrc\n        // Run text.modify pipeline\n        if len(l.Config.TextModify) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, mod := range l.Config.TextModify </span><span class=\"cov8\" title=\"1\">{\n                        textVal = mod(textVal)\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">tkn := l.Token(\"#TX\", TinTX, textVal, msrc)\n        l.pnt.SI += mlen\n        l.pnt.CI += mlen\n\n        // Check if next chars are a fixed token - push as lookahead (subMatchFixed)\n        if l.pnt.SI &lt; l.pnt.Len </span><span class=\"cov8\" title=\"1\">{\n                remaining := src[l.pnt.SI:]\n                matched := false\n                for _, fs := range l.Config.FixedSorted </span><span class=\"cov8\" title=\"1\">{\n                        if strings.HasPrefix(remaining, fs) </span><span class=\"cov8\" title=\"1\">{\n                                tin := l.Config.FixedTokens[fs]\n                                fixTkn := l.Token(l.tinNameFor(tin), tin, nil, fs)\n                                l.pnt.SI += len(fs)\n                                l.pnt.CI += len(fs)\n                                l.tokens = append(l.tokens, fixTkn)\n                                matched = true\n                                break</span>\n                        }\n                }\n                <span class=\"cov8\" title=\"1\">if !matched &amp;&amp; len(l.Config.FixedSorted) == 0 </span><span class=\"cov0\" title=\"0\">{\n                        // Fallback for standalone lexer\n                        nextCh := string(src[l.pnt.SI])\n                        if tin, ok := l.Config.FixedTokens[nextCh]; ok </span><span class=\"cov0\" title=\"0\">{\n                                fixTkn := l.Token(l.tinNameFor(tin), tin, nil, nextCh)\n                                l.pnt.SI++\n                                l.pnt.CI++\n                                l.tokens = append(l.tokens, fixTkn)\n                        }</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">return tkn</span>\n}\n\n// Helper functions\n\n// tinNameFor returns the name for a Tin, checking custom names first.\nfunc (l *Lex) tinNameFor(tin Tin) string <span class=\"cov8\" title=\"1\">{\n        if l.Config.TinNames != nil </span><span class=\"cov8\" title=\"1\">{\n                if name, ok := l.Config.TinNames[tin]; ok </span><span class=\"cov8\" title=\"1\">{\n                        return name\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return tinName(tin)</span>\n}\n\nfunc tinName(tin Tin) string <span class=\"cov8\" title=\"1\">{\n        switch tin </span>{\n        case TinOB:<span class=\"cov8\" title=\"1\">\n                return \"#OB\"</span>\n        case TinCB:<span class=\"cov8\" title=\"1\">\n                return \"#CB\"</span>\n        case TinOS:<span class=\"cov8\" title=\"1\">\n                return \"#OS\"</span>\n        case TinCS:<span class=\"cov8\" title=\"1\">\n                return \"#CS\"</span>\n        case TinCL:<span class=\"cov8\" title=\"1\">\n                return \"#CL\"</span>\n        case TinCA:<span class=\"cov8\" title=\"1\">\n                return \"#CA\"</span>\n        default:<span class=\"cov0\" title=\"0\">\n                return \"#UK\"</span>\n        }\n}\n\nfunc isDigit(ch byte) bool <span class=\"cov8\" title=\"1\">{\n        return ch &gt;= '0' &amp;&amp; ch &lt;= '9'\n}</span>\n\nfunc isHexDigitByte(ch byte) bool <span class=\"cov8\" title=\"1\">{\n        return (ch &gt;= '0' &amp;&amp; ch &lt;= '9') || (ch &gt;= 'a' &amp;&amp; ch &lt;= 'f') || (ch &gt;= 'A' &amp;&amp; ch &lt;= 'F')\n}</span>\n\n// isTextChar returns true if the character can continue a text token,\n// checking against the config's fixed tokens, ender chars, and string chars.\nfunc (l *Lex) isTextChar(pos int) bool <span class=\"cov8\" title=\"1\">{\n        if pos &gt;= len(l.Src) </span><span class=\"cov8\" title=\"1\">{\n                return false\n        }</span>\n        <span class=\"cov8\" title=\"1\">ch := l.Src[pos]\n        r := rune(ch)\n        // Only treat whitespace as non-text if the corresponding lexer is enabled.\n        if l.Config.SpaceLex &amp;&amp; l.Config.SpaceChars[r] </span><span class=\"cov8\" title=\"1\">{\n                return false\n        }</span>\n        <span class=\"cov8\" title=\"1\">if l.Config.LineLex &amp;&amp; l.Config.LineChars[r] </span><span class=\"cov8\" title=\"1\">{\n                return false\n        }</span>\n        // Check string chars (only when string lexing is enabled)\n        <span class=\"cov8\" title=\"1\">if l.Config.StringLex &amp;&amp; l.Config.StringChars[r] </span><span class=\"cov0\" title=\"0\">{\n                return false\n        }</span>\n        // Check ender chars\n        <span class=\"cov8\" title=\"1\">if l.Config.EnderChars[r] </span><span class=\"cov0\" title=\"0\">{\n                return false\n        }</span>\n        // Check fixed tokens (multi-char: check if any fixed token starts here)\n        <span class=\"cov8\" title=\"1\">rest := l.Src[pos:]\n        for _, fs := range l.Config.FixedSorted </span><span class=\"cov8\" title=\"1\">{\n                if strings.HasPrefix(rest, fs) </span><span class=\"cov8\" title=\"1\">{\n                        return false\n                }</span>\n        }\n        // Fallback for standalone lexer without sorted list\n        <span class=\"cov8\" title=\"1\">if len(l.Config.FixedSorted) == 0 </span><span class=\"cov0\" title=\"0\">{\n                if ch == '{' || ch == '}' || ch == '[' || ch == ']' ||\n                        ch == ':' || ch == ',' </span><span class=\"cov0\" title=\"0\">{\n                        return false\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return true</span>\n}\n\n// isFollowingText returns true if the character at pos would continue a text token,\n// taking into account fixed tokens, ender chars, and comment starters.\nfunc (l *Lex) isFollowingText(pos int) bool <span class=\"cov8\" title=\"1\">{\n        if !l.isTextChar(pos) </span><span class=\"cov8\" title=\"1\">{\n                return false\n        }</span>\n        // Comment starters are not text continuation (only when comment lexing is enabled)\n        <span class=\"cov8\" title=\"1\">if l.Config.CommentLex </span><span class=\"cov8\" title=\"1\">{\n                rest := l.Src[pos:]\n                for _, cs := range l.Config.CommentLine </span><span class=\"cov8\" title=\"1\">{\n                        if strings.HasPrefix(rest, cs) </span><span class=\"cov8\" title=\"1\">{\n                                return false\n                        }</span>\n                }\n                <span class=\"cov8\" title=\"1\">for _, cb := range l.Config.CommentBlock </span><span class=\"cov8\" title=\"1\">{\n                        if strings.HasPrefix(rest, cb[0]) </span><span class=\"cov0\" title=\"0\">{\n                                return false\n                        }</span>\n                }\n        }\n        <span class=\"cov8\" title=\"1\">return true</span>\n}\n\nfunc parseHexInt(s string) int <span class=\"cov8\" title=\"1\">{\n        val := 0\n        for _, ch := range s </span><span class=\"cov8\" title=\"1\">{\n                val &lt;&lt;= 4\n                switch </span>{\n                case ch &gt;= '0' &amp;&amp; ch &lt;= '9':<span class=\"cov8\" title=\"1\">\n                        val |= int(ch - '0')</span>\n                case ch &gt;= 'a' &amp;&amp; ch &lt;= 'f':<span class=\"cov0\" title=\"0\">\n                        val |= int(ch-'a') + 10</span>\n                case ch &gt;= 'A' &amp;&amp; ch &lt;= 'F':<span class=\"cov0\" title=\"0\">\n                        val |= int(ch-'A') + 10</span>\n                default:<span class=\"cov0\" title=\"0\">\n                        return -1</span>\n                }\n        }\n        <span class=\"cov8\" title=\"1\">return val</span>\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file5\" style=\"display: none\">package jsonic\n\nimport \"strconv\"\n\n// Options configures a Jsonic parser instance.\n// All fields use pointer types so that nil means \"use default\".\n// This matches the TypeScript pattern where unset options fall back to defaults.\ntype Options struct {\n        // Safe controls prototype-pollution-style key safety.\n        Safe *SafeOptions\n\n        // Fixed controls fixed token recognition ({, }, [, ], :, ,).\n        Fixed *FixedOptions\n\n        // Space controls space/tab lexing.\n        Space *SpaceOptions\n\n        // Line controls line-ending lexing.\n        Line *LineOptions\n\n        // Text controls unquoted text lexing.\n        Text *TextOptions\n\n        // Number controls numeric literal lexing.\n        Number *NumberOptions\n\n        // Comment controls comment lexing.\n        Comment *CommentOptions\n\n        // String controls quoted string lexing.\n        String *StringOptions\n\n        // Map controls object/map merging behavior.\n        Map *MapOptions\n\n        // List controls array/list behavior.\n        List *ListOptions\n\n        // Value controls keyword literal matching (true, false, null, etc.).\n        Value *ValueOptions\n\n        // Ender lists additional characters that end text tokens.\n        Ender []string\n\n        // Rule controls parser rule behavior.\n        Rule *RuleOptions\n\n        // Lex controls global lexer behavior (empty source, etc.).\n        Lex *LexOptions\n\n        // Parser allows custom parser overrides.\n        Parser *ParserOptions\n\n        // Result controls parse result validation.\n        Result *ResultOptions\n\n        // Error provides custom error message templates keyed by error code.\n        // e.g. {\"unexpected\": \"unexpected character(s): {src}\"}\n        Error map[string]string\n\n        // Hint provides additional explanatory text per error code.\n        Hint map[string]string\n\n        // ErrMsg controls error message formatting.\n        ErrMsg *ErrMsgOptions\n\n        // Property holds Go-specific options not present in the TypeScript version.\n        Property *PropertyOptions\n\n        // Tag is an instance identifier tag.\n        Tag string\n}\n\n// ErrMsgOptions controls error message formatting.\n// Matches the TypeScript errmsg option.\ntype ErrMsgOptions struct {\n        // Name sets the error tag in formatted messages.\n        // Default: \"jsonic\". E.g. Name=\"bar\" → \"[bar/unexpected]: ...\"\n        Name string\n}\n\n// PropertyOptions holds Go-specific options not present in the TypeScript version.\ntype PropertyOptions struct {\n        // ConfigModify callbacks, keyed by name.\n        // Called after config construction to allow dynamic customization.\n        ConfigModify map[string]ConfigModifier\n\n        // TextInfo enables extended text info in output.\n        // When true, string and text values are wrapped in Text structs\n        // that include the quote character used. Default: false.\n        TextInfo *bool\n\n        // ListRef enables returning lists as ListRef structs instead of []any.\n        // When true, list values include an Implicit flag indicating whether\n        // the list was created implicitly (without brackets). Default: false.\n        ListRef *bool\n\n        // MapRef enables returning maps as MapRef structs instead of map[string]any.\n        // When true, map values include an Implicit flag indicating whether\n        // the map was created implicitly (without braces). Default: false.\n        MapRef *bool\n}\n\n// SafeOptions controls key safety.\ntype SafeOptions struct {\n        Key *bool // Prevent __proto__ keys. Default: true.\n}\n\n// FixedOptions controls fixed token recognition.\ntype FixedOptions struct {\n        Lex *bool // Enable fixed tokens. Default: true.\n}\n\n// SpaceOptions controls space lexing.\ntype SpaceOptions struct {\n        Lex   *bool  // Enable space lexing. Default: true.\n        Chars string // Space characters. Default: \" \\t\".\n}\n\n// LineOptions controls line-ending lexing.\ntype LineOptions struct {\n        Lex      *bool  // Enable line lexing. Default: true.\n        Chars    string // Line characters. Default: \"\\r\\n\".\n        RowChars string // Row-counting characters. Default: \"\\n\".\n        Single   *bool  // Generate separate tokens per newline. Default: false.\n}\n\n// ValModifier transforms a text token value after lexing.\ntype ValModifier func(val any) any\n\n// TextOptions controls unquoted text lexing.\ntype TextOptions struct {\n        Lex    *bool         // Enable text matching. Default: true.\n        Modify []ValModifier // Pipeline of value modifiers applied after text matching.\n}\n\n// NumberOptions controls numeric literal lexing.\ntype NumberOptions struct {\n        Lex     *bool             // Enable number matching. Default: true.\n        Hex     *bool             // Support 0x hex format. Default: true.\n        Oct     *bool             // Support 0o octal format. Default: true.\n        Bin     *bool             // Support 0b binary format. Default: true.\n        Sep     string            // Number separator character. Default: \"_\". Empty string disables.\n        Exclude func(string) bool // Exclude certain number-like strings from number matching.\n}\n\n// CommentDef defines a single comment type.\ntype CommentDef struct {\n        Line    bool   // true = line comment, false = block comment.\n        Start   string // Start marker, e.g. \"#\", \"//\", \"/*\".\n        End     string // End marker for block comments, e.g. \"*/\".\n        Lex     *bool  // Enable this comment type. Default: true.\n        EatLine *bool  // Also consume trailing line chars. Default: false.\n}\n\n// CommentOptions controls comment lexing.\ntype CommentOptions struct {\n        Lex *bool                  // Enable all comment lexing. Default: true.\n        Def map[string]*CommentDef // Comment type definitions.\n}\n\n// StringOptions controls quoted string lexing.\ntype StringOptions struct {\n        Lex          *bool             // Enable string matching. Default: true.\n        Chars        string            // Quote characters. Default: `'\"` + \"`\".\n        MultiChars   string            // Multiline quote characters. Default: \"`\".\n        EscapeChar   string            // Escape character. Default: \"\\\\\".\n        Escape       map[string]string // Escape mappings, e.g. {\"n\": \"\\n\"}.\n        AllowUnknown *bool             // Allow unknown escapes. Default: true.\n        Abandon      *bool             // On string error, return nil to let next matcher try. Default: false.\n        Replace      map[rune]string   // Character replacements applied during string scanning.\n}\n\n// MapMergeFunc is a custom merge function for duplicate map keys.\n// Receives the previous value, new value, rule, and context.\ntype MapMergeFunc func(prev, val any, r *Rule, ctx *Context) any\n\n// MapOptions controls object/map behavior.\ntype MapOptions struct {\n        Extend *bool        // Deep-merge duplicate keys. Default: true.\n        Child  *bool        // Parse bare colon as child$ key: {:1} → {\"child$\":1}. Default: false.\n        Merge  MapMergeFunc // Custom merge function for duplicate keys. Takes precedence over Extend.\n}\n\n// ListOptions controls array/list behavior.\ntype ListOptions struct {\n        Property *bool // Allow named properties in arrays [a:1]. Default: true.\n        Pair     *bool // Push pairs as object elements: [a:1] → [{\"a\":1}]. Default: false.\n        Child    *bool // Parse bare colon as child value: [:1] → ListRef with Child=1. Default: false.\n}\n\n// ValueDef defines a keyword value.\ntype ValueDef struct {\n        Val any // Value to produce for this keyword.\n}\n\n// ValueOptions controls keyword value matching.\ntype ValueOptions struct {\n        Lex *bool                // Enable value matching. Default: true.\n        Def map[string]*ValueDef // Keyword definitions, e.g. {\"true\": {Val: true}}.\n}\n\n// RuleOptions controls parser rule behavior.\ntype RuleOptions struct {\n        Start   string   // Starting rule name. Default: \"val\".\n        Finish  *bool    // Auto-close unclosed structures at EOF. Default: true.\n        MaxMul  *int     // Max rule occurrence multiplier. Default: 3.\n        Exclude string   // Comma-separated group tags to exclude from grammar.\n}\n\n// LexOptions controls global lex behavior.\ntype LexOptions struct {\n        Empty       *bool // Allow empty source. Default: true.\n        EmptyResult any   // Result for empty source. Default: nil.\n\n        // Match defines custom lexer matchers, keyed by name.\n        // Matches the TypeScript pattern: jsonic.options({ lex: { match: { name: { order, make } } } })\n        Match map[string]*MatchSpec\n}\n\n// ParserOptions allows custom parser overrides.\ntype ParserOptions struct {\n        Start func(src string, j *Jsonic, meta map[string]any) (any, error)\n}\n\n// ResultOptions controls parse result validation.\n// Matches the TypeScript result option.\ntype ResultOptions struct {\n        // Fail lists values that are treated as parse failures.\n        // If the parse result matches any of these, an \"unexpected\" error is returned.\n        // E.g. Fail: []any{nil} would make nil results fail.\n        Fail []any\n}\n\n// ConfigModifier is a function that modifies the LexConfig after construction.\ntype ConfigModifier func(cfg *LexConfig, opts *Options)\n\n// idCounter is used to generate unique Jsonic instance IDs.\nvar idCounter int\n\n// Jsonic is a configured parser instance, equivalent to TypeScript's Jsonic.make().\ntype Jsonic struct {\n        id           string             // Unique instance identifier (TS: jsonic.id)\n        options      *Options\n        parser       *Parser\n        plugins      []pluginEntry      // Registered plugins\n        tinByName    map[string]Tin     // Custom token name → Tin\n        nameByTin    map[Tin]string     // Custom Tin → token name\n        nextTin      Tin                // Next available Tin for allocation\n        lexSubs      []LexSub           // Lex event subscribers\n        ruleSubs     []RuleSub          // Rule event subscribers\n        hints        map[string]string  // Error hints per error code\n        emptyAllow   bool               // Allow empty source\n        emptyResult  any                // Result for empty source\n        parserStart  func(src string, j *Jsonic, meta map[string]any) (any, error)\n        inSetOptions bool               // Re-entrancy guard for SetOptions\n        decorations     map[string]any     // Plugin decorations (TS: jsonic.foo = value)\n        pluginOpts      map[string]map[string]any // Plugin options namespace (TS: options.plugin)\n        customTokenSets map[string][]Tin  // Custom token sets (TS: options.tokenSet)\n}\n\n// Decorate sets a named value on this instance. This is the Go equivalent of\n// the TypeScript pattern where plugins add properties to the jsonic instance\n// (e.g. jsonic.foo = () =&gt; 'FOO'). Decorations are inherited by Derive.\nfunc (j *Jsonic) Decorate(name string, value any) *Jsonic <span class=\"cov8\" title=\"1\">{\n        if j.decorations == nil </span><span class=\"cov8\" title=\"1\">{\n                j.decorations = make(map[string]any)\n        }</span>\n        <span class=\"cov8\" title=\"1\">j.decorations[name] = value\n        return j</span>\n}\n\n// Decoration returns a named value previously set by Decorate.\n// Returns nil if the name has not been set.\nfunc (j *Jsonic) Decoration(name string) any <span class=\"cov8\" title=\"1\">{\n        if j.decorations == nil </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">return j.decorations[name]</span>\n}\n\n// Id returns the unique instance identifier for this Jsonic instance.\n// Matches TS jsonic.id.\nfunc (j *Jsonic) Id() string <span class=\"cov8\" title=\"1\">{\n        return j.id\n}</span>\n\n// PluginOptions returns the options stored for a named plugin.\n// Matches TS `jsonic.options.plugin[name]`.\nfunc (j *Jsonic) PluginOptions(name string) map[string]any <span class=\"cov8\" title=\"1\">{\n        if j.pluginOpts == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">return j.pluginOpts[name]</span>\n}\n\n// SetPluginOptions stores options for a named plugin.\n// Matches TS `jsonic.options({ plugin: { name: opts } })`.\nfunc (j *Jsonic) SetPluginOptions(name string, opts map[string]any) <span class=\"cov8\" title=\"1\">{\n        if j.pluginOpts == nil </span><span class=\"cov8\" title=\"1\">{\n                j.pluginOpts = make(map[string]map[string]any)\n        }</span>\n        <span class=\"cov8\" title=\"1\">existing := j.pluginOpts[name]\n        if existing == nil </span><span class=\"cov8\" title=\"1\">{\n                j.pluginOpts[name] = opts\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                for k, v := range opts </span><span class=\"cov8\" title=\"1\">{\n                        existing[k] = v\n                }</span>\n        }\n}\n\n// Make creates a new Jsonic parser instance with the given options.\n// Unset option fields fall back to defaults, matching TypeScript Jsonic.make().\nfunc Make(opts ...Options) *Jsonic <span class=\"cov8\" title=\"1\">{\n        var o Options\n        if len(opts) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                o = opts[0]\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">cfg := buildConfig(&amp;o)\n        rsm := make(map[string]*RuleSpec)\n        buildGrammar(rsm, cfg)\n\n        maxmul := 3\n        if o.Rule != nil &amp;&amp; o.Rule.MaxMul != nil </span><span class=\"cov0\" title=\"0\">{\n                maxmul = *o.Rule.MaxMul\n        }</span>\n\n        // Copy global FixedTokens into the config for per-instance customization.\n        <span class=\"cov8\" title=\"1\">cfg.FixedTokens = make(map[string]Tin, len(FixedTokens))\n        for k, v := range FixedTokens </span><span class=\"cov8\" title=\"1\">{\n                cfg.FixedTokens[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">cfg.SortFixedTokens()\n\n        // Copy global error messages as defaults.\n        msgs := make(map[string]string, len(errorMessages))\n        for k, v := range errorMessages </span><span class=\"cov8\" title=\"1\">{\n                msgs[k] = v\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">p := &amp;Parser{Config: cfg, RSM: rsm, MaxMul: maxmul, ErrorMessages: msgs}\n\n        // Initialize built-in token name mappings.\n        tinByName := map[string]Tin{\n                \"#BD\": TinBD, \"#ZZ\": TinZZ, \"#UK\": TinUK, \"#AA\": TinAA,\n                \"#SP\": TinSP, \"#LN\": TinLN, \"#CM\": TinCM, \"#NR\": TinNR,\n                \"#ST\": TinST, \"#TX\": TinTX, \"#VL\": TinVL, \"#OB\": TinOB,\n                \"#CB\": TinCB, \"#OS\": TinOS, \"#CS\": TinCS, \"#CL\": TinCL,\n                \"#CA\": TinCA,\n        }\n        nameByTin := make(map[Tin]string, len(tinByName))\n        for name, tin := range tinByName </span><span class=\"cov8\" title=\"1\">{\n                nameByTin[tin] = name\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">idCounter++\n        tag := \"\"\n        if o.Tag != \"\" </span><span class=\"cov8\" title=\"1\">{\n                tag = \"/\" + o.Tag\n        }</span>\n        <span class=\"cov8\" title=\"1\">instanceId := \"Jsonic/\" + strconv.Itoa(idCounter) + tag\n\n        j := &amp;Jsonic{\n                id:          instanceId,\n                options:     &amp;o,\n                parser:      p,\n                tinByName:   tinByName,\n                nameByTin:   nameByTin,\n                nextTin:     TinMAX,\n                emptyAllow:  true, // default: allow empty source\n        }\n\n        // Apply custom error messages.\n        if o.Error != nil </span><span class=\"cov8\" title=\"1\">{\n                for k, v := range o.Error </span><span class=\"cov8\" title=\"1\">{\n                        j.parser.ErrorMessages[k] = v\n                }</span>\n        }\n\n        // Apply error hints.\n        <span class=\"cov8\" title=\"1\">if o.Hint != nil </span><span class=\"cov8\" title=\"1\">{\n                j.hints = make(map[string]string, len(o.Hint))\n                j.parser.Hints = make(map[string]string, len(o.Hint))\n                for k, v := range o.Hint </span><span class=\"cov8\" title=\"1\">{\n                        j.hints[k] = v\n                        j.parser.Hints[k] = v\n                }</span>\n        }\n\n        // Apply errmsg options.\n        <span class=\"cov8\" title=\"1\">if o.ErrMsg != nil &amp;&amp; o.ErrMsg.Name != \"\" </span><span class=\"cov8\" title=\"1\">{\n                j.parser.ErrTag = o.ErrMsg.Name\n        }</span>\n\n        // Apply lex options (empty source handling).\n        <span class=\"cov8\" title=\"1\">if o.Lex != nil </span><span class=\"cov8\" title=\"1\">{\n                if o.Lex.Empty != nil </span><span class=\"cov8\" title=\"1\">{\n                        j.emptyAllow = *o.Lex.Empty\n                }</span>\n                <span class=\"cov8\" title=\"1\">j.emptyResult = o.Lex.EmptyResult</span>\n        }\n\n        // Apply custom parser start.\n        <span class=\"cov8\" title=\"1\">if o.Parser != nil &amp;&amp; o.Parser.Start != nil </span><span class=\"cov8\" title=\"1\">{\n                j.parserStart = o.Parser.Start\n        }</span>\n\n        // Apply rule exclude.\n        <span class=\"cov8\" title=\"1\">if o.Rule != nil &amp;&amp; o.Rule.Exclude != \"\" </span><span class=\"cov8\" title=\"1\">{\n                j.Exclude(o.Rule.Exclude)\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return j</span>\n}\n\n// Empty creates a Jsonic instance with no built-in grammar rules.\n// Matches TS jsonic.empty(). Useful for building a parser from scratch.\nfunc Empty(opts ...Options) *Jsonic <span class=\"cov8\" title=\"1\">{\n        j := Make(opts...)\n        // Clear all grammar rules.\n        for _, rs := range j.parser.RSM </span><span class=\"cov8\" title=\"1\">{\n                rs.Clear()\n        }</span>\n        <span class=\"cov8\" title=\"1\">return j</span>\n}\n\n// Parse parses a jsonic string using this instance's configuration.\nfunc (j *Jsonic) Parse(src string) (any, error) <span class=\"cov8\" title=\"1\">{\n        return j.parseInternal(src, nil)\n}</span>\n\n// parseInternal handles empty source, custom parser.start, and delegation.\nfunc (j *Jsonic) parseInternal(src string, meta map[string]any) (any, error) <span class=\"cov8\" title=\"1\">{\n        // Handle empty source.\n        if src == \"\" </span><span class=\"cov8\" title=\"1\">{\n                if !j.emptyAllow </span><span class=\"cov8\" title=\"1\">{\n                        return nil, j.parser.makeError(\"unexpected\", \"\", src, 0, 1, 1)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return j.emptyResult, nil</span>\n        }\n\n        // Custom parser start.\n        <span class=\"cov8\" title=\"1\">if j.parserStart != nil </span><span class=\"cov8\" title=\"1\">{\n                result, err := j.parserStart(src, j, meta)\n                return result, j.attachHint(err)\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">result, err := j.parser.startParse(src, meta, j.lexSubs, j.ruleSubs, j)\n        return result, j.attachHint(err)</span>\n}\n\n// attachHint adds hint text to a JsonicError if hints are configured.\nfunc (j *Jsonic) attachHint(err error) error <span class=\"cov8\" title=\"1\">{\n        if err == nil || j.hints == nil </span><span class=\"cov8\" title=\"1\">{\n                return err\n        }</span>\n        <span class=\"cov8\" title=\"1\">if je, ok := err.(*JsonicError); ok &amp;&amp; je.Hint == \"\" </span><span class=\"cov8\" title=\"1\">{\n                if hint, ok := j.hints[je.Code]; ok </span><span class=\"cov8\" title=\"1\">{\n                        je.Hint = hint\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return err</span>\n}\n\n// Options returns a copy of this instance's options.\nfunc (j *Jsonic) Options() Options <span class=\"cov8\" title=\"1\">{\n        if j.options != nil </span><span class=\"cov8\" title=\"1\">{\n                return *j.options\n        }</span>\n        <span class=\"cov0\" title=\"0\">return Options{}</span>\n}\n\n// boolPtr is a helper to create a *bool.\nfunc boolPtr(b bool) *bool <span class=\"cov8\" title=\"1\">{\n        return &amp;b\n}</span>\n\n// intPtr is a helper to create a *int.\nfunc intPtr(i int) *int <span class=\"cov0\" title=\"0\">{\n        return &amp;i\n}</span>\n\n// boolVal returns the value of a *bool, or the default if nil.\nfunc boolVal(p *bool, def bool) bool <span class=\"cov8\" title=\"1\">{\n        if p != nil </span><span class=\"cov8\" title=\"1\">{\n                return *p\n        }</span>\n        <span class=\"cov8\" title=\"1\">return def</span>\n}\n\n// buildConfig converts Options into a LexConfig, applying defaults for unset fields.\nfunc buildConfig(o *Options) *LexConfig <span class=\"cov8\" title=\"1\">{\n        cfg := &amp;LexConfig{}\n\n        // Fixed tokens\n        cfg.FixedLex = boolVal(optBool(o.Fixed, func(f *FixedOptions) *bool </span><span class=\"cov0\" title=\"0\">{ return f.Lex }</span>), true)\n\n        // Space\n        <span class=\"cov8\" title=\"1\">cfg.SpaceLex = boolVal(optBool(o.Space, func(s *SpaceOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return s.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Space != nil &amp;&amp; o.Space.Chars != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.SpaceChars = runeSet(o.Space.Chars)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.SpaceChars = map[rune]bool{' ': true, '\\t': true}\n        }</span>\n\n        // Line\n        <span class=\"cov8\" title=\"1\">cfg.LineLex = boolVal(optBool(o.Line, func(l *LineOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return l.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">cfg.LineSingle = boolVal(optBool(o.Line, func(l *LineOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return l.Single }</span>), false)\n        <span class=\"cov8\" title=\"1\">if o.Line != nil &amp;&amp; o.Line.Chars != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.LineChars = runeSet(o.Line.Chars)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.LineChars = map[rune]bool{'\\r': true, '\\n': true}\n        }</span>\n        <span class=\"cov8\" title=\"1\">if o.Line != nil &amp;&amp; o.Line.RowChars != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.RowChars = runeSet(o.Line.RowChars)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.RowChars = map[rune]bool{'\\n': true}\n        }</span>\n\n        // Text\n        <span class=\"cov8\" title=\"1\">cfg.TextLex = boolVal(optBool(o.Text, func(t *TextOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return t.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Text != nil &amp;&amp; len(o.Text.Modify) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                cfg.TextModify = o.Text.Modify\n        }</span>\n\n        // Number\n        <span class=\"cov8\" title=\"1\">cfg.NumberLex = boolVal(optBool(o.Number, func(n *NumberOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return n.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Number != nil &amp;&amp; o.Number.Exclude != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.NumberExclude = o.Number.Exclude\n        }</span>\n        <span class=\"cov8\" title=\"1\">cfg.NumberHex = boolVal(optBool(o.Number, func(n *NumberOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return n.Hex }</span>), true)\n        <span class=\"cov8\" title=\"1\">cfg.NumberOct = boolVal(optBool(o.Number, func(n *NumberOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return n.Oct }</span>), true)\n        <span class=\"cov8\" title=\"1\">cfg.NumberBin = boolVal(optBool(o.Number, func(n *NumberOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return n.Bin }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Number != nil &amp;&amp; o.Number.Sep != \"\" </span><span class=\"cov8\" title=\"1\">{\n                cfg.NumberSep = rune(o.Number.Sep[0])\n        }</span> else<span class=\"cov8\" title=\"1\"> if o.Number != nil &amp;&amp; o.Number.Sep == \"\" &amp;&amp; o.Number.Lex != nil </span><span class=\"cov8\" title=\"1\">{\n                // Explicitly set to empty: disable separator\n                cfg.NumberSep = 0\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.NumberSep = '_'\n        }</span>\n\n        // Comment\n        <span class=\"cov8\" title=\"1\">cfg.CommentLex = boolVal(optBool(o.Comment, func(c *CommentOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return c.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Comment != nil &amp;&amp; o.Comment.Def != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.CommentLine = nil\n                cfg.CommentBlock = nil\n                cfg.CommentLineEatLine = make(map[string]bool)\n                cfg.CommentBlockEatLine = make(map[string]bool)\n                for _, def := range o.Comment.Def </span><span class=\"cov8\" title=\"1\">{\n                        if def == nil || !boolVal(def.Lex, true) </span><span class=\"cov8\" title=\"1\">{\n                                continue</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">eatLine := boolVal(def.EatLine, false)\n                        if def.Line </span><span class=\"cov8\" title=\"1\">{\n                                cfg.CommentLine = append(cfg.CommentLine, def.Start)\n                                if eatLine </span><span class=\"cov8\" title=\"1\">{\n                                        cfg.CommentLineEatLine[def.Start] = true\n                                }</span>\n                        } else<span class=\"cov8\" title=\"1\"> {\n                                cfg.CommentBlock = append(cfg.CommentBlock, [2]string{def.Start, def.End})\n                                if eatLine </span><span class=\"cov0\" title=\"0\">{\n                                        cfg.CommentBlockEatLine[def.Start] = true\n                                }</span>\n                        }\n                }\n        } else<span class=\"cov8\" title=\"1\"> {\n                cfg.CommentLine = []string{\"#\", \"//\"}\n                cfg.CommentBlock = [][2]string{{\"/*\", \"*/\"}}\n        }</span>\n\n        // String\n        <span class=\"cov8\" title=\"1\">cfg.StringLex = boolVal(optBool(o.String, func(s *StringOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return s.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.String != nil &amp;&amp; o.String.Chars != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.StringChars = runeSet(o.String.Chars)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.StringChars = map[rune]bool{'\\'': true, '\"': true, '`': true}\n        }</span>\n        <span class=\"cov8\" title=\"1\">if o.String != nil &amp;&amp; o.String.MultiChars != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.MultiChars = runeSet(o.String.MultiChars)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.MultiChars = map[rune]bool{'`': true}\n        }</span>\n        <span class=\"cov8\" title=\"1\">if o.String != nil &amp;&amp; o.String.EscapeChar != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.EscapeChar = rune(o.String.EscapeChar[0])\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.EscapeChar = '\\\\'\n        }</span>\n        <span class=\"cov8\" title=\"1\">cfg.AllowUnknownEscape = boolVal(optBool(o.String, func(s *StringOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return s.AllowUnknown }</span>), true)\n        <span class=\"cov8\" title=\"1\">cfg.StringAbandon = boolVal(optBool(o.String, func(s *StringOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return s.Abandon }</span>), false)\n        <span class=\"cov8\" title=\"1\">if o.String != nil &amp;&amp; o.String.Replace != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.StringReplace = o.String.Replace\n        }</span>\n        <span class=\"cov8\" title=\"1\">if o.String != nil &amp;&amp; o.String.Escape != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.EscapeMap = make(map[string]string, len(o.String.Escape))\n                for k, v := range o.String.Escape </span><span class=\"cov8\" title=\"1\">{\n                        cfg.EscapeMap[k] = v\n                }</span>\n        }\n\n        // Ender\n        <span class=\"cov8\" title=\"1\">if len(o.Ender) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                cfg.EnderChars = make(map[rune]bool)\n                for _, e := range o.Ender </span><span class=\"cov8\" title=\"1\">{\n                        for _, r := range e </span><span class=\"cov8\" title=\"1\">{\n                                cfg.EnderChars[r] = true\n                        }</span>\n                }\n        }\n\n        // Value\n        <span class=\"cov8\" title=\"1\">cfg.ValueLex = boolVal(optBool(o.Value, func(v *ValueOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return v.Lex }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Value != nil &amp;&amp; o.Value.Def != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.ValueDef = make(map[string]any)\n                for k, v := range o.Value.Def </span><span class=\"cov8\" title=\"1\">{\n                        if v != nil </span><span class=\"cov8\" title=\"1\">{\n                                cfg.ValueDef[k] = v.Val\n                        }</span>\n                }\n        }\n\n        // Map\n        <span class=\"cov8\" title=\"1\">cfg.MapExtend = boolVal(optBool(o.Map, func(m *MapOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return m.Extend }</span>), true)\n        <span class=\"cov8\" title=\"1\">cfg.MapChild = boolVal(optBool(o.Map, func(m *MapOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return m.Child }</span>), false)\n        <span class=\"cov8\" title=\"1\">if o.Map != nil &amp;&amp; o.Map.Merge != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.MapMerge = o.Map.Merge\n        }</span>\n\n        // List\n        <span class=\"cov8\" title=\"1\">cfg.ListProperty = boolVal(optBool(o.List, func(l *ListOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return l.Property }</span>), true)\n        <span class=\"cov8\" title=\"1\">cfg.ListPair = boolVal(optBool(o.List, func(l *ListOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return l.Pair }</span>), false)\n        <span class=\"cov8\" title=\"1\">cfg.ListChild = boolVal(optBool(o.List, func(l *ListOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return l.Child }</span>), false)\n\n        // Rule\n        <span class=\"cov8\" title=\"1\">cfg.FinishRule = boolVal(optBool(o.Rule, func(r *RuleOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return r.Finish }</span>), true)\n        <span class=\"cov8\" title=\"1\">if o.Rule != nil &amp;&amp; o.Rule.Start != \"\" </span><span class=\"cov0\" title=\"0\">{\n                cfg.RuleStart = o.Rule.Start\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                cfg.RuleStart = \"val\"\n        }</span>\n\n        // Safe\n        <span class=\"cov8\" title=\"1\">cfg.SafeKey = boolVal(optBool(o.Safe, func(s *SafeOptions) *bool </span><span class=\"cov8\" title=\"1\">{ return s.Key }</span>), true)\n\n        // Initialize per-instance token sets from defaults (matching TS cfg.tokenSetTins).\n        <span class=\"cov8\" title=\"1\">cfg.IgnoreSet = map[Tin]bool{TinSP: true, TinLN: true, TinCM: true}\n        cfg.ValSet = make([]Tin, len(TinSetVAL))\n        copy(cfg.ValSet, TinSetVAL)\n        cfg.KeySet = make([]Tin, len(TinSetKEY))\n        copy(cfg.KeySet, TinSetKEY)\n\n        // Property (Go-specific options)\n        if o.Property != nil </span><span class=\"cov8\" title=\"1\">{\n                cfg.TextInfo = boolVal(o.Property.TextInfo, false)\n                cfg.ListRef = boolVal(o.Property.ListRef, false)\n                cfg.MapRef = boolVal(o.Property.MapRef, false)\n        }</span>\n        // list.child requires ListRef to store the child value on ListRef.Child.\n        <span class=\"cov8\" title=\"1\">if cfg.ListChild </span><span class=\"cov8\" title=\"1\">{\n                cfg.ListRef = true\n        }</span>\n\n        // Result fail values.\n        <span class=\"cov8\" title=\"1\">if o.Result != nil &amp;&amp; len(o.Result.Fail) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                cfg.ResultFail = append(cfg.ResultFail, o.Result.Fail...)\n        }</span>\n\n        // Apply config modifiers.\n        <span class=\"cov8\" title=\"1\">if o.Property != nil &amp;&amp; o.Property.ConfigModify != nil </span><span class=\"cov8\" title=\"1\">{\n                for _, mod := range o.Property.ConfigModify </span><span class=\"cov8\" title=\"1\">{\n                        mod(cfg, o)\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return cfg</span>\n}\n\n// optBool extracts a *bool from an optional sub-options struct.\nfunc optBool[T any](outer *T, getter func(*T) *bool) *bool <span class=\"cov8\" title=\"1\">{\n        if outer == nil </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">return getter(outer)</span>\n}\n\n// runeSet converts a string into a rune presence map.\nfunc runeSet(s string) map[rune]bool <span class=\"cov0\" title=\"0\">{\n        m := make(map[rune]bool, len(s))\n        for _, r := range s </span><span class=\"cov0\" title=\"0\">{\n                m[r] = true\n        }</span>\n        <span class=\"cov0\" title=\"0\">return m</span>\n}</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file6\" style=\"display: none\">package jsonic\n\nimport (\n        \"math\"\n        \"strconv\"\n        \"strings\"\n)\n\n// Context holds the parse state, matching the TypeScript Context type.\ntype Context struct {\n        UI       int               // Unique rule ID counter (TS: uI)\n        T0       *Token            // Current token (TS: t0)\n        T1       *Token            // Next token / lookahead (TS: t1)\n        V1       *Token            // Previous token (TS: v1)\n        V2       *Token            // Previous previous token (TS: v2)\n        RS       []*Rule           // Rule stack (TS: rs)\n        RSI      int               // Rule stack index (TS: rsI)\n        RSM      map[string]*RuleSpec // Rule spec map (TS: rsm)\n        KI       int               // Iteration counter (TS: kI)\n        Rule     *Rule             // Current parsing rule (TS: rule)\n        Meta     map[string]any    // Parse metadata (TS: meta)\n        LexSubs  []LexSub          // Lex event subscribers (TS: sub.lex)\n        RuleSubs []RuleSub         // Rule event subscribers (TS: sub.rule)\n        ParseErr *Token            // Error token, halts parse\n\n        // Fields matching TS Context:\n        Opts     *Options          // Jsonic instance options (TS: opts)\n        Cfg      *LexConfig        // Jsonic instance config (TS: cfg)\n        Src      string            // Source text being parsed (TS: src)\n        Inst     *Jsonic           // Current Jsonic instance (TS: inst)\n        U        map[string]any    // Custom plugin data bag (TS: u)\n        Root     *Rule             // Root rule (TS: root)\n        TC       int               // Token count (TS: tC)\n        F        func(any) string  // Format value as string (TS: F)\n        Log      func(...any)      // Debug logger (TS: log)\n        NOTOKEN  *Token            // Sentinel no-token (TS: NOTOKEN)\n        NORULE   *Rule             // Sentinel no-rule (TS: NORULE)\n}\n\n// Parser orchestrates the parsing process.\ntype Parser struct {\n        Config        *LexConfig\n        RSM           map[string]*RuleSpec\n        MaxMul        int               // Max rule occurrence multiplier. Default: 3.\n        ErrorMessages map[string]string  // Custom error message templates.\n        Hints         map[string]string  // Explanatory hints per error code.\n        ErrTag        string             // Custom error tag (TS: errmsg.name). Default: \"jsonic\".\n}\n\n// NewParser creates a parser with default configuration.\nfunc NewParser() *Parser <span class=\"cov8\" title=\"1\">{\n        cfg := DefaultLexConfig()\n        rsm := make(map[string]*RuleSpec)\n        buildGrammar(rsm, cfg)\n        // Copy global error messages as defaults.\n        msgs := make(map[string]string, len(errorMessages))\n        for k, v := range errorMessages </span><span class=\"cov8\" title=\"1\">{\n                msgs[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">return &amp;Parser{Config: cfg, RSM: rsm, MaxMul: 3, ErrorMessages: msgs}</span>\n}\n\n// Start parses the source string and returns the result.\n// Returns a *JsonicError if parsing fails.\nfunc (p *Parser) Start(src string) (any, error) <span class=\"cov8\" title=\"1\">{\n        return p.startParse(src, nil, nil, nil, nil)\n}</span>\n\n// StartMeta parses the source string with metadata, subscriptions, and\n// an optional Jsonic instance reference (for Context.Inst).\nfunc (p *Parser) StartMeta(src string, meta map[string]any, lexSubs []LexSub, ruleSubs []RuleSub) (any, error) <span class=\"cov0\" title=\"0\">{\n        return p.startParse(src, meta, lexSubs, ruleSubs, nil)\n}</span>\n\n// startParse is the internal entry point that populates the full Context.\nfunc (p *Parser) startParse(src string, meta map[string]any, lexSubs []LexSub, ruleSubs []RuleSub, inst *Jsonic) (any, error) <span class=\"cov8\" title=\"1\">{\n        if src == \"\" </span><span class=\"cov8\" title=\"1\">{\n                return nil, nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">lex := NewLex(src, p.Config)\n\n        var opts *Options\n        if inst != nil </span><span class=\"cov8\" title=\"1\">{\n                opts = inst.options\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">ctx := &amp;Context{\n                UI:       0,\n                T0:       NoToken,\n                T1:       NoToken,\n                V1:       NoToken,\n                V2:       NoToken,\n                RS:       make([]*Rule, len(src)*4+100),\n                RSI:      0,\n                RSM:      p.RSM,\n                Meta:     meta,\n                LexSubs:  lexSubs,\n                RuleSubs: ruleSubs,\n                Opts:     opts,\n                Cfg:      p.Config,\n                Src:      src,\n                Inst:     inst,\n                U:        make(map[string]any),\n                TC:       0,\n                NOTOKEN:  NoToken,\n                NORULE:   NoRule,\n                F:        func(v any) string </span><span class=\"cov8\" title=\"1\">{ return Str(v, 44) }</span>,\n        }\n\n        <span class=\"cov8\" title=\"1\">lex.Ctx = ctx\n\n        startName := p.Config.RuleStart\n        if startName == \"\" </span><span class=\"cov0\" title=\"0\">{\n                startName = \"val\"\n        }</span>\n        <span class=\"cov8\" title=\"1\">startSpec := p.RSM[startName]\n        if startSpec == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil, nil\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">rule := MakeRule(startSpec, ctx, nil)\n        root := rule\n        ctx.Root = root\n\n        // Run parse.prepare hooks\n        if len(p.Config.ParsePrepare) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, prep := range p.Config.ParsePrepare </span><span class=\"cov8\" title=\"1\">{\n                        prep(ctx)\n                }</span>\n        }\n\n        // Maximum iterations: 2 * numRules * srcLen * 2 * maxmul\n        <span class=\"cov8\" title=\"1\">maxmul := p.MaxMul\n        if maxmul &lt;= 0 </span><span class=\"cov0\" title=\"0\">{\n                maxmul = 3\n        }</span>\n        <span class=\"cov8\" title=\"1\">maxr := 2 * len(p.RSM) * len(src) * 2 * maxmul\n        if maxr &lt; 100 </span><span class=\"cov8\" title=\"1\">{\n                maxr = 100\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">kI := 0\n        for rule != NoRule &amp;&amp; kI &lt; maxr </span><span class=\"cov8\" title=\"1\">{\n                ctx.KI = kI\n                ctx.Rule = rule\n\n                // Fire rule subscribers BEFORE process (matching TS).\n                if len(ctx.RuleSubs) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                        for _, sub := range ctx.RuleSubs </span><span class=\"cov8\" title=\"1\">{\n                                sub(rule, ctx)\n                        }</span>\n                }\n\n                <span class=\"cov8\" title=\"1\">rule = rule.Process(ctx, lex)\n\n                // Check for parse error from alt.E or actions.\n                if ctx.ParseErr != nil </span><span class=\"cov8\" title=\"1\">{\n                        tkn := ctx.ParseErr\n                        return nil, p.makeError(\"unexpected\", tkn.Src, src, tkn.SI, tkn.RI, tkn.CI)\n                }</span>\n\n                <span class=\"cov8\" title=\"1\">kI++</span>\n        }\n\n        // Check for lexer errors (unterminated strings, comments, etc.)\n        <span class=\"cov8\" title=\"1\">if lex.Err != nil </span><span class=\"cov8\" title=\"1\">{\n                return nil, lex.Err\n        }</span>\n\n        // Check for unconsumed tokens (syntax error) - explicit trailing content check.\n        // First check tokens already in the lookahead buffer.\n        <span class=\"cov8\" title=\"1\">if ctx.T0 != nil &amp;&amp; !ctx.T0.IsNoToken() &amp;&amp; ctx.T0.Tin != TinZZ </span><span class=\"cov0\" title=\"0\">{\n                return nil, p.makeError(\"unexpected\", ctx.T0.Src, src, ctx.T0.SI, ctx.T0.RI, ctx.T0.CI)\n        }</span>\n        // Also explicitly ask lexer for more (matching TS parser.ts:187-189).\n        <span class=\"cov8\" title=\"1\">endTkn := lex.Next(rule)\n        if endTkn.Tin != TinZZ </span><span class=\"cov0\" title=\"0\">{\n                return nil, p.makeError(\"unexpected\", endTkn.Src, src, endTkn.SI, endTkn.RI, endTkn.CI)\n        }</span>\n        // Check lexer errors from that final Next() call.\n        <span class=\"cov8\" title=\"1\">if lex.Err != nil </span><span class=\"cov0\" title=\"0\">{\n                return nil, lex.Err\n        }</span>\n\n        // Follow replacement chain: when val is replaced by list (implicit list),\n        // root.Node is stale. Follow Next/Prev links to find the actual result.\n        <span class=\"cov8\" title=\"1\">result := root\n        for result.Next != NoRule &amp;&amp; result.Next != nil &amp;&amp; result.Next.Prev == result </span><span class=\"cov8\" title=\"1\">{\n                result = result.Next\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">if IsUndefined(result.Node) </span><span class=\"cov8\" title=\"1\">{\n                return nil, nil\n        }</span>\n\n        // Check result.fail\n        <span class=\"cov8\" title=\"1\">if len(p.Config.ResultFail) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, fail := range p.Config.ResultFail </span><span class=\"cov8\" title=\"1\">{\n                        if result.Node == fail </span><span class=\"cov8\" title=\"1\">{\n                                return nil, p.makeError(\"unexpected\", \"\", src, 0, 1, 1)\n                        }</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">return result.Node, nil</span>\n}\n\n// makeError creates a JsonicError using this parser's error messages.\nfunc (p *Parser) makeError(code, src, fullSource string, pos, row, col int) *JsonicError <span class=\"cov8\" title=\"1\">{\n        msgs := p.ErrorMessages\n        if msgs == nil </span><span class=\"cov0\" title=\"0\">{\n                msgs = errorMessages\n        }</span>\n        <span class=\"cov8\" title=\"1\">tmpl, ok := msgs[code]\n        if !ok </span><span class=\"cov0\" title=\"0\">{\n                tmpl = msgs[\"unknown\"]\n                if tmpl == \"\" </span><span class=\"cov0\" title=\"0\">{\n                        tmpl = errorMessages[\"unknown\"]\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">detail := tmpl + src\n\n        hint := \"\"\n        if p.Hints != nil </span><span class=\"cov8\" title=\"1\">{\n                hint = p.Hints[code]\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return &amp;JsonicError{\n                Code:       code,\n                Detail:     detail,\n                Pos:        pos,\n                Row:        row,\n                Col:        col,\n                Src:        src,\n                Hint:       hint,\n                fullSource: fullSource,\n                tag:        p.ErrTag,\n        }</span>\n}\n\n// parseNumericString converts a numeric string to float64.\n// Handles standard decimals, hex (0x), octal (0o), binary (0b), and signs.\nfunc parseNumericString(s string) float64 <span class=\"cov8\" title=\"1\">{\n        if len(s) == 0 </span><span class=\"cov0\" title=\"0\">{\n                return math.NaN()\n        }</span>\n\n        // Handle sign prefix for special formats\n        <span class=\"cov8\" title=\"1\">sign := 1.0\n        ns := s\n        if ns[0] == '-' </span><span class=\"cov8\" title=\"1\">{\n                sign = -1.0\n                ns = ns[1:]\n        }</span> else<span class=\"cov8\" title=\"1\"> if ns[0] == '+' </span><span class=\"cov8\" title=\"1\">{\n                sign = 1.0\n                ns = ns[1:]\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">if len(ns) &gt;= 2 </span><span class=\"cov8\" title=\"1\">{\n                switch </span>{\n                case ns[0] == '0' &amp;&amp; (ns[1] == 'x' || ns[1] == 'X'):<span class=\"cov8\" title=\"1\">\n                        val, err := strconv.ParseInt(ns[2:], 16, 64)\n                        if err != nil </span><span class=\"cov0\" title=\"0\">{\n                                return math.NaN()\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">return sign * float64(val)</span>\n                case ns[0] == '0' &amp;&amp; (ns[1] == 'o' || ns[1] == 'O'):<span class=\"cov8\" title=\"1\">\n                        val, err := strconv.ParseInt(ns[2:], 8, 64)\n                        if err != nil </span><span class=\"cov0\" title=\"0\">{\n                                return math.NaN()\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">return sign * float64(val)</span>\n                case ns[0] == '0' &amp;&amp; (ns[1] == 'b' || ns[1] == 'B'):<span class=\"cov8\" title=\"1\">\n                        val, err := strconv.ParseInt(ns[2:], 2, 64)\n                        if err != nil </span><span class=\"cov0\" title=\"0\">{\n                                return math.NaN()\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">return sign * float64(val)</span>\n                }\n        }\n\n        // Remove underscores if present\n        <span class=\"cov8\" title=\"1\">ns = strings.ReplaceAll(s, \"_\", \"\")\n\n        val, err := strconv.ParseFloat(ns, 64)\n        if err != nil </span><span class=\"cov0\" title=\"0\">{\n                return math.NaN()\n        }</span>\n\n        // Normalize -0 to 0\n        <span class=\"cov8\" title=\"1\">if val == 0 </span><span class=\"cov8\" title=\"1\">{\n                return 0\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return val</span>\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file7\" style=\"display: none\">package jsonic\n\nimport (\n        \"sort\"\n        \"strings\"\n)\n\n// Plugin is a function that modifies a Jsonic instance.\n// Plugins can add custom tokens, matchers, and rule modifications.\n// Matching the TypeScript pattern: (jsonic, plugin_options?) =&gt; void\ntype Plugin func(j *Jsonic, opts map[string]any)\n\n// LexMatcher is a custom lexer matcher function.\n// It receives the lexer and the current parsing rule, and returns a Token\n// if matched, or nil to pass. The rule parameter allows context-sensitive\n// lexing (e.g. checking lex.Ctx.Rule, rule.K, rule.U, rule.N, or rule.State).\n// The matcher can read the current position via lex.Cursor() and must\n// advance the cursor if it produces a token.\ntype LexMatcher func(lex *Lex, rule *Rule) *Token\n\n// MakeLexMatcher is a factory function that creates a LexMatcher.\n// It receives the lexer config and parser options, matching the TypeScript\n// pattern: (cfg: Config, opts: Options) =&gt; (lex: Lex) =&gt; Token | undefined.\ntype MakeLexMatcher func(cfg *LexConfig, opts *Options) LexMatcher\n\n// MatchSpec defines a custom matcher to be registered via options.\n// This matches the TypeScript pattern: { order: number, make: MakeLexMatcher }.\ntype MatchSpec struct {\n        Order int            // Priority order (lower runs first)\n        Make  MakeLexMatcher // Factory function that creates the matcher\n}\n\n// MatcherEntry holds a named custom matcher with a priority for ordering.\n// Lower priority numbers run first. Built-in matchers use:\n// fixed=2e6, space=3e6, line=4e6, string=5e6, comment=6e6, number=7e6, text=8e6.\n// Custom matchers at priority &lt; 2e6 run before all built-ins (matching TS behavior).\ntype MatcherEntry struct {\n        Name     string\n        Priority int\n        Match    LexMatcher\n}\n\n// RuleDefiner is a callback that modifies a RuleSpec.\n// Plugins use this to add alternates, actions, or conditions to grammar rules.\ntype RuleDefiner func(rs *RuleSpec)\n\n// LexSub is a subscriber callback invoked after each token is lexed.\ntype LexSub func(tkn *Token, rule *Rule, ctx *Context)\n\n// RuleSub is a subscriber callback invoked after each rule step.\ntype RuleSub func(rule *Rule, ctx *Context)\n\n// pluginEntry stores a registered plugin and its options.\ntype pluginEntry struct {\n        plugin Plugin\n        opts   map[string]any\n}\n\n// Use registers and invokes a plugin on this Jsonic instance.\n// The plugin function is called with the Jsonic instance and optional options.\n// Returns the Jsonic instance for chaining.\n//\n// Example:\n//\n//        j := jsonic.Make()\n//        j.Use(myPlugin, map[string]any{\"key\": \"value\"})\nfunc (j *Jsonic) Use(plugin Plugin, opts ...map[string]any) *Jsonic <span class=\"cov8\" title=\"1\">{\n        var pluginOpts map[string]any\n        if len(opts) &gt; 0 &amp;&amp; opts[0] != nil </span><span class=\"cov8\" title=\"1\">{\n                pluginOpts = opts[0]\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">j.plugins = append(j.plugins, pluginEntry{plugin: plugin, opts: pluginOpts})\n        plugin(j, pluginOpts)\n        return j</span>\n}\n\n// Rule modifies or creates a grammar rule by name.\n// The definer callback receives the RuleSpec and can modify its Open/Close\n// alternates, and BO/BC/AO/AC state actions.\n//\n// If the rule does not exist, a new empty RuleSpec is created.\n// Returns the Jsonic instance for chaining.\n//\n// Example:\n//\n//        j.Rule(\"val\", func(rs *RuleSpec) {\n//            rs.Open = append([]*AltSpec{{\n//                S: [][]Tin{{myToken}},\n//                A: func(r *Rule, ctx *Context) { r.Node = \"custom\" },\n//            }}, rs.Open...)\n//        })\nfunc (j *Jsonic) Rule(name string, definer RuleDefiner) *Jsonic <span class=\"cov8\" title=\"1\">{\n        rs := j.parser.RSM[name]\n        if rs == nil </span><span class=\"cov8\" title=\"1\">{\n                rs = &amp;RuleSpec{Name: name}\n                j.parser.RSM[name] = rs\n        }</span>\n        <span class=\"cov8\" title=\"1\">definer(rs)\n        return j</span>\n}\n\n// Token registers a new token type or looks up an existing one.\n// With just a name, it returns the Tin for an existing token.\n// With a name and source character(s), it registers a new fixed token.\n//\n// Returns the Tin (token identification number) for the token.\n//\n// Example:\n//\n//        // Register a new fixed token\n//        TT := j.Token(\"#TL\", \"~\")\n//\n//        // Look up existing token\n//        OB := j.Token(\"#OB\", \"\")\nfunc (j *Jsonic) Token(name string, src ...string) Tin <span class=\"cov8\" title=\"1\">{\n        // Look up existing token by name.\n        if tin, ok := j.tinByName[name]; ok </span><span class=\"cov8\" title=\"1\">{\n                // If src provided, update the fixed token mapping.\n                if len(src) &gt; 0 &amp;&amp; src[0] != \"\" </span><span class=\"cov8\" title=\"1\">{\n                        if j.parser.Config.FixedTokens == nil </span><span class=\"cov0\" title=\"0\">{\n                                j.parser.Config.FixedTokens = make(map[string]Tin)\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">j.parser.Config.FixedTokens[src[0]] = tin\n                        j.parser.Config.SortFixedTokens()</span>\n                }\n                <span class=\"cov8\" title=\"1\">return tin</span>\n        }\n\n        // Allocate a new Tin.\n        <span class=\"cov8\" title=\"1\">tin := j.nextTin\n        j.nextTin++\n\n        j.tinByName[name] = tin\n        j.nameByTin[tin] = name\n\n        // Also store in the config's TinNames for lexer access.\n        if j.parser.Config.TinNames == nil </span><span class=\"cov8\" title=\"1\">{\n                j.parser.Config.TinNames = make(map[Tin]string)\n        }</span>\n        <span class=\"cov8\" title=\"1\">j.parser.Config.TinNames[tin] = name\n\n        // Register as fixed token if src provided.\n        if len(src) &gt; 0 &amp;&amp; src[0] != \"\" </span><span class=\"cov8\" title=\"1\">{\n                if j.parser.Config.FixedTokens == nil </span><span class=\"cov0\" title=\"0\">{\n                        j.parser.Config.FixedTokens = make(map[string]Tin)\n                }</span>\n                <span class=\"cov8\" title=\"1\">j.parser.Config.FixedTokens[src[0]] = tin\n                j.parser.Config.SortFixedTokens()</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return tin</span>\n}\n\n// FixedSrc returns the Tin for a fixed token source string (e.g. \"{\" → TinOB).\n// Returns 0 if the source string is not a fixed token.\n// Matches TS `jsonic.fixed('b')`.\nfunc (j *Jsonic) FixedSrc(src string) Tin <span class=\"cov8\" title=\"1\">{\n        if tin, ok := j.parser.Config.FixedTokens[src]; ok </span><span class=\"cov8\" title=\"1\">{\n                return tin\n        }</span>\n        <span class=\"cov8\" title=\"1\">return 0</span>\n}\n\n// FixedTin returns the source string for a fixed token Tin (e.g. TinOB → \"{\").\n// Returns \"\" if the Tin is not a fixed token.\n// Matches TS `jsonic.fixed(18)`.\nfunc (j *Jsonic) FixedTin(tin Tin) string <span class=\"cov8\" title=\"1\">{\n        for src, t := range j.parser.Config.FixedTokens </span><span class=\"cov8\" title=\"1\">{\n                if t == tin </span><span class=\"cov8\" title=\"1\">{\n                        return src\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return \"\"</span>\n}\n\n// AddMatcher adds a custom lexer matcher with the given name and priority.\n// Matchers are tried in priority order (lower first). Built-in matchers use:\n//\n//        fixed=2000000, space=3000000, line=4000000, string=5000000,\n//        comment=6000000, number=7000000, text=8000000\n//\n// Use priority &lt; 2000000 to run before all built-ins (matching TS match behavior).\n// The matcher receives the current parsing rule for context-sensitive lexing.\n// Returns the Jsonic instance for chaining.\nfunc (j *Jsonic) AddMatcher(name string, priority int, matcher LexMatcher) *Jsonic <span class=\"cov8\" title=\"1\">{\n        entry := &amp;MatcherEntry{\n                Name:     name,\n                Priority: priority,\n                Match:    matcher,\n        }\n        j.parser.Config.CustomMatchers = append(j.parser.Config.CustomMatchers, entry)\n\n        // Keep sorted by priority.\n        sort.Slice(j.parser.Config.CustomMatchers, func(i, k int) bool </span><span class=\"cov0\" title=\"0\">{\n                return j.parser.Config.CustomMatchers[i].Priority &lt; j.parser.Config.CustomMatchers[k].Priority\n        }</span>)\n        <span class=\"cov8\" title=\"1\">return j</span>\n}\n\n\n// Plugins returns the list of installed plugins (for introspection).\nfunc (j *Jsonic) Plugins() []Plugin <span class=\"cov8\" title=\"1\">{\n        out := make([]Plugin, len(j.plugins))\n        for i, pe := range j.plugins </span><span class=\"cov8\" title=\"1\">{\n                out[i] = pe.plugin\n        }</span>\n        <span class=\"cov8\" title=\"1\">return out</span>\n}\n\n// Config returns the parser's LexConfig for direct inspection or modification.\n// Use with care — prefer Token(), Rule(), and AddMatcher() for most plugin work.\nfunc (j *Jsonic) Config() *LexConfig <span class=\"cov8\" title=\"1\">{\n        return j.parser.Config\n}</span>\n\n// RSM returns the rule spec map for direct inspection or modification.\nfunc (j *Jsonic) RSM() map[string]*RuleSpec <span class=\"cov8\" title=\"1\">{\n        return j.parser.RSM\n}</span>\n\n// TinName returns the name for a Tin value, checking both built-in and custom tokens.\nfunc (j *Jsonic) TinName(tin Tin) string <span class=\"cov8\" title=\"1\">{\n        if name, ok := j.nameByTin[tin]; ok </span><span class=\"cov8\" title=\"1\">{\n                return name\n        }</span>\n        <span class=\"cov0\" title=\"0\">return tinName(tin)</span>\n}\n\n// TokenSet returns a named set of Tin values.\n// Built-in sets: \"IGNORE\" (space, line, comment), \"VAL\" (text, number, string, value),\n// \"KEY\" (text, number, string, value).\n// Custom sets can be added via SetTokenSet.\n// Returns nil if the set name is not recognized.\nfunc (j *Jsonic) TokenSet(name string) []Tin <span class=\"cov8\" title=\"1\">{\n        // Check custom sets first.\n        if j.customTokenSets != nil </span><span class=\"cov8\" title=\"1\">{\n                if tins, ok := j.customTokenSets[name]; ok </span><span class=\"cov8\" title=\"1\">{\n                        result := make([]Tin, len(tins))\n                        copy(result, tins)\n                        return result\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">switch name </span>{\n        case \"IGNORE\":<span class=\"cov8\" title=\"1\">\n                ignoreSet := j.parser.Config.IgnoreSet\n                tins := make([]Tin, 0, len(ignoreSet))\n                for tin := range ignoreSet </span><span class=\"cov8\" title=\"1\">{\n                        tins = append(tins, tin)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return tins</span>\n        case \"VAL\":<span class=\"cov8\" title=\"1\">\n                valSet := j.parser.Config.ValSet\n                result := make([]Tin, len(valSet))\n                copy(result, valSet)\n                return result</span>\n        case \"KEY\":<span class=\"cov8\" title=\"1\">\n                keySet := j.parser.Config.KeySet\n                result := make([]Tin, len(keySet))\n                copy(result, keySet)\n                return result</span>\n        default:<span class=\"cov8\" title=\"1\">\n                return nil</span>\n        }\n}\n\n// SetTokenSet registers a custom named token set.\n// Matches TS options.tokenSet.\n// Also updates the per-instance config sets (IgnoreSet, ValSet, KeySet)\n// to keep them in sync, matching TS cfg.tokenSetTins behavior.\nfunc (j *Jsonic) SetTokenSet(name string, tins []Tin) <span class=\"cov8\" title=\"1\">{\n        if j.customTokenSets == nil </span><span class=\"cov8\" title=\"1\">{\n                j.customTokenSets = make(map[string][]Tin)\n        }</span>\n        <span class=\"cov8\" title=\"1\">j.customTokenSets[name] = tins\n\n        // Keep per-instance config sets in sync (matching TS cfg.tokenSetTins).\n        switch name </span>{\n        case \"IGNORE\":<span class=\"cov8\" title=\"1\">\n                ignoreSet := make(map[Tin]bool, len(tins))\n                for _, tin := range tins </span><span class=\"cov8\" title=\"1\">{\n                        ignoreSet[tin] = true\n                }</span>\n                <span class=\"cov8\" title=\"1\">j.parser.Config.IgnoreSet = ignoreSet</span>\n        case \"VAL\":<span class=\"cov8\" title=\"1\">\n                copied := make([]Tin, len(tins))\n                copy(copied, tins)\n                j.parser.Config.ValSet = copied</span>\n        case \"KEY\":<span class=\"cov8\" title=\"1\">\n                copied := make([]Tin, len(tins))\n                copy(copied, tins)\n                j.parser.Config.KeySet = copied</span>\n        }\n}\n\n// Sub subscribes to lex and/or rule events.\n// LexSub fires after each non-ignored token is lexed.\n// RuleSub fires after each rule processing step.\n// Returns the Jsonic instance for chaining.\nfunc (j *Jsonic) Sub(lexSub LexSub, ruleSub RuleSub) *Jsonic <span class=\"cov8\" title=\"1\">{\n        if lexSub != nil </span><span class=\"cov8\" title=\"1\">{\n                j.lexSubs = append(j.lexSubs, lexSub)\n        }</span>\n        <span class=\"cov8\" title=\"1\">if ruleSub != nil </span><span class=\"cov8\" title=\"1\">{\n                j.ruleSubs = append(j.ruleSubs, ruleSub)\n        }</span>\n        <span class=\"cov8\" title=\"1\">return j</span>\n}\n\n// Derive creates a new Jsonic instance inheriting this instance's config,\n// rules, plugins, and custom tokens. Changes to the child do not affect the parent.\n// This matches TypeScript's jsonic.make(options, parent).\nfunc (j *Jsonic) Derive(opts ...Options) *Jsonic <span class=\"cov8\" title=\"1\">{\n        // Start with parent's options, merge with new ones.\n        child := Make(opts...)\n\n        // Copy parent's custom fixed tokens.\n        for k, v := range j.parser.Config.FixedTokens </span><span class=\"cov8\" title=\"1\">{\n                child.parser.Config.FixedTokens[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">child.parser.Config.SortFixedTokens()\n\n        // Copy parent's custom token names.\n        for k, v := range j.tinByName </span><span class=\"cov8\" title=\"1\">{\n                child.tinByName[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">for k, v := range j.nameByTin </span><span class=\"cov8\" title=\"1\">{\n                child.nameByTin[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">if child.nextTin &lt; j.nextTin </span><span class=\"cov8\" title=\"1\">{\n                child.nextTin = j.nextTin\n        }</span>\n\n        // Copy TinNames into child config.\n        <span class=\"cov8\" title=\"1\">if j.parser.Config.TinNames != nil </span><span class=\"cov8\" title=\"1\">{\n                if child.parser.Config.TinNames == nil </span><span class=\"cov8\" title=\"1\">{\n                        child.parser.Config.TinNames = make(map[Tin]string)\n                }</span>\n                <span class=\"cov8\" title=\"1\">for k, v := range j.parser.Config.TinNames </span><span class=\"cov8\" title=\"1\">{\n                        child.parser.Config.TinNames[k] = v\n                }</span>\n        }\n\n        // Copy parent's custom matchers.\n        <span class=\"cov8\" title=\"1\">for _, m := range j.parser.Config.CustomMatchers </span><span class=\"cov0\" title=\"0\">{\n                child.parser.Config.CustomMatchers = append(child.parser.Config.CustomMatchers, m)\n        }</span>\n\n        // Copy parent's ender chars.\n        <span class=\"cov8\" title=\"1\">if j.parser.Config.EnderChars != nil </span><span class=\"cov0\" title=\"0\">{\n                if child.parser.Config.EnderChars == nil </span><span class=\"cov0\" title=\"0\">{\n                        child.parser.Config.EnderChars = make(map[rune]bool)\n                }</span>\n                <span class=\"cov0\" title=\"0\">for k, v := range j.parser.Config.EnderChars </span><span class=\"cov0\" title=\"0\">{\n                        child.parser.Config.EnderChars[k] = v\n                }</span>\n        }\n\n        // Copy parent's escape map.\n        <span class=\"cov8\" title=\"1\">if j.parser.Config.EscapeMap != nil </span><span class=\"cov0\" title=\"0\">{\n                if child.parser.Config.EscapeMap == nil </span><span class=\"cov0\" title=\"0\">{\n                        child.parser.Config.EscapeMap = make(map[string]string)\n                }</span>\n                <span class=\"cov0\" title=\"0\">for k, v := range j.parser.Config.EscapeMap </span><span class=\"cov0\" title=\"0\">{\n                        child.parser.Config.EscapeMap[k] = v\n                }</span>\n        }\n\n        // Copy custom token sets.\n        <span class=\"cov8\" title=\"1\">if j.customTokenSets != nil </span><span class=\"cov8\" title=\"1\">{\n                if child.customTokenSets == nil </span><span class=\"cov8\" title=\"1\">{\n                        child.customTokenSets = make(map[string][]Tin)\n                }</span>\n                <span class=\"cov8\" title=\"1\">for name, tins := range j.customTokenSets </span><span class=\"cov8\" title=\"1\">{\n                        copied := make([]Tin, len(tins))\n                        copy(copied, tins)\n                        child.customTokenSets[name] = copied\n                }</span>\n        }\n\n        // Copy per-instance token sets (matching TS cfg.tokenSetTins inheritance).\n        <span class=\"cov8\" title=\"1\">if j.parser.Config.IgnoreSet != nil </span><span class=\"cov8\" title=\"1\">{\n                child.parser.Config.IgnoreSet = make(map[Tin]bool, len(j.parser.Config.IgnoreSet))\n                for k, v := range j.parser.Config.IgnoreSet </span><span class=\"cov8\" title=\"1\">{\n                        child.parser.Config.IgnoreSet[k] = v\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if j.parser.Config.ValSet != nil </span><span class=\"cov8\" title=\"1\">{\n                child.parser.Config.ValSet = make([]Tin, len(j.parser.Config.ValSet))\n                copy(child.parser.Config.ValSet, j.parser.Config.ValSet)\n        }</span>\n        <span class=\"cov8\" title=\"1\">if j.parser.Config.KeySet != nil </span><span class=\"cov8\" title=\"1\">{\n                child.parser.Config.KeySet = make([]Tin, len(j.parser.Config.KeySet))\n                copy(child.parser.Config.KeySet, j.parser.Config.KeySet)\n        }</span>\n\n        // Re-apply parent's plugins on the child.\n        <span class=\"cov8\" title=\"1\">for _, pe := range j.plugins </span><span class=\"cov8\" title=\"1\">{\n                child.plugins = append(child.plugins, pe)\n                pe.plugin(child, pe.opts)\n        }</span>\n\n        // Copy subscriptions.\n        <span class=\"cov8\" title=\"1\">child.lexSubs = append(child.lexSubs, j.lexSubs...)\n        child.ruleSubs = append(child.ruleSubs, j.ruleSubs...)\n\n        // Copy decorations (TS: parent properties inherited by child).\n        if j.decorations != nil </span><span class=\"cov8\" title=\"1\">{\n                if child.decorations == nil </span><span class=\"cov8\" title=\"1\">{\n                        child.decorations = make(map[string]any)\n                }</span>\n                <span class=\"cov8\" title=\"1\">for k, v := range j.decorations </span><span class=\"cov8\" title=\"1\">{\n                        child.decorations[k] = v\n                }</span>\n        }\n\n        // Copy plugin options namespace.\n        <span class=\"cov8\" title=\"1\">if j.pluginOpts != nil </span><span class=\"cov8\" title=\"1\">{\n                if child.pluginOpts == nil </span><span class=\"cov8\" title=\"1\">{\n                        child.pluginOpts = make(map[string]map[string]any)\n                }</span>\n                <span class=\"cov8\" title=\"1\">for name, opts := range j.pluginOpts </span><span class=\"cov8\" title=\"1\">{\n                        copied := make(map[string]any, len(opts))\n                        for k, v := range opts </span><span class=\"cov8\" title=\"1\">{\n                                copied[k] = v\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">child.pluginOpts[name] = copied</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">return child</span>\n}\n\n// SetOptions deep-merges new options into this instance and rebuilds the\n// config. Existing grammar rules (including plugin modifications) are\n// preserved — matching the TypeScript clone/inherit pattern where\n// options() does not rebuild the grammar.\n// When called from within a plugin (during re-apply), skips plugin\n// re-application to avoid infinite recursion.\n// Returns the instance for chaining.\nfunc (j *Jsonic) SetOptions(opts Options) *Jsonic <span class=\"cov8\" title=\"1\">{\n        merged := Deep(*j.options, opts).(Options)\n        j.options = &amp;merged\n\n        // Rebuild config from merged options.\n        cfg := buildConfig(j.options)\n\n        // Preserve per-instance state.\n        cfg.FixedTokens = j.parser.Config.FixedTokens\n        cfg.FixedSorted = j.parser.Config.FixedSorted\n        cfg.TinNames = j.parser.Config.TinNames\n        cfg.CustomMatchers = j.parser.Config.CustomMatchers\n\n        // Update config in-place to preserve pointer identity.\n        // Grammar closures capture the original *LexConfig pointer, so updating\n        // the object they point to (rather than replacing it) ensures they see\n        // the new config values. This matches TS behavior where configure()\n        // mutates the existing config and parser.clone() inherits the rules.\n        *j.parser.Config = *cfg\n\n        // Do NOT rebuild grammar — preserve existing RSM with user rule\n        // modifications. This matches TS where options() calls parser.clone()\n        // which inherits existing rules rather than rebuilding from scratch.\n\n        // Re-apply plugins (with re-entrancy guard to match TS behavior where\n        // options() setter does not re-trigger plugin application).\n        if !j.inSetOptions </span><span class=\"cov8\" title=\"1\">{\n                j.inSetOptions = true\n                for _, pe := range j.plugins </span><span class=\"cov8\" title=\"1\">{\n                        pe.plugin(j, pe.opts)\n                }</span>\n                <span class=\"cov8\" title=\"1\">j.inSetOptions = false</span>\n        }\n\n        // Apply lex.match specs: create matchers from MakeLexMatcher factories.\n        <span class=\"cov8\" title=\"1\">if opts.Lex != nil &amp;&amp; opts.Lex.Match != nil </span><span class=\"cov0\" title=\"0\">{\n                for name, spec := range opts.Lex.Match </span><span class=\"cov0\" title=\"0\">{\n                        matcher := spec.Make(j.parser.Config, j.options)\n                        j.AddMatcher(name, spec.Order, matcher)\n                }</span>\n        }\n\n        // Apply error messages.\n        <span class=\"cov8\" title=\"1\">if j.options.Error != nil </span><span class=\"cov8\" title=\"1\">{\n                for k, v := range j.options.Error </span><span class=\"cov8\" title=\"1\">{\n                        j.parser.ErrorMessages[k] = v\n                }</span>\n        }\n\n        // Apply hints.\n        <span class=\"cov8\" title=\"1\">if j.options.Hint != nil </span><span class=\"cov8\" title=\"1\">{\n                if j.hints == nil </span><span class=\"cov8\" title=\"1\">{\n                        j.hints = make(map[string]string)\n                }</span>\n                <span class=\"cov8\" title=\"1\">if j.parser.Hints == nil </span><span class=\"cov8\" title=\"1\">{\n                        j.parser.Hints = make(map[string]string)\n                }</span>\n                <span class=\"cov8\" title=\"1\">for k, v := range j.options.Hint </span><span class=\"cov8\" title=\"1\">{\n                        j.hints[k] = v\n                        j.parser.Hints[k] = v\n                }</span>\n        }\n\n        // Apply errmsg options.\n        <span class=\"cov8\" title=\"1\">if j.options.ErrMsg != nil &amp;&amp; j.options.ErrMsg.Name != \"\" </span><span class=\"cov8\" title=\"1\">{\n                j.parser.ErrTag = j.options.ErrMsg.Name\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">return j</span>\n}\n\n// Exclude removes grammar alternates tagged with any of the given group names.\n// Group names are comma-separated in AltSpec.G fields.\n// Use Exclude(\"json\") to strip all jsonic extensions and get strict JSON parsing.\n// Returns the Jsonic instance for chaining.\nfunc (j *Jsonic) Exclude(groups ...string) *Jsonic <span class=\"cov8\" title=\"1\">{\n        excludeSet := make(map[string]bool)\n        for _, g := range groups </span><span class=\"cov8\" title=\"1\">{\n                for _, part := range strings.Split(g, \",\") </span><span class=\"cov8\" title=\"1\">{\n                        part = strings.TrimSpace(part)\n                        if part != \"\" </span><span class=\"cov8\" title=\"1\">{\n                                excludeSet[part] = true\n                        }</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">for _, rs := range j.parser.RSM </span><span class=\"cov8\" title=\"1\">{\n                rs.Open = filterAlts(rs.Open, excludeSet)\n                rs.Close = filterAlts(rs.Close, excludeSet)\n        }</span>\n        <span class=\"cov8\" title=\"1\">return j</span>\n}\n\n// filterAlts removes alternates whose G tags overlap with the exclude set.\nfunc filterAlts(alts []*AltSpec, excludeSet map[string]bool) []*AltSpec <span class=\"cov8\" title=\"1\">{\n        result := make([]*AltSpec, 0, len(alts))\n        for _, alt := range alts </span><span class=\"cov8\" title=\"1\">{\n                if alt.G == \"\" </span><span class=\"cov8\" title=\"1\">{\n                        result = append(result, alt)\n                        continue</span>\n                }\n                <span class=\"cov8\" title=\"1\">excluded := false\n                for _, tag := range strings.Split(alt.G, \",\") </span><span class=\"cov8\" title=\"1\">{\n                        tag = strings.TrimSpace(tag)\n                        if excludeSet[tag] </span><span class=\"cov8\" title=\"1\">{\n                                excluded = true\n                                break</span>\n                        }\n                }\n                <span class=\"cov8\" title=\"1\">if !excluded </span><span class=\"cov8\" title=\"1\">{\n                        result = append(result, alt)\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return result</span>\n}\n\n// ParseMeta parses a jsonic string with metadata passed through to the parse context.\n// The meta map is accessible in rule actions/conditions via ctx.Meta.\nfunc (j *Jsonic) ParseMeta(src string, meta map[string]any) (any, error) <span class=\"cov8\" title=\"1\">{\n        return j.parseInternal(src, meta)\n}</span>\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file8\" style=\"display: none\">package jsonic\n\nimport \"strings\"\n\n// RuleState represents whether a rule is in open or close state.\ntype RuleState = string\n\nconst (\n        OPEN  RuleState = \"o\"\n        CLOSE RuleState = \"c\"\n)\n\n// Undefined is a sentinel value distinguishing \"no value\" from nil (null).\n// In TypeScript, undefined !== null. In Go, we use this sentinel.\ntype undefinedType struct{}\n\nvar Undefined any = &amp;undefinedType{}\n\n// IsUndefined checks if a value is the Undefined sentinel.\nfunc IsUndefined(v any) bool <span class=\"cov8\" title=\"1\">{\n        _, ok := v.(*undefinedType)\n        return ok\n}</span>\n\n// Skip is a sentinel value that acts as undefined in deep merge — the base\n// value is preserved. Represented as \"@SKIP\" in grammar options.\ntype skipType struct{}\n\nvar Skip any = &amp;skipType{}\n\n// IsSkip checks if a value is the Skip sentinel.\nfunc IsSkip(v any) bool <span class=\"cov8\" title=\"1\">{\n        _, ok := v.(*skipType)\n        return ok\n}</span>\n\n// UnwrapUndefined converts Undefined sentinels to nil in the result.\nfunc UnwrapUndefined(v any) any <span class=\"cov0\" title=\"0\">{\n        if IsUndefined(v) </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov0\" title=\"0\">switch val := v.(type) </span>{\n        case map[string]any:<span class=\"cov0\" title=\"0\">\n                for k, vv := range val </span><span class=\"cov0\" title=\"0\">{\n                        val[k] = UnwrapUndefined(vv)\n                }</span>\n                <span class=\"cov0\" title=\"0\">return val</span>\n        case []any:<span class=\"cov0\" title=\"0\">\n                for i, vv := range val </span><span class=\"cov0\" title=\"0\">{\n                        val[i] = UnwrapUndefined(vv)\n                }</span>\n                <span class=\"cov0\" title=\"0\">return val</span>\n        }\n        <span class=\"cov0\" title=\"0\">return v</span>\n}\n\n// AltCond is a condition function for an alternate.\ntype AltCond func(r *Rule, ctx *Context) bool\n\n// AltAction is an action function for an alternate.\ntype AltAction func(r *Rule, ctx *Context)\n\n// AltError is an error function for an alternate.\ntype AltError func(r *Rule, ctx *Context) *Token\n\n// AltModifier can modify an alt match result. Returns the (possibly modified) AltSpec.\ntype AltModifier func(alt *AltSpec, r *Rule, ctx *Context) *AltSpec\n\n// StateAction is a before/after action on a rule state transition.\ntype StateAction func(r *Rule, ctx *Context)\n\n// CondOp represents a comparison operator with a value for declarative conditions.\n// Used in the CD field of AltSpec to define conditions declaratively,\n// matching the TypeScript c: { 'n.pk': { $lte: 0 } } syntax.\ntype CondOp struct {\n        Op  string\n        Val int\n}\n\n// Comparison operator constructors for declarative conditions (AltSpec.CD field).\nfunc CEq(val int) CondOp  <span class=\"cov0\" title=\"0\">{ return CondOp{Op: \"$eq\", Val: val} }</span>\nfunc CNe(val int) CondOp  <span class=\"cov0\" title=\"0\">{ return CondOp{Op: \"$ne\", Val: val} }</span>\nfunc CLt(val int) CondOp  <span class=\"cov0\" title=\"0\">{ return CondOp{Op: \"$lt\", Val: val} }</span>\nfunc CLte(val int) CondOp <span class=\"cov8\" title=\"1\">{ return CondOp{Op: \"$lte\", Val: val} }</span>\nfunc CGt(val int) CondOp  <span class=\"cov8\" title=\"1\">{ return CondOp{Op: \"$gt\", Val: val} }</span>\nfunc CGte(val int) CondOp <span class=\"cov0\" title=\"0\">{ return CondOp{Op: \"$gte\", Val: val} }</span>\n\n// AltSpec defines a parse alternate specification.\ntype AltSpec struct {\n        S  [][]Tin                            // Token Tin sequences to match: s[0] for t0, s[1] for t1\n        P  string                             // Push rule name (create child)\n        R  string                             // Replace rule name (create sibling)\n        B  int                                // Move token pointer backward (backtrack)\n        C  AltCond                            // Custom condition (function)\n        CD map[string]any                     // Declarative condition (converted to C by NormAlt)\n        N  map[string]int                     // Counter increments\n        A  AltAction                          // Match action\n        U  map[string]any                     // Custom props added to Rule.u\n        K  map[string]any                     // Custom props added to Rule.k (propagated)\n        G  string                             // Named group tags (comma-separated)\n        H  AltModifier                        // Alt modifier (called after match to potentially modify the alt)\n        E  AltError                           // Error generation\n        PF func(r *Rule, ctx *Context) string // Dynamic push rule name\n        RF func(r *Rule, ctx *Context) string // Dynamic replace rule name\n        BF func(r *Rule, ctx *Context) int    // Dynamic backtrack\n}\n\n// RuleSpec defines the specification for a parsing rule.\ntype RuleSpec struct {\n        Name  string\n        Open  []*AltSpec\n        Close []*AltSpec\n        BO    []StateAction // Before-open actions\n        BC    []StateAction // Before-close actions\n        AO    []StateAction // After-open actions\n        AC    []StateAction // After-close actions\n}\n\n// Clear removes all alternates and state actions from this RuleSpec.\nfunc (rs *RuleSpec) Clear() *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.Open = rs.Open[:0]\n        rs.Close = rs.Close[:0]\n        rs.BO = rs.BO[:0]\n        rs.BC = rs.BC[:0]\n        rs.AO = rs.AO[:0]\n        rs.AC = rs.AC[:0]\n        return rs\n}</span>\n\n// AddOpen appends alternates to the open list (at the end).\nfunc (rs *RuleSpec) AddOpen(alts ...*AltSpec) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.Open = append(rs.Open, alts...)\n        return rs\n}</span>\n\n// AddClose appends alternates to the close list (at the end).\nfunc (rs *RuleSpec) AddClose(alts ...*AltSpec) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.Close = append(rs.Close, alts...)\n        return rs\n}</span>\n\n// PrependOpen inserts alternates at the beginning of the open list.\nfunc (rs *RuleSpec) PrependOpen(alts ...*AltSpec) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.Open = append(alts, rs.Open...)\n        return rs\n}</span>\n\n// PrependClose inserts alternates at the beginning of the close list.\nfunc (rs *RuleSpec) PrependClose(alts ...*AltSpec) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.Close = append(alts, rs.Close...)\n        return rs\n}</span>\n\n// AltModListOpts configures modifications for RuleSpec alternate lists.\n// Matches the TS ListMods parameter to rs.open(alts, mods)/rs.close(alts, mods).\ntype AltModListOpts struct {\n        Delete []int                            // Indices to delete (supports negative).\n        Move   []int                            // Pairs: [from, to, from, to, ...].\n        Custom func(list []*AltSpec) []*AltSpec // Custom modification callback.\n}\n\n// ModifyOpen applies delete/move/custom modifications to the open alternates list.\n// Matches TS `rs.open(alts, mods)` where mods has delete/move/custom.\nfunc (rs *RuleSpec) ModifyOpen(mods *AltModListOpts) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.Open = modifyAltList(rs.Open, mods)\n        return rs\n}</span>\n\n// ModifyClose applies delete/move/custom modifications to the close alternates list.\nfunc (rs *RuleSpec) ModifyClose(mods *AltModListOpts) *RuleSpec <span class=\"cov0\" title=\"0\">{\n        rs.Close = modifyAltList(rs.Close, mods)\n        return rs\n}</span>\n\nfunc modifyAltList(list []*AltSpec, mods *AltModListOpts) []*AltSpec <span class=\"cov8\" title=\"1\">{\n        if mods == nil || list == nil </span><span class=\"cov0\" title=\"0\">{\n                return list\n        }</span>\n        // Convert to []any, apply ModList, convert back.\n        <span class=\"cov8\" title=\"1\">anyList := make([]any, len(list))\n        for i, v := range list </span><span class=\"cov8\" title=\"1\">{\n                anyList[i] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">anyList = ModList(anyList, &amp;ModListOpts{\n                Delete: mods.Delete,\n                Move:   mods.Move,\n        })\n        result := make([]*AltSpec, len(anyList))\n        for i, v := range anyList </span><span class=\"cov8\" title=\"1\">{\n                result[i] = v.(*AltSpec)\n        }</span>\n        <span class=\"cov8\" title=\"1\">if mods.Custom != nil </span><span class=\"cov8\" title=\"1\">{\n                if newList := mods.Custom(result); newList != nil </span><span class=\"cov8\" title=\"1\">{\n                        result = newList\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return result</span>\n}\n\n// AddBO appends a before-open action.\nfunc (rs *RuleSpec) AddBO(action StateAction) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.BO = append(rs.BO, action)\n        return rs\n}</span>\n\n// AddAO appends an after-open action.\nfunc (rs *RuleSpec) AddAO(action StateAction) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.AO = append(rs.AO, action)\n        return rs\n}</span>\n\n// AddBC appends a before-close action.\nfunc (rs *RuleSpec) AddBC(action StateAction) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.BC = append(rs.BC, action)\n        return rs\n}</span>\n\n// AddAC appends an after-close action.\nfunc (rs *RuleSpec) AddAC(action StateAction) *RuleSpec <span class=\"cov8\" title=\"1\">{\n        rs.AC = append(rs.AC, action)\n        return rs\n}</span>\n\n// getRuleProp accesses a rule property by path (e.g. \"d\", \"n.pk\").\n// Returns the integer value and whether it was found.\n// Matches the TypeScript getRuleProp(r, prop, subprop) function.\nfunc getRuleProp(r *Rule, prop string, subprop string) (int, bool) <span class=\"cov8\" title=\"1\">{\n        if r == nil </span><span class=\"cov0\" title=\"0\">{\n                return 0, false\n        }</span>\n        <span class=\"cov8\" title=\"1\">switch prop </span>{\n        case \"d\":<span class=\"cov8\" title=\"1\">\n                return r.D, true</span>\n        case \"n\":<span class=\"cov8\" title=\"1\">\n                if subprop != \"\" </span><span class=\"cov8\" title=\"1\">{\n                        val, ok := r.N[subprop]\n                        return val, ok\n                }</span>\n        }\n        <span class=\"cov0\" title=\"0\">return 0, false</span>\n}\n\n// MakeRuleCond creates an AltCond function from a comparison operator, property path, and value.\n// Matches the TypeScript makeRuleCond(co, prop, subprop, val) function.\n// When the property is not set (missing), the condition returns true.\nfunc MakeRuleCond(op string, prop string, subprop string, val int) AltCond <span class=\"cov8\" title=\"1\">{\n        switch op </span>{\n        case \"$eq\":<span class=\"cov8\" title=\"1\">\n                return func(r *Rule, ctx *Context) bool </span><span class=\"cov8\" title=\"1\">{\n                        rval, ok := getRuleProp(r, prop, subprop)\n                        return !ok || rval == val\n                }</span>\n        case \"$ne\":<span class=\"cov0\" title=\"0\">\n                return func(r *Rule, ctx *Context) bool </span><span class=\"cov0\" title=\"0\">{\n                        rval, ok := getRuleProp(r, prop, subprop)\n                        return !ok || rval != val\n                }</span>\n        case \"$lt\":<span class=\"cov0\" title=\"0\">\n                return func(r *Rule, ctx *Context) bool </span><span class=\"cov0\" title=\"0\">{\n                        rval, ok := getRuleProp(r, prop, subprop)\n                        return !ok || rval &lt; val\n                }</span>\n        case \"$lte\":<span class=\"cov8\" title=\"1\">\n                return func(r *Rule, ctx *Context) bool </span><span class=\"cov8\" title=\"1\">{\n                        rval, ok := getRuleProp(r, prop, subprop)\n                        return !ok || rval &lt;= val\n                }</span>\n        case \"$gt\":<span class=\"cov8\" title=\"1\">\n                return func(r *Rule, ctx *Context) bool </span><span class=\"cov8\" title=\"1\">{\n                        rval, ok := getRuleProp(r, prop, subprop)\n                        return !ok || rval &gt; val\n                }</span>\n        case \"$gte\":<span class=\"cov0\" title=\"0\">\n                return func(r *Rule, ctx *Context) bool </span><span class=\"cov0\" title=\"0\">{\n                        rval, ok := getRuleProp(r, prop, subprop)\n                        return !ok || rval &gt;= val\n                }</span>\n        default:<span class=\"cov0\" title=\"0\">\n                panic(\"MakeRuleCond: unknown comparison operator: \" + op)</span>\n        }\n}\n\n// NormAlt normalizes an AltSpec by converting a declarative CD condition into a C function.\n// Matches the TypeScript normalt() behavior for the c: field.\n// If C is already set, CD is ignored.\nfunc NormAlt(alt *AltSpec) <span class=\"cov8\" title=\"1\">{\n        if alt == nil || alt.CD == nil || alt.C != nil </span><span class=\"cov8\" title=\"1\">{\n                return\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">var conds []AltCond\n        for propdef, pspec := range alt.CD </span><span class=\"cov8\" title=\"1\">{\n                parts := strings.SplitN(propdef, \".\", 2)\n                prop := parts[0]\n                subprop := \"\"\n                if len(parts) == 2 </span><span class=\"cov8\" title=\"1\">{\n                        subprop = parts[1]\n                }</span>\n\n                <span class=\"cov8\" title=\"1\">switch v := pspec.(type) </span>{\n                case int:<span class=\"cov8\" title=\"1\">\n                        conds = append(conds, MakeRuleCond(\"$eq\", prop, subprop, v))</span>\n                case CondOp:<span class=\"cov8\" title=\"1\">\n                        conds = append(conds, MakeRuleCond(v.Op, prop, subprop, v.Val))</span>\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">if len(conds) == 1 </span><span class=\"cov8\" title=\"1\">{\n                alt.C = conds[0]\n        }</span> else<span class=\"cov8\" title=\"1\"> if len(conds) &gt; 1 </span><span class=\"cov8\" title=\"1\">{\n                alt.C = func(r *Rule, ctx *Context) bool </span><span class=\"cov8\" title=\"1\">{\n                        for _, cond := range conds </span><span class=\"cov8\" title=\"1\">{\n                                if !cond(r, ctx) </span><span class=\"cov8\" title=\"1\">{\n                                        return false\n                                }</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">return true</span>\n                }\n        }\n}\n\n// NormAlts normalizes all alternates in a RuleSpec.\nfunc NormAlts(spec *RuleSpec) <span class=\"cov0\" title=\"0\">{\n        for _, alt := range spec.Open </span><span class=\"cov0\" title=\"0\">{\n                NormAlt(alt)\n        }</span>\n        <span class=\"cov0\" title=\"0\">for _, alt := range spec.Close </span><span class=\"cov0\" title=\"0\">{\n                NormAlt(alt)\n        }</span>\n}\n\n// Rule represents a rule instance during parsing.\ntype Rule struct {\n        I      int\n        Name   string\n        Spec   *RuleSpec\n        Node   any\n        State  RuleState\n        D      int\n        Child  *Rule\n        Parent *Rule\n        Prev   *Rule\n        Next   *Rule\n        O0     *Token\n        O1     *Token\n        C0     *Token\n        C1     *Token\n        OS     int\n        CS     int\n        N      map[string]int\n        U      map[string]any\n        K      map[string]any\n        Why    string\n}\n\n// NoRule is a sentinel rule.\n// Node is Undefined (like TS where NORULE.node = undefined).\nvar NoRule *Rule\n\nfunc init() <span class=\"cov8\" title=\"1\">{\n        NoRule = &amp;Rule{Name: \"norule\", I: -1, State: OPEN, Node: Undefined,\n                N: make(map[string]int), U: make(map[string]any), K: make(map[string]any)}\n}</span>\n\n// Eq checks if counter equals limit (nil/missing → true).\nfunc (r *Rule) Eq(counter string, limit int) bool <span class=\"cov0\" title=\"0\">{\n        val, ok := r.N[counter]\n        return !ok || val == limit\n}</span>\n\n// Lt checks if counter &lt; limit (nil/missing → true).\nfunc (r *Rule) Lt(counter string, limit int) bool <span class=\"cov0\" title=\"0\">{\n        val, ok := r.N[counter]\n        return !ok || val &lt; limit\n}</span>\n\n// Gt checks if counter &gt; limit (nil/missing → true).\nfunc (r *Rule) Gt(counter string, limit int) bool <span class=\"cov0\" title=\"0\">{\n        val, ok := r.N[counter]\n        return !ok || val &gt; limit\n}</span>\n\n// Lte checks if counter &lt;= limit (nil/missing → true).\nfunc (r *Rule) Lte(counter string, limit int) bool <span class=\"cov0\" title=\"0\">{\n        val, ok := r.N[counter]\n        return !ok || val &lt;= limit\n}</span>\n\n// Gte checks if counter &gt;= limit (nil/missing → true).\nfunc (r *Rule) Gte(counter string, limit int) bool <span class=\"cov0\" title=\"0\">{\n        val, ok := r.N[counter]\n        return !ok || val &gt;= limit\n}</span>\n\n// MakeRule creates a new Rule from a RuleSpec.\nfunc MakeRule(spec *RuleSpec, ctx *Context, node any) *Rule <span class=\"cov8\" title=\"1\">{\n        r := &amp;Rule{\n                I: ctx.UI, Name: spec.Name, Spec: spec, Node: node,\n                State: OPEN, D: ctx.RSI,\n                Child: NoRule, Parent: NoRule, Prev: NoRule, Next: NoRule,\n                O0: NoToken, O1: NoToken, C0: NoToken, C1: NoToken,\n                N: make(map[string]int), U: make(map[string]any), K: make(map[string]any),\n        }\n        ctx.UI++\n        return r\n}</span>\n\n// Process processes this rule, returning the next rule to process.\nfunc (r *Rule) Process(ctx *Context, lex *Lex) *Rule <span class=\"cov8\" title=\"1\">{\n        isOpen := r.State == OPEN\n        var next *Rule\n        if isOpen </span><span class=\"cov8\" title=\"1\">{\n                next = r\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                next = NoRule\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">def := r.Spec\n        var alts []*AltSpec\n        if isOpen </span><span class=\"cov8\" title=\"1\">{\n                alts = def.Open\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                alts = def.Close\n        }</span>\n\n        // Before actions\n        <span class=\"cov8\" title=\"1\">if isOpen &amp;&amp; len(def.BO) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, action := range def.BO </span><span class=\"cov8\" title=\"1\">{\n                        action(r, ctx)\n                }</span>\n        } else<span class=\"cov8\" title=\"1\"> if !isOpen &amp;&amp; len(def.BC) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, action := range def.BC </span><span class=\"cov8\" title=\"1\">{\n                        action(r, ctx)\n                }</span>\n        }\n\n        // Match alternates\n        <span class=\"cov8\" title=\"1\">alt, _ := ParseAlts(isOpen, alts, lex, r, ctx)\n\n        // No alternate matched: immediate parse error (matching TS parse_alts behavior).\n        // In TS, when alts exist but none match, out.e = ctx.t0 which triggers this.bad().\n        if alt == nil &amp;&amp; len(alts) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                ctx.ParseErr = ctx.T0\n                return next\n        }</span>\n\n        // Alt modifier\n        <span class=\"cov8\" title=\"1\">if alt != nil &amp;&amp; alt.H != nil </span><span class=\"cov0\" title=\"0\">{\n                alt = alt.H(alt, r, ctx)\n        }</span>\n\n        // Error check: if alt.E returns a token, signal a parse error.\n        <span class=\"cov8\" title=\"1\">if alt != nil &amp;&amp; alt.E != nil </span><span class=\"cov8\" title=\"1\">{\n                errTkn := alt.E(r, ctx)\n                if errTkn != nil </span><span class=\"cov8\" title=\"1\">{\n                        ctx.ParseErr = errTkn\n                }</span>\n        }\n\n        // Update counters\n        <span class=\"cov8\" title=\"1\">if alt != nil &amp;&amp; alt.N != nil </span><span class=\"cov8\" title=\"1\">{\n                for cn, cv := range alt.N </span><span class=\"cov8\" title=\"1\">{\n                        if cv == 0 </span><span class=\"cov8\" title=\"1\">{\n                                r.N[cn] = 0\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                if _, ok := r.N[cn]; !ok </span><span class=\"cov8\" title=\"1\">{\n                                        r.N[cn] = 0\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">r.N[cn] += cv</span>\n                        }\n                }\n        }\n\n        // Set custom properties\n        <span class=\"cov8\" title=\"1\">if alt != nil &amp;&amp; alt.U != nil </span><span class=\"cov8\" title=\"1\">{\n                for k, v := range alt.U </span><span class=\"cov8\" title=\"1\">{\n                        r.U[k] = v\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">if alt != nil &amp;&amp; alt.K != nil </span><span class=\"cov0\" title=\"0\">{\n                for k, v := range alt.K </span><span class=\"cov0\" title=\"0\">{\n                        r.K[k] = v\n                }</span>\n        }\n\n        // Action callback\n        <span class=\"cov8\" title=\"1\">if alt != nil &amp;&amp; alt.A != nil </span><span class=\"cov8\" title=\"1\">{\n                alt.A(r, ctx)\n        }</span>\n\n        // Push / Replace / Pop\n        <span class=\"cov8\" title=\"1\">if alt != nil </span><span class=\"cov8\" title=\"1\">{\n                // Resolve push rule name (static or dynamic)\n                pushName := alt.P\n                if alt.PF != nil </span><span class=\"cov0\" title=\"0\">{\n                        pushName = alt.PF(r, ctx)\n                }</span>\n                // Resolve replace rule name (static or dynamic)\n                <span class=\"cov8\" title=\"1\">replaceName := alt.R\n                if alt.RF != nil </span><span class=\"cov0\" title=\"0\">{\n                        replaceName = alt.RF(r, ctx)\n                }</span>\n\n                <span class=\"cov8\" title=\"1\">if pushName != \"\" </span><span class=\"cov8\" title=\"1\">{\n                        rulespec, ok := ctx.RSM[pushName]\n                        if ok </span><span class=\"cov8\" title=\"1\">{\n                                if ctx.RSI &lt; len(ctx.RS) </span><span class=\"cov8\" title=\"1\">{\n                                        ctx.RS[ctx.RSI] = r\n                                }</span> else<span class=\"cov0\" title=\"0\"> {\n                                        ctx.RS = append(ctx.RS, r)\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">ctx.RSI++\n                                next = MakeRule(rulespec, ctx, r.Node)\n                                r.Child = next\n                                next.Parent = r\n                                for k, v := range r.N </span><span class=\"cov8\" title=\"1\">{\n                                        next.N[k] = v\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">if len(r.K) &gt; 0 </span><span class=\"cov0\" title=\"0\">{\n                                        for k, v := range r.K </span><span class=\"cov0\" title=\"0\">{\n                                                next.K[k] = v\n                                        }</span>\n                                }\n                        }\n                } else<span class=\"cov8\" title=\"1\"> if replaceName != \"\" </span><span class=\"cov8\" title=\"1\">{\n                        rulespec, ok := ctx.RSM[replaceName]\n                        if ok </span><span class=\"cov8\" title=\"1\">{\n                                next = MakeRule(rulespec, ctx, r.Node)\n                                next.Parent = r.Parent\n                                next.Prev = r\n                                for k, v := range r.N </span><span class=\"cov8\" title=\"1\">{\n                                        next.N[k] = v\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">if len(r.K) &gt; 0 </span><span class=\"cov0\" title=\"0\">{\n                                        for k, v := range r.K </span><span class=\"cov0\" title=\"0\">{\n                                                next.K[k] = v\n                                        }</span>\n                                }\n                        }\n                } else<span class=\"cov8\" title=\"1\"> if !isOpen </span><span class=\"cov8\" title=\"1\">{\n                        // Pop\n                        if ctx.RSI &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                ctx.RSI--\n                                next = ctx.RS[ctx.RSI]\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                next = NoRule\n                        }</span>\n                }\n        } else<span class=\"cov8\" title=\"1\"> if !isOpen </span><span class=\"cov8\" title=\"1\">{\n                // No alt matched AND we're closing → pop\n                if ctx.RSI &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                        ctx.RSI--\n                        next = ctx.RS[ctx.RSI]\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        next = NoRule\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">r.Next = next\n\n        // After actions\n        if isOpen &amp;&amp; len(def.AO) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, action := range def.AO </span><span class=\"cov8\" title=\"1\">{\n                        action(r, ctx)\n                }</span>\n        } else<span class=\"cov8\" title=\"1\"> if !isOpen &amp;&amp; len(def.AC) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                for _, action := range def.AC </span><span class=\"cov8\" title=\"1\">{\n                        action(r, ctx)\n                }</span>\n        }\n\n        // State transition\n        <span class=\"cov8\" title=\"1\">if r.State == OPEN </span><span class=\"cov8\" title=\"1\">{\n                r.State = CLOSE\n        }</span>\n\n        // Token consumption with backtrack (only when an alt matched)\n        <span class=\"cov8\" title=\"1\">if alt != nil </span><span class=\"cov8\" title=\"1\">{\n                backtrack := alt.B\n                if alt.BF != nil </span><span class=\"cov0\" title=\"0\">{\n                        backtrack = alt.BF(r, ctx)\n                }</span>\n                <span class=\"cov8\" title=\"1\">var consumed int\n                if isOpen </span><span class=\"cov8\" title=\"1\">{\n                        consumed = r.OS - backtrack\n                }</span> else<span class=\"cov8\" title=\"1\"> {\n                        consumed = r.CS - backtrack\n                }</span>\n\n                <span class=\"cov8\" title=\"1\">if consumed == 1 </span><span class=\"cov8\" title=\"1\">{\n                        ctx.V2 = ctx.V1\n                        ctx.V1 = ctx.T0\n                        ctx.T0 = ctx.T1\n                        ctx.T1 = NoToken\n                        ctx.TC++\n                }</span> else<span class=\"cov8\" title=\"1\"> if consumed == 2 </span><span class=\"cov8\" title=\"1\">{\n                        ctx.V2 = ctx.T1\n                        ctx.V1 = ctx.T0\n                        ctx.T0 = NoToken\n                        ctx.T1 = NoToken\n                        ctx.TC += 2\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return next</span>\n}\n\n// ParseAlts attempts to match one of the alternates.\nfunc ParseAlts(isOpen bool, alts []*AltSpec, lex *Lex, rule *Rule, ctx *Context) (*AltSpec, bool) <span class=\"cov8\" title=\"1\">{\n        if len(alts) == 0 </span><span class=\"cov8\" title=\"1\">{\n                return nil, false\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">for _, alt := range alts </span><span class=\"cov8\" title=\"1\">{\n                has0, has1 := false, false\n                cond := true\n\n                if len(alt.S) &gt; 0 &amp;&amp; len(alt.S[0]) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                        if ctx.T0.IsNoToken() </span><span class=\"cov8\" title=\"1\">{\n                                ctx.T0 = lex.Next(rule)\n                                // Fire lex subscribers.\n                                if len(ctx.LexSubs) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                        for _, sub := range ctx.LexSubs </span><span class=\"cov8\" title=\"1\">{\n                                                sub(ctx.T0, rule, ctx)\n                                        }</span>\n                                }\n                        }\n                        <span class=\"cov8\" title=\"1\">has0 = true\n                        cond = tinMatch(ctx.T0.Tin, alt.S[0])\n\n                        if cond &amp;&amp; len(alt.S) &gt; 1 &amp;&amp; len(alt.S[1]) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                if ctx.T1.IsNoToken() </span><span class=\"cov8\" title=\"1\">{\n                                        ctx.T1 = lex.Next(rule)\n                                        if len(ctx.LexSubs) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                                for _, sub := range ctx.LexSubs </span><span class=\"cov8\" title=\"1\">{\n                                                        sub(ctx.T1, rule, ctx)\n                                                }</span>\n                                        }\n                                }\n                                <span class=\"cov8\" title=\"1\">has1 = true\n                                cond = tinMatch(ctx.T1.Tin, alt.S[1])</span>\n                        }\n                }\n\n                <span class=\"cov8\" title=\"1\">if isOpen </span><span class=\"cov8\" title=\"1\">{\n                        if has0 </span><span class=\"cov8\" title=\"1\">{\n                                rule.O0 = ctx.T0\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                rule.O0 = NoToken\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">if has1 </span><span class=\"cov8\" title=\"1\">{\n                                rule.O1 = ctx.T1\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                rule.O1 = NoToken\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">rule.OS = boolToInt(has0) + boolToInt(has1)</span>\n                } else<span class=\"cov8\" title=\"1\"> {\n                        if has0 </span><span class=\"cov8\" title=\"1\">{\n                                rule.C0 = ctx.T0\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                rule.C0 = NoToken\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">if has1 </span><span class=\"cov8\" title=\"1\">{\n                                rule.C1 = ctx.T1\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                rule.C1 = NoToken\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">rule.CS = boolToInt(has0) + boolToInt(has1)</span>\n                }\n\n                <span class=\"cov8\" title=\"1\">if cond &amp;&amp; alt.C != nil </span><span class=\"cov8\" title=\"1\">{\n                        cond = alt.C(rule, ctx)\n                }</span>\n\n                <span class=\"cov8\" title=\"1\">if cond </span><span class=\"cov8\" title=\"1\">{\n                        return alt, true\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return nil, false</span>\n}\n\nfunc tinMatch(tin Tin, tins []Tin) bool <span class=\"cov8\" title=\"1\">{\n        for _, t := range tins </span><span class=\"cov8\" title=\"1\">{\n                if tin == t </span><span class=\"cov8\" title=\"1\">{\n                        return true\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return false</span>\n}\n\nfunc boolToInt(b bool) int <span class=\"cov8\" title=\"1\">{\n        if b </span><span class=\"cov8\" title=\"1\">{\n                return 1\n        }</span>\n        <span class=\"cov8\" title=\"1\">return 0</span>\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file9\" style=\"display: none\">package jsonic\n\n// Tin is a token identification number.\ntype Tin = int\n\n// Standard token Tins - assigned in order matching the TypeScript implementation.\nconst (\n        TinBD  Tin = 1  // #BD - BAD\n        TinZZ  Tin = 2  // #ZZ - END\n        TinUK  Tin = 3  // #UK - UNKNOWN\n        TinAA  Tin = 4  // #AA - ANY\n        TinSP  Tin = 5  // #SP - SPACE\n        TinLN  Tin = 6  // #LN - LINE\n        TinCM  Tin = 7  // #CM - COMMENT\n        TinNR  Tin = 8  // #NR - NUMBER\n        TinST  Tin = 9  // #ST - STRING\n        TinTX  Tin = 10 // #TX - TEXT\n        TinVL  Tin = 11 // #VL - VALUE (true, false, null)\n        TinOB  Tin = 12 // #OB - Open Brace {\n        TinCB  Tin = 13 // #CB - Close Brace }\n        TinOS  Tin = 14 // #OS - Open Square [\n        TinCS  Tin = 15 // #CS - Close Square ]\n        TinCL  Tin = 16 // #CL - Colon :\n        TinCA  Tin = 17 // #CA - Comma ,\n        TinMAX Tin = 18 // Next available Tin\n)\n\n// Token set constants\nvar (\n        // IGNORE tokens: space, line, comment\n        TinSetIGNORE = map[Tin]bool{TinSP: true, TinLN: true, TinCM: true}\n        // VAL tokens: text, number, string, value\n        TinSetVAL = []Tin{TinTX, TinNR, TinST, TinVL}\n        // KEY tokens: text, number, string, value (same as VAL)\n        TinSetKEY = []Tin{TinTX, TinNR, TinST, TinVL}\n)\n\n// Point tracks position in source text.\ntype Point struct {\n        Len int // Source length\n        SI  int // String index (0-based)\n        RI  int // Row index (1-based)\n        CI  int // Column index (1-based)\n}\n\n// Token represents a lexical token.\ntype Token struct {\n        Name string         // Token name (#OB, #ST, etc.)\n        Tin  Tin            // Token identification number\n        Val  any            // Resolved value\n        Src  string         // Source text\n        SI   int            // Start position\n        RI   int            // Row\n        CI   int            // Column\n        Err  string         // Error code\n        Why  string         // Tracing/reason\n        Use  map[string]any // Custom plugin metadata (TS: token.use)\n}\n\n// Bad converts this token to an error token with the given error code.\n// Matches TS token.bad(err, details).\nfunc (t *Token) Bad(err string, details ...map[string]any) *Token <span class=\"cov8\" title=\"1\">{\n        t.Err = err\n        if len(details) &gt; 0 &amp;&amp; details[0] != nil </span><span class=\"cov8\" title=\"1\">{\n                if t.Use == nil </span><span class=\"cov8\" title=\"1\">{\n                        t.Use = make(map[string]any)\n                }</span>\n                <span class=\"cov8\" title=\"1\">for k, v := range details[0] </span><span class=\"cov8\" title=\"1\">{\n                        t.Use[k] = v\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return t</span>\n}\n\n// IsNoToken returns true if this is a sentinel/empty token.\nfunc (t *Token) IsNoToken() bool <span class=\"cov8\" title=\"1\">{\n        return t.Tin == -1\n}</span>\n\n// ResolveVal returns the token's value (or src for numbers used as keys).\nfunc (t *Token) ResolveVal() any <span class=\"cov8\" title=\"1\">{\n        return t.Val\n}</span>\n\n// MakeToken creates a new Token.\nfunc MakeToken(name string, tin Tin, val any, src string, pnt Point) *Token <span class=\"cov8\" title=\"1\">{\n        return &amp;Token{\n                Name: name,\n                Tin:  tin,\n                Val:  val,\n                Src:  src,\n                SI:   pnt.SI,\n                RI:   pnt.RI,\n                CI:   pnt.CI,\n        }\n}</span>\n\n// NoToken is a sentinel token indicating \"no token\".\nvar NoToken = &amp;Token{Name: \"\", Tin: -1, SI: -1, RI: -1, CI: -1}\n\n// Fixed token source map: character -&gt; Tin\nvar FixedTokens = map[string]Tin{\n        \"{\": TinOB,\n        \"}\": TinCB,\n        \"[\": TinOS,\n        \"]\": TinCS,\n        \":\": TinCL,\n        \",\": TinCA,\n}\n</pre>\n\t\t\n\t\t<pre class=\"file\" id=\"file10\" style=\"display: none\">package jsonic\n\nimport (\n        \"encoding/json\"\n        \"fmt\"\n        \"reflect\"\n        \"regexp\"\n        \"strconv\"\n        \"strings\"\n)\n\n// Deep performs a recursive deep merge of multiple values.\n// Works on map[string]any, []any, and Go structs (via reflection),\n// matching the TypeScript deep() utility.\n// Zero/nil values in the overlay do not overwrite base values.\nfunc Deep(base any, rest ...any) any <span class=\"cov8\" title=\"1\">{\n        for _, over := range rest </span><span class=\"cov8\" title=\"1\">{\n                base = deepMerge(base, over)\n        }</span>\n        <span class=\"cov8\" title=\"1\">return base</span>\n}\n\nfunc deepMerge(base, over any) any <span class=\"cov8\" title=\"1\">{\n        // Match TS: undefined and Skip preserve base.\n        if IsUndefined(over) || IsSkip(over) </span><span class=\"cov8\" title=\"1\">{\n                return base\n        }</span>\n\n        // Extract maps from MapRef if present.\n        <span class=\"cov8\" title=\"1\">baseMap, baseIsMap := base.(map[string]any)\n        baseMR, baseIsMR := base.(MapRef)\n        if baseIsMR </span><span class=\"cov8\" title=\"1\">{\n                baseMap = baseMR.Val\n                baseIsMap = true\n        }</span>\n        <span class=\"cov8\" title=\"1\">overMap, overIsMap := over.(map[string]any)\n        overMR, overIsMR := over.(MapRef)\n        if overIsMR </span><span class=\"cov8\" title=\"1\">{\n                overMap = overMR.Val\n                overIsMap = true\n        }</span>\n\n        // Extract arrays from ListRef if present.\n        <span class=\"cov8\" title=\"1\">baseArr, baseIsArr := base.([]any)\n        baseLR, baseIsLR := base.(ListRef)\n        if baseIsLR </span><span class=\"cov8\" title=\"1\">{\n                baseArr = baseLR.Val\n                baseIsArr = true\n        }</span>\n        <span class=\"cov8\" title=\"1\">overArr, overIsArr := over.([]any)\n        overLR, overIsLR := over.(ListRef)\n        if overIsLR </span><span class=\"cov8\" title=\"1\">{\n                overArr = overLR.Val\n                overIsArr = true\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">if baseIsMap &amp;&amp; overIsMap </span><span class=\"cov8\" title=\"1\">{\n                // Both maps: recursively merge\n                result := make(map[string]any)\n                for k, v := range baseMap </span><span class=\"cov8\" title=\"1\">{\n                        result[k] = v\n                }</span>\n                <span class=\"cov8\" title=\"1\">for k, v := range overMap </span><span class=\"cov8\" title=\"1\">{\n                        if existing, ok := result[k]; ok </span><span class=\"cov8\" title=\"1\">{\n                                result[k] = deepMerge(existing, v)\n                        }</span> else<span class=\"cov8\" title=\"1\"> {\n                                result[k] = deepClone(v)\n                        }</span>\n                }\n                // Preserve MapRef wrapper if the over value was a MapRef.\n                <span class=\"cov8\" title=\"1\">if overIsMR </span><span class=\"cov8\" title=\"1\">{\n                        meta := mergeMeta(baseMR.Meta, overMR.Meta)\n                        return MapRef{Val: result, Implicit: overMR.Implicit, Meta: meta}\n                }</span>\n                <span class=\"cov8\" title=\"1\">return result</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if baseIsArr &amp;&amp; overIsArr </span><span class=\"cov8\" title=\"1\">{\n                // Both arrays: recursively merge elements at same index\n                maxLen := len(baseArr)\n                if len(overArr) &gt; maxLen </span><span class=\"cov8\" title=\"1\">{\n                        maxLen = len(overArr)\n                }</span>\n                <span class=\"cov8\" title=\"1\">result := make([]any, maxLen)\n                for i := 0; i &lt; maxLen; i++ </span><span class=\"cov8\" title=\"1\">{\n                        if i &lt; len(baseArr) &amp;&amp; i &lt; len(overArr) </span><span class=\"cov8\" title=\"1\">{\n                                result[i] = deepMerge(deepClone(baseArr[i]), overArr[i])\n                        }</span> else<span class=\"cov8\" title=\"1\"> if i &lt; len(overArr) </span><span class=\"cov8\" title=\"1\">{\n                                result[i] = deepClone(overArr[i])\n                        }</span> else<span class=\"cov8\" title=\"1\"> if i &lt; len(baseArr) </span><span class=\"cov8\" title=\"1\">{\n                                result[i] = deepClone(baseArr[i])\n                        }</span>\n                }\n                // Preserve ListRef wrapper if the over value was a ListRef.\n                <span class=\"cov8\" title=\"1\">if overIsLR </span><span class=\"cov8\" title=\"1\">{\n                        // Merge Child fields if both are ListRef.\n                        var child any\n                        if baseIsLR &amp;&amp; baseLR.Child != nil &amp;&amp; overLR.Child != nil </span><span class=\"cov0\" title=\"0\">{\n                                child = deepMerge(baseLR.Child, overLR.Child)\n                        }</span> else<span class=\"cov8\" title=\"1\"> if overLR.Child != nil </span><span class=\"cov0\" title=\"0\">{\n                                child = deepClone(overLR.Child)\n                        }</span> else<span class=\"cov8\" title=\"1\"> if baseIsLR </span><span class=\"cov8\" title=\"1\">{\n                                child = deepClone(baseLR.Child)\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">meta := mergeMeta(baseLR.Meta, overLR.Meta)\n                        return ListRef{Val: result, Implicit: overLR.Implicit, Child: child, Meta: meta}</span>\n                }\n                <span class=\"cov8\" title=\"1\">return result</span>\n        }\n\n        // Struct handling via reflection — matches TS deep() on plain objects.\n        <span class=\"cov8\" title=\"1\">if merged, ok := deepMergeStruct(base, over); ok </span><span class=\"cov8\" title=\"1\">{\n                return merged\n        }</span>\n\n        // Type mismatch or non-container: over wins\n        // Match TS: undefined preserves base, null replaces.\n        <span class=\"cov8\" title=\"1\">if IsUndefined(over) </span><span class=\"cov0\" title=\"0\">{\n                return base\n        }</span>\n        <span class=\"cov8\" title=\"1\">if over == nil </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">return deepClone(over)</span>\n}\n\n// deepMergeStruct merges two struct values field-by-field via reflection.\n// Zero-value fields in over do not overwrite base, matching TS deep() where\n// undefined properties are skipped. Returns (merged, true) if both values\n// are structs of the same type, or (nil, false) if not applicable.\nfunc deepMergeStruct(base, over any) (any, bool) <span class=\"cov8\" title=\"1\">{\n        if base == nil || over == nil </span><span class=\"cov8\" title=\"1\">{\n                return nil, false\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">bv := reflect.ValueOf(base)\n        ov := reflect.ValueOf(over)\n\n        // Unwrap pointers.\n        bIsPtr := bv.Kind() == reflect.Ptr\n        oIsPtr := ov.Kind() == reflect.Ptr\n\n        if bIsPtr &amp;&amp; bv.IsNil() </span><span class=\"cov0\" title=\"0\">{\n                if oIsPtr || ov.Kind() == reflect.Struct </span><span class=\"cov0\" title=\"0\">{\n                        return over, true\n                }</span>\n                <span class=\"cov0\" title=\"0\">return nil, false</span>\n        }\n        <span class=\"cov8\" title=\"1\">if oIsPtr &amp;&amp; ov.IsNil() </span><span class=\"cov0\" title=\"0\">{\n                if bIsPtr || bv.Kind() == reflect.Struct </span><span class=\"cov0\" title=\"0\">{\n                        return base, true\n                }</span>\n                <span class=\"cov0\" title=\"0\">return nil, false</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">bElem := bv\n        oElem := ov\n        if bIsPtr </span><span class=\"cov8\" title=\"1\">{\n                bElem = bv.Elem()\n        }</span>\n        <span class=\"cov8\" title=\"1\">if oIsPtr </span><span class=\"cov8\" title=\"1\">{\n                oElem = ov.Elem()\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">if bElem.Kind() != reflect.Struct || oElem.Kind() != reflect.Struct </span><span class=\"cov8\" title=\"1\">{\n                return nil, false\n        }</span>\n        <span class=\"cov8\" title=\"1\">if bElem.Type() != oElem.Type() </span><span class=\"cov0\" title=\"0\">{\n                return nil, false\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">result := reflect.New(bElem.Type()).Elem()\n        for i := 0; i &lt; bElem.NumField(); i++ </span><span class=\"cov8\" title=\"1\">{\n                bf := bElem.Field(i)\n                of := oElem.Field(i)\n\n                if !bf.CanInterface() </span><span class=\"cov0\" title=\"0\">{\n                        // Unexported field: keep base.\n                        continue</span>\n                }\n\n                <span class=\"cov8\" title=\"1\">if of.IsZero() </span><span class=\"cov8\" title=\"1\">{\n                        result.Field(i).Set(bf)\n                        continue</span>\n                }\n                <span class=\"cov8\" title=\"1\">if bf.IsZero() </span><span class=\"cov8\" title=\"1\">{\n                        result.Field(i).Set(of)\n                        continue</span>\n                }\n\n                // Both non-zero: merge based on kind.\n                <span class=\"cov8\" title=\"1\">switch of.Kind() </span>{\n                case reflect.Ptr:<span class=\"cov8\" title=\"1\">\n                        if of.Elem().Kind() == reflect.Struct </span><span class=\"cov8\" title=\"1\">{\n                                // Pointer to struct: recurse.\n                                merged, ok := deepMergeStruct(bf.Interface(), of.Interface())\n                                if ok </span><span class=\"cov8\" title=\"1\">{\n                                        result.Field(i).Set(reflect.ValueOf(merged))\n                                }</span> else<span class=\"cov0\" title=\"0\"> {\n                                        result.Field(i).Set(of)\n                                }</span>\n                        } else<span class=\"cov8\" title=\"1\"> {\n                                // Pointer to primitive (*bool, *int): over wins.\n                                result.Field(i).Set(of)\n                        }</span>\n                case reflect.Map:<span class=\"cov8\" title=\"1\">\n                        // Merge map entries: base first, then over overwrites.\n                        merged := reflect.MakeMap(bf.Type())\n                        for _, k := range bf.MapKeys() </span><span class=\"cov8\" title=\"1\">{\n                                merged.SetMapIndex(k, bf.MapIndex(k))\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">for _, k := range of.MapKeys() </span><span class=\"cov8\" title=\"1\">{\n                                merged.SetMapIndex(k, of.MapIndex(k))\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">result.Field(i).Set(merged)</span>\n                default:<span class=\"cov8\" title=\"1\">\n                        // String, slice, func, etc.: over wins.\n                        result.Field(i).Set(of)</span>\n                }\n        }\n\n        // Return with same pointer wrapping as inputs.\n        <span class=\"cov8\" title=\"1\">if bIsPtr || oIsPtr </span><span class=\"cov8\" title=\"1\">{\n                ptr := reflect.New(result.Type())\n                ptr.Elem().Set(result)\n                return ptr.Interface(), true\n        }</span>\n        <span class=\"cov8\" title=\"1\">return result.Interface(), true</span>\n}\n\n// cloneMeta creates a shallow copy of a Meta map.\nfunc cloneMeta(meta map[string]any) map[string]any <span class=\"cov8\" title=\"1\">{\n        if meta == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">result := make(map[string]any, len(meta))\n        for k, v := range meta </span><span class=\"cov0\" title=\"0\">{\n                result[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">return result</span>\n}\n\n// mergeMeta merges two Meta maps. The over map's values take precedence.\nfunc mergeMeta(base, over map[string]any) map[string]any <span class=\"cov8\" title=\"1\">{\n        if base == nil &amp;&amp; over == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">result := make(map[string]any)\n        for k, v := range base </span><span class=\"cov0\" title=\"0\">{\n                result[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">for k, v := range over </span><span class=\"cov0\" title=\"0\">{\n                result[k] = v\n        }</span>\n        <span class=\"cov8\" title=\"1\">return result</span>\n}\n\n// deepClone creates a deep copy of a value.\nfunc deepClone(val any) any <span class=\"cov8\" title=\"1\">{\n        if val == nil </span><span class=\"cov8\" title=\"1\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">switch v := val.(type) </span>{\n        case map[string]any:<span class=\"cov8\" title=\"1\">\n                result := make(map[string]any)\n                for k, val := range v </span><span class=\"cov8\" title=\"1\">{\n                        result[k] = deepClone(val)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return result</span>\n        case []any:<span class=\"cov8\" title=\"1\">\n                result := make([]any, len(v))\n                for i, val := range v </span><span class=\"cov8\" title=\"1\">{\n                        result[i] = deepClone(val)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return result</span>\n        case ListRef:<span class=\"cov8\" title=\"1\">\n                result := make([]any, len(v.Val))\n                for i, val := range v.Val </span><span class=\"cov0\" title=\"0\">{\n                        result[i] = deepClone(val)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return ListRef{Val: result, Implicit: v.Implicit, Child: deepClone(v.Child), Meta: cloneMeta(v.Meta)}</span>\n        case MapRef:<span class=\"cov8\" title=\"1\">\n                result := make(map[string]any)\n                for k, val := range v.Val </span><span class=\"cov8\" title=\"1\">{\n                        result[k] = deepClone(val)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return MapRef{Val: result, Implicit: v.Implicit, Meta: cloneMeta(v.Meta)}</span>\n        default:<span class=\"cov8\" title=\"1\">\n                return v</span>\n        }\n}\n\n// Snip returns the first maxlen characters of s, replacing \\r, \\n, \\t with '.'.\n// Matches the TypeScript snip() utility used for debug/display output.\nfunc Snip(s string, maxlen int) string <span class=\"cov8\" title=\"1\">{\n        if maxlen &lt;= 0 </span><span class=\"cov8\" title=\"1\">{\n                return \"\"\n        }</span>\n        <span class=\"cov8\" title=\"1\">if len(s) &gt; maxlen </span><span class=\"cov8\" title=\"1\">{\n                s = s[:maxlen]\n        }</span>\n        <span class=\"cov8\" title=\"1\">return strings.NewReplacer(\"\\r\", \".\", \"\\n\", \".\", \"\\t\", \".\").Replace(s)</span>\n}\n\n// Str converts a value to a truncated string representation.\n// If maxlen is &lt;= 0, returns empty string.\n// If the string representation exceeds maxlen, it is truncated with \"...\" appended.\n// Matches the TypeScript str() + snip() pipeline: converts to string, truncates,\n// then replaces \\r\\n\\t with '.'.\nfunc Str(val any, maxlen int) string <span class=\"cov8\" title=\"1\">{\n        if maxlen &lt;= 0 </span><span class=\"cov0\" title=\"0\">{\n                return \"\"\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">var s string\n        switch v := val.(type) </span>{\n        case string:<span class=\"cov8\" title=\"1\">\n                s = v</span>\n        case float64:<span class=\"cov8\" title=\"1\">\n                if v == float64(int64(v)) </span><span class=\"cov8\" title=\"1\">{\n                        s = strconv.FormatInt(int64(v), 10)\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        s = strconv.FormatFloat(v, 'f', -1, 64)\n                }</span>\n        case bool:<span class=\"cov8\" title=\"1\">\n                if v </span><span class=\"cov8\" title=\"1\">{\n                        s = \"true\"\n                }</span> else<span class=\"cov0\" title=\"0\"> {\n                        s = \"false\"\n                }</span>\n        case nil:<span class=\"cov8\" title=\"1\">\n                // Match TS: JSON.stringify(null) === \"null\"\n                s = \"null\"</span>\n        default:<span class=\"cov8\" title=\"1\">\n                b, err := json.Marshal(val)\n                if err != nil </span><span class=\"cov0\" title=\"0\">{\n                        s = fmt.Sprintf(\"%v\", val)\n                }</span> else<span class=\"cov8\" title=\"1\"> {\n                        s = string(b)\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if len(s) &gt; maxlen </span><span class=\"cov8\" title=\"1\">{\n                if maxlen &gt;= 4 </span><span class=\"cov8\" title=\"1\">{\n                        s = s[:maxlen-3] + \"...\"\n                }</span> else<span class=\"cov8\" title=\"1\"> {\n                        s = \"...\"[:maxlen]\n                }</span>\n        }\n\n        // Match TS: str() calls snip() which replaces \\r\\n\\t with '.'\n        <span class=\"cov8\" title=\"1\">return Snip(s, maxlen)</span>\n}\n\n// StrInject replaces template placeholders like {key} or {key.subkey} in a\n// template string with values from a map or array.\n// Returns the template unchanged if vals is not a map or array.\nfunc StrInject(template string, vals any) string <span class=\"cov8\" title=\"1\">{\n        if template == \"\" </span><span class=\"cov0\" title=\"0\">{\n                return \"\"\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">valsMap, isMap := vals.(map[string]any)\n        valsArr, isArr := vals.([]any)\n\n        if !isMap &amp;&amp; !isArr </span><span class=\"cov8\" title=\"1\">{\n                return template\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">var result strings.Builder\n        runes := []rune(template)\n        i := 0\n        for i &lt; len(runes) </span><span class=\"cov8\" title=\"1\">{\n                if runes[i] == '{' </span><span class=\"cov8\" title=\"1\">{\n                        // Find closing brace\n                        j := i + 1\n                        for j &lt; len(runes) &amp;&amp; runes[j] != '}' </span><span class=\"cov8\" title=\"1\">{\n                                j++\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">if j &lt; len(runes) </span><span class=\"cov8\" title=\"1\">{\n                                path := string(runes[i+1 : j])\n                                // Resolve path\n                                resolved, ok := resolvePath(path, valsMap, valsArr, isMap)\n                                if ok </span><span class=\"cov8\" title=\"1\">{\n                                        result.WriteString(formatInjectValue(resolved))\n                                }</span> else<span class=\"cov8\" title=\"1\"> {\n                                        // Keep original placeholder\n                                        result.WriteString(string(runes[i : j+1]))\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">i = j + 1</span>\n                        } else<span class=\"cov0\" title=\"0\"> {\n                                result.WriteRune(runes[i])\n                                i++\n                        }</span>\n                } else<span class=\"cov8\" title=\"1\"> {\n                        result.WriteRune(runes[i])\n                        i++\n                }</span>\n        }\n        <span class=\"cov8\" title=\"1\">return result.String()</span>\n}\n\n// resolvePath resolves a dotted path like \"a.b.0\" against a map or array.\nfunc resolvePath(path string, valsMap map[string]any, valsArr []any, isMap bool) (any, bool) <span class=\"cov8\" title=\"1\">{\n        parts := strings.Split(path, \".\")\n        var current any\n        if isMap </span><span class=\"cov8\" title=\"1\">{\n                current = any(valsMap)\n        }</span> else<span class=\"cov8\" title=\"1\"> {\n                current = any(valsArr)\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">for _, part := range parts </span><span class=\"cov8\" title=\"1\">{\n                switch c := current.(type) </span>{\n                case map[string]any:<span class=\"cov8\" title=\"1\">\n                        v, ok := c[part]\n                        if !ok </span><span class=\"cov8\" title=\"1\">{\n                                return nil, false\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">current = v</span>\n                case []any:<span class=\"cov8\" title=\"1\">\n                        idx, err := strconv.Atoi(part)\n                        if err != nil || idx &lt; 0 || idx &gt;= len(c) </span><span class=\"cov8\" title=\"1\">{\n                                return nil, false\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">current = c[idx]</span>\n                default:<span class=\"cov0\" title=\"0\">\n                        return nil, false</span>\n                }\n        }\n        <span class=\"cov8\" title=\"1\">return current, true</span>\n}\n\n// formatInjectValue formats a value for injection into a template.\nfunc formatInjectValue(val any) string <span class=\"cov8\" title=\"1\">{\n        switch v := val.(type) </span>{\n        case string:<span class=\"cov8\" title=\"1\">\n                return v</span>\n        case float64:<span class=\"cov8\" title=\"1\">\n                if v == float64(int64(v)) </span><span class=\"cov8\" title=\"1\">{\n                        return strconv.FormatInt(int64(v), 10)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return strconv.FormatFloat(v, 'f', -1, 64)</span>\n        case bool:<span class=\"cov0\" title=\"0\">\n                if v </span><span class=\"cov0\" title=\"0\">{\n                        return \"true\"\n                }</span>\n                <span class=\"cov0\" title=\"0\">return \"false\"</span>\n        case nil:<span class=\"cov0\" title=\"0\">\n                return \"null\"</span>\n        case map[string]any:<span class=\"cov8\" title=\"1\">\n                return formatCompactValue(v)</span>\n        case []any:<span class=\"cov8\" title=\"1\">\n                return formatCompactValue(v)</span>\n        default:<span class=\"cov0\" title=\"0\">\n                return fmt.Sprintf(\"%v\", v)</span>\n        }\n}\n\n// formatCompactValue formats maps/arrays in a compact non-JSON format\n// similar to jsonic's output: {key:value} instead of {\"key\":\"value\"}.\nfunc formatCompactValue(val any) string <span class=\"cov8\" title=\"1\">{\n        switch v := val.(type) </span>{\n        case map[string]any:<span class=\"cov8\" title=\"1\">\n                var sb strings.Builder\n                sb.WriteRune('{')\n                first := true\n                for k, v := range v </span><span class=\"cov8\" title=\"1\">{\n                        if !first </span><span class=\"cov0\" title=\"0\">{\n                                sb.WriteRune(',')\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">first = false\n                        sb.WriteString(k)\n                        sb.WriteRune(':')\n                        sb.WriteString(formatCompactValue(v))</span>\n                }\n                <span class=\"cov8\" title=\"1\">sb.WriteRune('}')\n                return sb.String()</span>\n        case []any:<span class=\"cov8\" title=\"1\">\n                var sb strings.Builder\n                sb.WriteRune('[')\n                for i, v := range v </span><span class=\"cov8\" title=\"1\">{\n                        if i &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                sb.WriteRune(',')\n                        }</span>\n                        <span class=\"cov8\" title=\"1\">sb.WriteString(formatCompactValue(v))</span>\n                }\n                <span class=\"cov8\" title=\"1\">sb.WriteRune(']')\n                return sb.String()</span>\n        case string:<span class=\"cov0\" title=\"0\">\n                return v</span>\n        case float64:<span class=\"cov8\" title=\"1\">\n                if v == float64(int64(v)) </span><span class=\"cov8\" title=\"1\">{\n                        return strconv.FormatInt(int64(v), 10)\n                }</span>\n                <span class=\"cov0\" title=\"0\">return strconv.FormatFloat(v, 'f', -1, 64)</span>\n        case bool:<span class=\"cov0\" title=\"0\">\n                if v </span><span class=\"cov0\" title=\"0\">{\n                        return \"true\"\n                }</span>\n                <span class=\"cov0\" title=\"0\">return \"false\"</span>\n        case nil:<span class=\"cov0\" title=\"0\">\n                return \"null\"</span>\n        default:<span class=\"cov0\" title=\"0\">\n                return fmt.Sprintf(\"%v\", v)</span>\n        }\n}\n\n// ModListOpts configures list modifications for ModList.\n// Matches the TypeScript ListMods type.\ntype ModListOpts struct {\n        Delete []int                   // Indices to delete (supports negative indices).\n        Move   []int                   // Pairs: [from, to, from, to, ...].\n        Custom func(list []any) []any  // Custom modification callback, applied last.\n}\n\n// ModList modifies a list by applying delete, move, and custom operations.\n// Matches the TypeScript modlist() utility.\nfunc ModList(list []any, opts *ModListOpts) []any <span class=\"cov8\" title=\"1\">{\n        if opts == nil || list == nil </span><span class=\"cov8\" title=\"1\">{\n                return list\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">if len(list) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                type sentinel struct{}\n                deleteMarker := sentinel{}\n\n                // Phase 1: Mark elements for deletion (before move so indexes still make sense).\n                if len(opts.Delete) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                        for _, idx := range opts.Delete </span><span class=\"cov8\" title=\"1\">{\n                                n := len(list)\n                                if idx &lt; 0 </span><span class=\"cov8\" title=\"1\">{\n                                        if -idx &lt;= n </span><span class=\"cov8\" title=\"1\">{\n                                                dI := (n + idx) % n\n                                                list[dI] = deleteMarker\n                                        }</span>\n                                } else<span class=\"cov8\" title=\"1\"> {\n                                        if idx &lt; n </span><span class=\"cov8\" title=\"1\">{\n                                                list[idx] = deleteMarker\n                                        }</span>\n                                }\n                        }\n                }\n\n                // Phase 2: Move operations (on array with markers still present).\n                <span class=\"cov8\" title=\"1\">if len(opts.Move) &gt;= 2 </span><span class=\"cov8\" title=\"1\">{\n                        for i := 0; i+1 &lt; len(opts.Move); i += 2 </span><span class=\"cov8\" title=\"1\">{\n                                n := len(list)\n                                if n == 0 </span><span class=\"cov0\" title=\"0\">{\n                                        break</span>\n                                }\n                                <span class=\"cov8\" title=\"1\">fromI := ((opts.Move[i] % n) + n) % n\n                                toI := ((opts.Move[i+1] % n) + n) % n\n                                entry := list[fromI]\n                                list = append(list[:fromI], list[fromI+1:]...)\n                                newList := make([]any, len(list)+1)\n                                copy(newList, list[:toI])\n                                newList[toI] = entry\n                                copy(newList[toI+1:], list[toI:])\n                                list = newList</span>\n                        }\n                }\n\n                // Phase 3: Filter out deleted entries.\n                <span class=\"cov8\" title=\"1\">if len(opts.Delete) &gt; 0 </span><span class=\"cov8\" title=\"1\">{\n                        filtered := make([]any, 0, len(list))\n                        for _, v := range list </span><span class=\"cov8\" title=\"1\">{\n                                if _, ok := v.(sentinel); !ok </span><span class=\"cov8\" title=\"1\">{\n                                        filtered = append(filtered, v)\n                                }</span>\n                        }\n                        <span class=\"cov8\" title=\"1\">list = filtered</span>\n                }\n        }\n\n        // Phase 4: Custom modification (matches TS mods.custom).\n        <span class=\"cov8\" title=\"1\">if opts.Custom != nil </span><span class=\"cov8\" title=\"1\">{\n                if newList := opts.Custom(list); newList != nil </span><span class=\"cov8\" title=\"1\">{\n                        list = newList\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return list</span>\n}\n\n// deepEqual compares two values for deep equality.\n// Used internally for testing.\nfunc deepEqual(a, b any) bool <span class=\"cov0\" title=\"0\">{\n        return reflect.DeepEqual(a, b)\n}</span>\n\n// IsFuncRef checks if a string is a function reference (starts with \"@\").\nfunc IsFuncRef(s string) bool <span class=\"cov8\" title=\"1\">{\n        return len(s) &gt; 0 &amp;&amp; s[0] == '@'\n}</span>\n\n// RequireRef looks up a FuncRef in the ref map and returns an error if not found.\nfunc RequireRef(ref map[FuncRef]any, name string, kind string) (any, error) <span class=\"cov8\" title=\"1\">{\n        if ref == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil, fmt.Errorf(\"Grammar: unknown %s function reference: %s (no ref map)\", kind, name)\n        }</span>\n        <span class=\"cov8\" title=\"1\">fn, ok := ref[name]\n        if !ok </span><span class=\"cov8\" title=\"1\">{\n                return nil, fmt.Errorf(\"Grammar: unknown %s function reference: %s\", kind, name)\n        }</span>\n        <span class=\"cov8\" title=\"1\">return fn, nil</span>\n}\n\n// LookupRef looks up a FuncRef in the ref map. Returns nil if not found.\nfunc LookupRef(ref map[FuncRef]any, name string) any <span class=\"cov8\" title=\"1\">{\n        if ref == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">return ref[name]</span>\n}\n\n// MapToOptions converts a map[string]any (with resolved FuncRefs) to an Options struct.\n// Only handles fields that are commonly set via grammar options.\nfunc MapToOptions(m map[string]any) Options <span class=\"cov8\" title=\"1\">{\n        var opts Options\n\n        if v, ok := m[\"tag\"].(string); ok </span><span class=\"cov8\" title=\"1\">{\n                opts.Tag = v\n        }</span>\n\n        <span class=\"cov8\" title=\"1\">if vm, ok := m[\"value\"].(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                opts.Value = &amp;ValueOptions{}\n                if lex, ok := vm[\"lex\"].(bool); ok </span><span class=\"cov8\" title=\"1\">{\n                        opts.Value.Lex = &amp;lex\n                }</span>\n                <span class=\"cov8\" title=\"1\">if defm, ok := vm[\"def\"].(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                        opts.Value.Def = make(map[string]*ValueDef, len(defm))\n                        for k, v := range defm </span><span class=\"cov8\" title=\"1\">{\n                                switch vv := v.(type) </span>{\n                                case map[string]any:<span class=\"cov8\" title=\"1\">\n                                        vd := &amp;ValueDef{}\n                                        if val, ok := vv[\"val\"]; ok </span><span class=\"cov8\" title=\"1\">{\n                                                vd.Val = val\n                                        }</span>\n                                        <span class=\"cov8\" title=\"1\">opts.Value.Def[k] = vd</span>\n                                case nil, bool:<span class=\"cov0\" title=\"0\"></span>\n                                        // nil or false removes the value def\n                                }\n                        }\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">if nm, ok := m[\"number\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                opts.Number = &amp;NumberOptions{}\n                if hex, ok := nm[\"hex\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Number.Hex = &amp;hex\n                }</span>\n                <span class=\"cov0\" title=\"0\">if oct, ok := nm[\"oct\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Number.Oct = &amp;oct\n                }</span>\n                <span class=\"cov0\" title=\"0\">if bin, ok := nm[\"bin\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Number.Bin = &amp;bin\n                }</span>\n                <span class=\"cov0\" title=\"0\">if sep, ok := nm[\"sep\"].(string); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Number.Sep = sep\n                }</span>\n                <span class=\"cov0\" title=\"0\">if fn, ok := nm[\"exclude\"].(func(string) bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Number.Exclude = fn\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if mm, ok := m[\"map\"].(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                opts.Map = &amp;MapOptions{}\n                if ext, ok := mm[\"extend\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Map.Extend = &amp;ext\n                }</span>\n                <span class=\"cov8\" title=\"1\">if fn, ok := mm[\"merge\"].(func(any, any, *Rule, *Context) any); ok </span><span class=\"cov8\" title=\"1\">{\n                        opts.Map.Merge = fn\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if sm, ok := m[\"string\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                opts.String = &amp;StringOptions{}\n                if esc, ok := sm[\"escape\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.String.Escape = make(map[string]string, len(esc))\n                        for k, v := range esc </span><span class=\"cov0\" title=\"0\">{\n                                if s, ok := v.(string); ok </span><span class=\"cov0\" title=\"0\">{\n                                        opts.String.Escape[k] = s\n                                }</span>\n                        }\n                }\n                <span class=\"cov0\" title=\"0\">if rep, ok := sm[\"replace\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.String.Replace = make(map[rune]string, len(rep))\n                        for k, v := range rep </span><span class=\"cov0\" title=\"0\">{\n                                if len(k) &gt; 0 </span><span class=\"cov0\" title=\"0\">{\n                                        if s, ok := v.(string); ok </span><span class=\"cov0\" title=\"0\">{\n                                                opts.String.Replace[rune(k[0])] = s\n                                        }</span>\n                                }\n                        }\n                }\n        }\n\n        <span class=\"cov8\" title=\"1\">if cm, ok := m[\"comment\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                opts.Comment = &amp;CommentOptions{}\n                if lex, ok := cm[\"lex\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Comment.Lex = &amp;lex\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if rm, ok := m[\"rule\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                opts.Rule = &amp;RuleOptions{}\n                if start, ok := rm[\"start\"].(string); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Rule.Start = start\n                }</span>\n                <span class=\"cov0\" title=\"0\">if finish, ok := rm[\"finish\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Rule.Finish = &amp;finish\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">if safe, ok := m[\"safe\"].(map[string]any); ok </span><span class=\"cov0\" title=\"0\">{\n                opts.Safe = &amp;SafeOptions{}\n                if key, ok := safe[\"key\"].(bool); ok </span><span class=\"cov0\" title=\"0\">{\n                        opts.Safe.Key = &amp;key\n                }</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return opts</span>\n}\n\n// ResolveFuncRefs recursively resolves FuncRef strings in a map[string]any:\n//   - \"@@prefix\" → literal \"@prefix\"\n//   - \"@SKIP\" → Skip sentinel\n//   - \"@/pattern/flags\" → *regexp.Regexp\n//   - \"@name\" → function from ref map\nfunc ResolveFuncRefs(obj any, ref map[FuncRef]any) any <span class=\"cov8\" title=\"1\">{\n        if obj == nil </span><span class=\"cov0\" title=\"0\">{\n                return nil\n        }</span>\n        <span class=\"cov8\" title=\"1\">if s, ok := obj.(string); ok &amp;&amp; len(s) &gt; 0 &amp;&amp; s[0] == '@' </span><span class=\"cov8\" title=\"1\">{\n                // Escape: @@ → literal @-prefixed string\n                if len(s) &gt; 1 &amp;&amp; s[1] == '@' </span><span class=\"cov8\" title=\"1\">{\n                        return s[1:]\n                }</span>\n                // Sentinel: @SKIP → Skip\n                <span class=\"cov8\" title=\"1\">if s == \"@SKIP\" </span><span class=\"cov8\" title=\"1\">{\n                        return Skip\n                }</span>\n                // Regex: @/pattern/flags → *regexp.Regexp\n                <span class=\"cov8\" title=\"1\">if len(s) &gt; 2 &amp;&amp; s[1] == '/' </span><span class=\"cov8\" title=\"1\">{\n                        if idx := strings.LastIndex(s, \"/\"); idx &gt; 1 </span><span class=\"cov8\" title=\"1\">{\n                                pattern := s[2:idx]\n                                flags := s[idx+1:]\n                                if flags != \"\" </span><span class=\"cov8\" title=\"1\">{\n                                        pattern = \"(?\" + flags + \")\" + pattern\n                                }</span>\n                                <span class=\"cov8\" title=\"1\">re, err := regexp.Compile(pattern)\n                                if err == nil </span><span class=\"cov8\" title=\"1\">{\n                                        return re\n                                }</span>\n                        }\n                }\n                // FuncRef: @name → function from ref\n                <span class=\"cov8\" title=\"1\">if ref != nil </span><span class=\"cov8\" title=\"1\">{\n                        if fn, ok := ref[s]; ok </span><span class=\"cov8\" title=\"1\">{\n                                return fn\n                        }</span>\n                }\n                <span class=\"cov0\" title=\"0\">return obj</span>\n        }\n\n        // Recurse into maps\n        <span class=\"cov8\" title=\"1\">if m, ok := obj.(map[string]any); ok </span><span class=\"cov8\" title=\"1\">{\n                out := make(map[string]any, len(m))\n                for k, v := range m </span><span class=\"cov8\" title=\"1\">{\n                        out[k] = ResolveFuncRefs(v, ref)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return out</span>\n        }\n\n        // Recurse into slices\n        <span class=\"cov8\" title=\"1\">if arr, ok := obj.([]any); ok </span><span class=\"cov8\" title=\"1\">{\n                out := make([]any, len(arr))\n                for i, v := range arr </span><span class=\"cov8\" title=\"1\">{\n                        out[i] = ResolveFuncRefs(v, ref)\n                }</span>\n                <span class=\"cov8\" title=\"1\">return out</span>\n        }\n\n        <span class=\"cov8\" title=\"1\">return obj</span>\n}\n</pre>\n\t\t\n\t\t</div>\n\t</body>\n\t<script>\n\t(function() {\n\t\tvar files = document.getElementById('files');\n\t\tvar visible;\n\t\tfiles.addEventListener('change', onChange, false);\n\t\tfunction select(part) {\n\t\t\tif (visible)\n\t\t\t\tvisible.style.display = 'none';\n\t\t\tvisible = document.getElementById(part);\n\t\t\tif (!visible)\n\t\t\t\treturn;\n\t\t\tfiles.value = part;\n\t\t\tvisible.style.display = 'block';\n\t\t\tlocation.hash = part;\n\t\t}\n\t\tfunction onChange() {\n\t\t\tselect(files.value);\n\t\t\twindow.scrollTo(0, 0);\n\t\t}\n\t\tif (location.hash != \"\") {\n\t\t\tselect(location.hash.substr(1));\n\t\t}\n\t\tif (!visible) {\n\t\t\tselect(\"file0\");\n\t\t}\n\t})();\n\t</script>\n</html>\n"
  },
  {
    "path": "go/csv_grammar_test.go",
    "content": "package jsonic\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\n// Minimal CSV grammar built directly on the jsonic parser API.\n// Mirrors test/csv-grammar.test.js — the grammar treats comma-separated\n// values as cells, newline-separated rows as records, and single tokens\n// (text, number, string, keyword) as cell values. Empty cells become \"\";\n// empty rows are dropped.\nfunc makeCSV() *Jsonic {\n\tj := Make()\n\n\t// pushBack keeps the replaced-rule chain in sync so the parent rule\n\t// always sees the latest slice through r.Parent.Child.Node. Go's\n\t// append may reallocate, which means a bare tail-call replacement\n\t// would leave the original row.Node stale — unlike JS arrays.\n\tpushBack := func(r *Rule) {\n\t\tif r.Parent != NoRule && r.Parent != nil &&\n\t\t\tr.Parent.Child != NoRule && r.Parent.Child != nil {\n\t\t\tr.Parent.Child.Node = r.Node\n\t\t}\n\t}\n\n\tappendCell := func(r *Rule, val any) {\n\t\tarr, _ := r.Node.([]any)\n\t\tr.Node = append(arr, val)\n\t\tpushBack(r)\n\t}\n\n\t// csv: outer list of rows. Fresh bo resets Node for each new parse.\n\tj.Rule(\"csv\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.BO = []StateAction{func(r *Rule, ctx *Context) {\n\t\t\tr.Node = []any{}\n\t\t}}\n\t\trs.Open = []*AltSpec{\n\t\t\t{S: [][]Tin{{TinZZ}}},\n\t\t\t{P: \"row\"},\n\t\t}\n\t\trs.Close = []*AltSpec{\n\t\t\t{S: [][]Tin{{TinLN}, {TinZZ}}},\n\t\t\t{S: [][]Tin{{TinLN}}, R: \"csvcont\"},\n\t\t\t{S: [][]Tin{{TinZZ}}},\n\t\t}\n\t\trs.BC = []StateAction{func(r *Rule, ctx *Context) {\n\t\t\tif cells, ok := r.Child.Node.([]any); ok && len(cells) > 0 {\n\t\t\t\touter, _ := r.Node.([]any)\n\t\t\t\tr.Node = append(outer, cells)\n\t\t\t}\n\t\t}}\n\t})\n\n\t// csvcont: tail-call sibling of csv. Inherits the outer-list node so\n\t// the replace chain carries the rows accumulated so far.\n\tj.Rule(\"csvcont\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = []*AltSpec{\n\t\t\t{S: [][]Tin{{TinZZ}}},\n\t\t\t{P: \"row\"},\n\t\t}\n\t\trs.Close = []*AltSpec{\n\t\t\t{S: [][]Tin{{TinLN}, {TinZZ}}},\n\t\t\t{S: [][]Tin{{TinLN}}, R: \"csvcont\"},\n\t\t\t{S: [][]Tin{{TinZZ}}},\n\t\t}\n\t\trs.BC = []StateAction{func(r *Rule, ctx *Context) {\n\t\t\tif cells, ok := r.Child.Node.([]any); ok && len(cells) > 0 {\n\t\t\t\touter, _ := r.Node.([]any)\n\t\t\t\tr.Node = append(outer, cells)\n\t\t\t}\n\t\t}}\n\t})\n\n\t// row: handles the first cell (initialising the row slice) then hands\n\t// the continuation to rowcont. Row-ending tokens at open produce an\n\t// empty row, which csv.bc drops.\n\tj.Rule(\"row\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = []*AltSpec{\n\t\t\t{S: [][]Tin{TinSetVAL}, A: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = []any{r.O[0].Val}\n\t\t\t}},\n\t\t\t{S: [][]Tin{{TinCA}}, B: 1, A: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = []any{\"\"}\n\t\t\t}},\n\t\t\t{S: [][]Tin{{TinLN}}, B: 1, A: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = []any{}\n\t\t\t}},\n\t\t\t{S: [][]Tin{{TinZZ}}, B: 1, A: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = []any{}\n\t\t\t}},\n\t\t}\n\t\trs.Close = []*AltSpec{\n\t\t\t{S: [][]Tin{{TinCA}}, R: \"rowcont\"},\n\t\t\t{S: [][]Tin{{TinLN}}, B: 1},\n\t\t\t{S: [][]Tin{{TinZZ}}, B: 1},\n\t\t}\n\t})\n\n\t// rowcont: continues appending cells into the row slice. pushBack\n\t// keeps row.Node (the node parent rule reads) synced after append.\n\tj.Rule(\"rowcont\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = []*AltSpec{\n\t\t\t{S: [][]Tin{TinSetVAL}, A: func(r *Rule, ctx *Context) {\n\t\t\t\tappendCell(r, r.O[0].Val)\n\t\t\t}},\n\t\t\t{S: [][]Tin{{TinCA}}, B: 1, A: func(r *Rule, ctx *Context) {\n\t\t\t\tappendCell(r, \"\")\n\t\t\t}},\n\t\t\t{S: [][]Tin{{TinLN}}, B: 1, A: func(r *Rule, ctx *Context) {\n\t\t\t\tappendCell(r, \"\")\n\t\t\t}},\n\t\t\t{S: [][]Tin{{TinZZ}}, B: 1, A: func(r *Rule, ctx *Context) {\n\t\t\t\tappendCell(r, \"\")\n\t\t\t}},\n\t\t}\n\t\trs.Close = []*AltSpec{\n\t\t\t{S: [][]Tin{{TinCA}}, R: \"rowcont\"},\n\t\t\t{S: [][]Tin{{TinLN}}, B: 1},\n\t\t\t{S: [][]Tin{{TinZZ}}, B: 1},\n\t\t}\n\t})\n\n\t// Select the custom start rule and drop jsonic-only extensions.\n\t// Keep #SP and #CM in IGNORE but let #LN reach the parser.\n\tj.SetOptions(Options{\n\t\tRule: &RuleOptions{Start: \"csv\", Exclude: \"jsonic,imp\"},\n\t\tLex:  &LexOptions{EmptyResult: []any{}},\n\t\tTokenSet: map[string][]string{\n\t\t\t\"IGNORE\": {\"#SP\", \"#CM\"},\n\t\t},\n\t})\n\n\treturn j\n}\n\nfunc runCSV(t *testing.T, name, src string, want []any) {\n\tt.Helper()\n\tj := makeCSV()\n\tgot, err := j.Parse(src)\n\tif err != nil {\n\t\tt.Fatalf(\"%s: Parse(%q) error: %v\", name, src, err)\n\t}\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"%s: Parse(%q)\\n  got:      %#v\\n  expected: %#v\",\n\t\t\tname, src, got, want)\n\t}\n}\n\nfunc TestCSVEmptyInput(t *testing.T) {\n\trunCSV(t, \"empty-input\", \"\", []any{})\n}\n\nfunc TestCSVSingleRow(t *testing.T) {\n\trunCSV(t, \"single-row\", \"a,b,c\",\n\t\t[]any{[]any{\"a\", \"b\", \"c\"}})\n}\n\nfunc TestCSVMultipleRows(t *testing.T) {\n\trunCSV(t, \"multiple-rows\", \"a,b\\nc,d\",\n\t\t[]any{[]any{\"a\", \"b\"}, []any{\"c\", \"d\"}})\n}\n\nfunc TestCSVTrailingNewline(t *testing.T) {\n\trunCSV(t, \"trailing-newline\", \"a,b,c\\n\",\n\t\t[]any{[]any{\"a\", \"b\", \"c\"}})\n}\n\nfunc TestCSVBlankLinesSkipped(t *testing.T) {\n\trunCSV(t, \"blank-lines\", \"a,b\\n\\nc,d\\n\",\n\t\t[]any{[]any{\"a\", \"b\"}, []any{\"c\", \"d\"}})\n}\n\nfunc TestCSVNumbersParsed(t *testing.T) {\n\trunCSV(t, \"numbers\", \"1,2,3\",\n\t\t[]any{[]any{float64(1), float64(2), float64(3)}})\n}\n\nfunc TestCSVQuotedStrings(t *testing.T) {\n\trunCSV(t, \"quoted\", `\"hello\",\"world\"`,\n\t\t[]any{[]any{\"hello\", \"world\"}})\n}\n\nfunc TestCSVMixedTypes(t *testing.T) {\n\trunCSV(t, \"mixed\", `a,1,\"x\",true`,\n\t\t[]any{[]any{\"a\", float64(1), \"x\", true}})\n}\n\nfunc TestCSVEmptyLeadingField(t *testing.T) {\n\trunCSV(t, \"leading-empty\", \",a,b\",\n\t\t[]any{[]any{\"\", \"a\", \"b\"}})\n}\n\nfunc TestCSVEmptyMiddleField(t *testing.T) {\n\trunCSV(t, \"middle-empty\", \"a,,b\",\n\t\t[]any{[]any{\"a\", \"\", \"b\"}})\n}\n\nfunc TestCSVEmptyTrailingField(t *testing.T) {\n\trunCSV(t, \"trailing-empty\", \"a,b,\",\n\t\t[]any{[]any{\"a\", \"b\", \"\"}})\n}\n\nfunc TestCSVSingleCellRow(t *testing.T) {\n\trunCSV(t, \"single-cell\", \"x\\ny\",\n\t\t[]any{[]any{\"x\"}, []any{\"y\"}})\n}\n\nfunc TestCSVKeywords(t *testing.T) {\n\trunCSV(t, \"keywords\", \"true,false,null\",\n\t\t[]any{[]any{true, false, nil}})\n}\n"
  },
  {
    "path": "go/debug.go",
    "content": "package jsonic\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// Debug is a plugin that provides introspection and tracing capabilities.\n// It matches the TypeScript Debug plugin functionality.\n//\n// Usage:\n//\n//\tj := jsonic.Make()\n//\tj.Use(jsonic.Debug, map[string]any{\"trace\": true})\n//\tfmt.Println(jsonic.Describe(j))\nvar Debug Plugin = func(j *Jsonic, opts map[string]any) error {\n\tif opts != nil {\n\t\tif trace, ok := opts[\"trace\"]; ok {\n\t\t\tif traceBool, ok := trace.(bool); ok && traceBool {\n\t\t\t\taddTrace(j)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// addTrace installs lex and rule subscribers that log each step.\nfunc addTrace(j *Jsonic) {\n\tj.Sub(\n\t\tfunc(tkn *Token, rule *Rule, ctx *Context) {\n\t\t\tfmt.Printf(\"[lex] %s tin=%d src=%q val=%v at %d:%d\\n\",\n\t\t\t\ttkn.Name, tkn.Tin, tkn.Src, tkn.Val, tkn.RI, tkn.CI)\n\t\t},\n\t\tfunc(rule *Rule, ctx *Context) {\n\t\t\tfmt.Printf(\"[rule] %s state=%s node=%v ki=%d\\n\",\n\t\t\t\trule.Name, rule.State, rule.Node, ctx.KI)\n\t\t},\n\t)\n}\n\n// Describe returns a human-readable description of a Jsonic instance's configuration.\n// It lists tokens, fixed tokens, rules, matchers, plugins, and key config settings.\nfunc Describe(j *Jsonic) string {\n\tvar b strings.Builder\n\n\tb.WriteString(\"=== Jsonic Instance ===\\n\")\n\tif j.options != nil && j.options.Tag != \"\" {\n\t\tb.WriteString(fmt.Sprintf(\"Tag: %s\\n\", j.options.Tag))\n\t}\n\n\t// Tokens\n\tb.WriteString(\"\\n--- Tokens ---\\n\")\n\tnames := make([]string, 0, len(j.tinByName))\n\tfor name := range j.tinByName {\n\t\tnames = append(names, name)\n\t}\n\tsort.Strings(names)\n\tfor _, name := range names {\n\t\ttin := j.tinByName[name]\n\t\tb.WriteString(fmt.Sprintf(\"  %s = %d\\n\", name, tin))\n\t}\n\n\t// Fixed tokens\n\tb.WriteString(\"\\n--- Fixed Tokens ---\\n\")\n\tcfg := j.Config()\n\tif cfg.FixedTokens != nil {\n\t\tftKeys := make([]string, 0, len(cfg.FixedTokens))\n\t\tfor k := range cfg.FixedTokens {\n\t\t\tftKeys = append(ftKeys, k)\n\t\t}\n\t\tsort.Strings(ftKeys)\n\t\tfor _, k := range ftKeys {\n\t\t\ttin := cfg.FixedTokens[k]\n\t\t\tname := j.TinName(tin)\n\t\t\tb.WriteString(fmt.Sprintf(\"  %q -> %s (%d)\\n\", k, name, tin))\n\t\t}\n\t}\n\n\t// Rules\n\tb.WriteString(\"\\n--- Rules ---\\n\")\n\truleNames := make([]string, 0, len(j.parser.RSM))\n\tfor name := range j.parser.RSM {\n\t\truleNames = append(ruleNames, name)\n\t}\n\tsort.Strings(ruleNames)\n\tfor _, name := range ruleNames {\n\t\trs := j.parser.RSM[name]\n\t\tb.WriteString(fmt.Sprintf(\"  %s: open=%d close=%d bo=%d ao=%d bc=%d ac=%d\\n\",\n\t\t\tname, len(rs.Open), len(rs.Close), len(rs.BO), len(rs.AO), len(rs.BC), len(rs.AC)))\n\t}\n\n\t// Custom matchers\n\tif len(cfg.CustomMatchers) > 0 {\n\t\tb.WriteString(\"\\n--- Custom Matchers ---\\n\")\n\t\tfor _, m := range cfg.CustomMatchers {\n\t\t\tb.WriteString(fmt.Sprintf(\"  %s (priority=%d)\\n\", m.Name, m.Priority))\n\t\t}\n\t}\n\n\t// Plugins\n\tb.WriteString(fmt.Sprintf(\"\\n--- Plugins: %d ---\\n\", len(j.plugins)))\n\n\t// Subscriptions\n\tb.WriteString(fmt.Sprintf(\"\\n--- Subscriptions ---\\n\"))\n\tb.WriteString(fmt.Sprintf(\"  Lex subscribers: %d\\n\", len(j.lexSubs)))\n\tb.WriteString(fmt.Sprintf(\"  Rule subscribers: %d\\n\", len(j.ruleSubs)))\n\n\t// Config summary\n\tb.WriteString(\"\\n--- Config ---\\n\")\n\tb.WriteString(fmt.Sprintf(\"  FixedLex: %v\\n\", cfg.FixedLex))\n\tb.WriteString(fmt.Sprintf(\"  SpaceLex: %v\\n\", cfg.SpaceLex))\n\tb.WriteString(fmt.Sprintf(\"  LineLex: %v\\n\", cfg.LineLex))\n\tb.WriteString(fmt.Sprintf(\"  TextLex: %v\\n\", cfg.TextLex))\n\tb.WriteString(fmt.Sprintf(\"  NumberLex: %v\\n\", cfg.NumberLex))\n\tb.WriteString(fmt.Sprintf(\"  CommentLex: %v\\n\", cfg.CommentLex))\n\tb.WriteString(fmt.Sprintf(\"  StringLex: %v\\n\", cfg.StringLex))\n\tb.WriteString(fmt.Sprintf(\"  ValueLex: %v\\n\", cfg.ValueLex))\n\tb.WriteString(fmt.Sprintf(\"  MapExtend: %v\\n\", cfg.MapExtend))\n\tb.WriteString(fmt.Sprintf(\"  ListProperty: %v\\n\", cfg.ListProperty))\n\tb.WriteString(fmt.Sprintf(\"  SafeKey: %v\\n\", cfg.SafeKey))\n\tb.WriteString(fmt.Sprintf(\"  FinishRule: %v\\n\", cfg.FinishRule))\n\tb.WriteString(fmt.Sprintf(\"  RuleStart: %s\\n\", cfg.RuleStart))\n\n\treturn b.String()\n}\n"
  },
  {
    "path": "go/directive_grammar_test.go",
    "content": "package jsonic\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// Minimal directive-style plugin defined inline for the test. A directive\n// binds a fixed OPEN token to a named rule that reads the following val\n// and replaces it with the result of an action callback. Mirrors the\n// essential shape of the JS @jsonic/directive plugin — token + rule +\n// transform — without the close-token, rule-filtering, or error-plumbing\n// surface area. Kept in-test so the core repo carries no plugin dependency.\nfunc defineDirective(j *Jsonic, name, open string, action func(any) any) {\n\tOPEN := j.Token(\"#OD_\"+name, open)\n\n\tj.Rule(name, func(rs *RuleSpec, _ *Parser) {\n\t\trs.BO = []StateAction{func(r *Rule, ctx *Context) {\n\t\t\tr.Node = nil\n\t\t}}\n\t\trs.Open = []*AltSpec{\n\t\t\t{P: \"val\"},\n\t\t}\n\t\trs.BC = []StateAction{func(r *Rule, ctx *Context) {\n\t\t\tvar childNode any\n\t\t\tif r.Child != nil && r.Child != NoRule {\n\t\t\t\tchildNode = r.Child.Node\n\t\t\t}\n\t\t\tr.Node = action(childNode)\n\t\t}}\n\t})\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\tS: [][]Tin{{OPEN}},\n\t\t\tP: name,\n\t\t}}, rs.Open...)\n\t})\n}\n\nfunc makeDirectiveJ() *Jsonic {\n\tj := Make()\n\tdefineDirective(j, \"upper\", \"@up\", func(val any) any {\n\t\treturn strings.ToUpper(fmt.Sprintf(\"%v\", val))\n\t})\n\tdefineDirective(j, \"wrap\", \"@wrap\", func(val any) any {\n\t\treturn map[string]any{\"wrapped\": val}\n\t})\n\treturn j\n}\n\nfunc runDirective(t *testing.T, name, src string, want any) {\n\tt.Helper()\n\tj := makeDirectiveJ()\n\tgot, err := j.Parse(src)\n\tif err != nil {\n\t\tt.Fatalf(\"%s: Parse(%q) error: %v\", name, src, err)\n\t}\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"%s: Parse(%q)\\n  got:      %#v\\n  expected: %#v\",\n\t\t\tname, src, got, want)\n\t}\n}\n\nfunc TestDirectiveUpperString(t *testing.T) {\n\trunDirective(t, \"upper-string\", `@up \"hello\"`, \"HELLO\")\n}\n\nfunc TestDirectiveUpperBare(t *testing.T) {\n\trunDirective(t, \"upper-bare\", `@up hello`, \"HELLO\")\n}\n\nfunc TestDirectiveUpperNumber(t *testing.T) {\n\trunDirective(t, \"upper-number\", `@up 42`, \"42\")\n}\n\nfunc TestDirectiveWrapNumber(t *testing.T) {\n\trunDirective(t, \"wrap-number\", `@wrap 42`,\n\t\tmap[string]any{\"wrapped\": float64(42)})\n}\n\nfunc TestDirectiveWrapKeyword(t *testing.T) {\n\trunDirective(t, \"wrap-keyword\", `@wrap true`,\n\t\tmap[string]any{\"wrapped\": true})\n}\n\nfunc TestDirectiveInList(t *testing.T) {\n\trunDirective(t, \"in-list\", `[1, @up \"x\", 2]`,\n\t\t[]any{float64(1), \"X\", float64(2)})\n}\n\nfunc TestDirectiveInMap(t *testing.T) {\n\trunDirective(t, \"in-map\", `{a: @up \"v\", b: @wrap 3}`,\n\t\tmap[string]any{\n\t\t\t\"a\": \"V\",\n\t\t\t\"b\": map[string]any{\"wrapped\": float64(3)},\n\t\t})\n}\n\nfunc TestDirectiveNested(t *testing.T) {\n\trunDirective(t, \"nested\", `@wrap @up \"hi\"`,\n\t\tmap[string]any{\"wrapped\": \"HI\"})\n}\n\nfunc TestDirectiveWrappingList(t *testing.T) {\n\trunDirective(t, \"wrapping-list\", `@wrap [1, @up \"x\"]`,\n\t\tmap[string]any{\"wrapped\": []any{float64(1), \"X\"}})\n}\n\nfunc TestDirectiveWrappingMap(t *testing.T) {\n\trunDirective(t, \"wrapping-map\", `@wrap {k: @up \"v\"}`,\n\t\tmap[string]any{\"wrapped\": map[string]any{\"k\": \"V\"}})\n}\n"
  },
  {
    "path": "go/doc/api.md",
    "content": "# API Reference (Go)\n\n```go\nimport \"github.com/jsonicjs/jsonic/go\"\n```\n\n## Parsing\n\n### `Parse(src string) (any, error)`\n\nParse a string using default settings. Convenience function that creates a\nfresh parser for each call.\n\n```go\nresult, err := jsonic.Parse(\"a:1, b:2\")\n// result: map[string]any{\"a\": float64(1), \"b\": float64(2)}\n```\n\n### `(*Jsonic) Parse(src string) (any, error)`\n\nParse using an instance's configuration.\n\n```go\nj := jsonic.Make()\nresult, err := j.Parse(\"a:1\")\n```\n\n### `(*Jsonic) ParseMeta(src string, meta map[string]any) (any, error)`\n\nParse with metadata accessible in rule actions via `ctx.Meta`.\n\n```go\nresult, err := j.ParseMeta(\"a:1\", map[string]any{\"filename\": \"config.jsonic\"})\n```\n\n## Instance Management\n\n### `Make(opts ...Options) *Jsonic`\n\nCreate a new parser instance. Unset option fields use defaults.\n\n```go\nj := jsonic.Make(jsonic.Options{\n    Comment: &jsonic.CommentOptions{Lex: boolp(false)},\n    Number:  &jsonic.NumberOptions{Hex: boolp(false)},\n})\n```\n\n### `(*Jsonic) Derive(opts ...Options) *Jsonic`\n\nCreate a child instance inheriting the parent's configuration, plugins, custom\ntokens, and subscriptions. Changes to the child do not affect the parent.\n\n```go\nchild := j.Derive(jsonic.Options{\n    Comment: &jsonic.CommentOptions{Lex: boolp(false)},\n})\n```\n\n### `(*Jsonic) SetOptions(opts Options) *Jsonic`\n\nDeep-merge new options into the instance and rebuild the configuration,\ngrammar, and plugins. Nil/zero fields in opts do not overwrite existing values,\nmatching the TypeScript `options()` setter behavior. Returns the instance for\nchaining.\n\n### `(*Jsonic) Options() Options`\n\nReturns a copy of the instance's current options.\n\n### `(*Jsonic) Decorate(name string, value any) *Jsonic`\n\nSet a named value on the instance. This is the Go equivalent of the\nTypeScript pattern where plugins add properties dynamically\n(`jsonic.foo = () => 'FOO'`). Decorations are inherited by `Derive`.\n\n```go\nj.Use(func(j *jsonic.Jsonic, opts map[string]any) {\n    j.Decorate(\"greet\", func(name string) string {\n        return \"hello \" + name\n    })\n})\n```\n\n### `(*Jsonic) Decoration(name string) any`\n\nReturns a named value previously set by `Decorate`, or nil.\n\n```go\nfn := j.Decoration(\"greet\").(func(string) string)\nfmt.Println(fn(\"world\")) // \"hello world\"\n```\n\n## Grammar\n\n### `(*Jsonic) Rule(name string, definer RuleDefiner) *Jsonic`\n\nModify or create a grammar rule. The definer callback receives the\n`*RuleSpec` and the owning `*Parser`, and can modify the rule's\n`Open`/`Close` alternate lists and state actions (`BO`, `BC`, `AO`, `AC`).\nThe parser is available for inspecting or referencing other rules.\n\n```go\nj.Rule(\"val\", func(rs *jsonic.RuleSpec, p *jsonic.Parser) {\n    rs.Open = append([]*jsonic.AltSpec{{\n        S: [][]jsonic.Tin{{myToken}},\n        A: func(r *jsonic.Rule, ctx *jsonic.Context) {\n            r.Node = \"custom\"\n        },\n    }}, rs.Open...)\n})\n```\n\n### `(*Jsonic) RSM() map[string]*RuleSpec`\n\nReturns the rule spec map for direct inspection.\n\n### `(*Jsonic) Token(name string, src ...string) Tin`\n\nRegister a new token type or look up an existing one. With `src`, registers\na fixed token mapping.\n\n```go\nTL := j.Token(\"#TL\", \"~\")  // register ~ as #TL token\nOB := j.Token(\"#OB\", \"\")   // look up existing #OB token\n```\n\n### `(*Jsonic) TokenSet(name string) []Tin`\n\nGet a named token set:\n- `\"IGNORE\"` -- space, line, comment tokens\n- `\"VAL\"` -- text, number, string, value tokens\n- `\"KEY\"` -- text, number, string, value tokens\n\n### `(*Jsonic) TinName(tin Tin) string`\n\nReturns the name for a token identification number.\n\n## Plugins\n\n### `(*Jsonic) Use(plugin Plugin, opts ...map[string]any) *Jsonic`\n\nRegister and execute a plugin. Returns the instance for chaining.\n\n```go\nj.Use(myPlugin, map[string]any{\"key\": \"value\"})\n```\n\n### `Plugin` type\n\n```go\ntype Plugin func(j *Jsonic, opts map[string]any)\n```\n\n### `(*Jsonic) Plugins() []Plugin`\n\nReturns the list of installed plugins.\n\n## Custom Matchers\n\nRegister custom lexer matchers via `options.lex.match`, keyed by name.\nThis mirrors the TypeScript `jsonic.options({ lex: { match: ... } })` API.\nMatchers are tried in priority order (lower first). Built-in priorities:\n\n| Matcher | Priority |\n|---|---|\n| fixed | 2,000,000 |\n| space | 3,000,000 |\n| line | 4,000,000 |\n| string | 5,000,000 |\n| comment | 6,000,000 |\n| number | 7,000,000 |\n| text | 8,000,000 |\n\nUse an `Order` below 2,000,000 to run before all built-ins.\n\n```go\nj := jsonic.Make()\nj.SetOptions(jsonic.Options{Lex: &jsonic.LexOptions{\n    Match: map[string]*jsonic.MatchSpec{\n        \"date\": {Order: 1_000_000, Make: func(_ *jsonic.LexConfig, _ *jsonic.Options) jsonic.LexMatcher {\n            return func(lex *jsonic.Lex, rule *jsonic.Rule) *jsonic.Token {\n                // ... read from lex.Cursor(), advance on match, return a Token\n                return nil\n            }\n        }},\n    },\n}})\n```\n\nSetting a spec under an existing name replaces it.\n\n### `LexMatcher` and `MakeLexMatcher` types\n\n```go\ntype LexMatcher     func(lex *Lex, rule *Rule) *Token\ntype MakeLexMatcher func(cfg *LexConfig, opts *Options) LexMatcher\n\ntype MatchSpec struct {\n    Order int            // lower runs first\n    Make  MakeLexMatcher // factory invoked when options are applied\n}\n```\n\nThe matcher reads the current position via `lex.Cursor()` and must advance\nthe cursor if it produces a token.\n\n## Events\n\n### `(*Jsonic) Sub(lexSub LexSub, ruleSub RuleSub) *Jsonic`\n\nSubscribe to lex and/or rule events. Pass `nil` for either to skip.\n\n```go\nj.Sub(func(tkn *jsonic.Token, rule *jsonic.Rule, ctx *jsonic.Context) {\n    fmt.Println(\"token:\", tkn)\n}, nil)\n```\n\n### Subscriber types\n\n```go\ntype LexSub func(tkn *Token, rule *Rule, ctx *Context)\ntype RuleSub func(rule *Rule, ctx *Context)\n```\n\n## Configuration\n\n### `(*Jsonic) Config() *LexConfig`\n\nReturns the parser's internal configuration for direct inspection or\nmodification. Prefer `Token()`, `Rule()`, and `options.lex.match` for most work.\n\n### `(*Jsonic) Exclude(groups ...string) *Jsonic`\n\nRemove grammar alternates tagged with the given group names.\n\n```go\nj.Exclude(\"jsonic\") // keep only JSON-tagged rules for strict parsing\n```\n\n## Error Handling\n\nParse errors are returned as `*JsonicError`:\n\n```go\ntype JsonicError struct {\n    Code   string // \"unexpected\", \"unterminated_string\", \"unterminated_comment\"\n    Detail string // Human-readable message\n    Pos    int    // 0-based character position\n    Row    int    // 1-based line number\n    Col    int    // 1-based column number\n    Src    string // Source fragment at error\n    Hint   string // Additional context (if configured)\n}\n```\n\n```go\nresult, err := jsonic.Parse(\"{a:\")\nif err != nil {\n    if je, ok := err.(*jsonic.JsonicError); ok {\n        fmt.Println(je.Code, \"at line\", je.Row)\n    }\n}\n```\n\n## Helper Pattern\n\nGo requires a pointer to pass `*bool` option fields. A common pattern:\n\n```go\nfunc boolp(b bool) *bool { return &b }\n\njsonic.Options{\n    Comment: &jsonic.CommentOptions{Lex: boolp(false)},\n}\n```\n\n## Constants\n\n### `Version`\n\n```go\nconst Version = \"0.1.6\"\n```\n\nThe current version of the jsonic Go module.\n"
  },
  {
    "path": "go/doc/differences.md",
    "content": "# Differences from TypeScript\n\nThe TypeScript version is the authoritative implementation. The Go version is\na faithful port but has some differences in behavior, missing features, and\nGo-specific additions.\n\n## Behavioral Differences\n\nThese affect parse output for the same input.\n\n### Number + Text Tokenization\n\nInput like `123abc` produces separate number and text tokens in TypeScript\nbut is rejected as not-a-number in Go (treated as text).\n\n```\n// TypeScript: 123abc → number(123) + text(\"abc\")\n// Go:         123abc → text(\"123abc\")\n```\n\n### Empty / Whitespace Input\n\nBoth implementations short-circuit exact empty-string input (`\"\"`).\nWhitespace/comment-only input is processed through the normal parse flow in both\nimplementations and resolves to `null`/`nil` by grammar behavior.\n\n### Token Consumption\n\nWhen no grammar alternate matches, both implementations now raise an immediate\nparse error. Token consumption behavior is aligned.\n\n## Missing Features\n\nThe following TypeScript features are not yet available in Go:\n\n| Feature | TS Option | Notes |\n|---|---|---|\n| Custom match matchers | `match.token`, `match.value` | Use `options.lex.match` instead |\n\n## Go-Specific Features\n\nThese are available only in the Go version:\n\n### `TextInfo` Option\n\nWraps string and text values in a `Text` struct that preserves the quote\ncharacter used:\n\n```go\nj := jsonic.Make(jsonic.Options{TextInfo: boolp(true)})\nresult, _ := j.Parse(`'hello'`)\n// result: jsonic.Text{Quote: '\\'', Str: \"hello\"}\n```\n\n### `ListRef` Option\n\nWraps arrays in a `ListRef` struct with metadata:\n\n```go\nj := jsonic.Make(jsonic.Options{ListRef: boolp(true)})\nresult, _ := j.Parse(\"a, b, c\")\n// result: jsonic.ListRef{Val: []any{\"a\", \"b\", \"c\"}, Implicit: true}\n```\n\n### `MapRef` Option\n\nWraps objects in a `MapRef` struct with metadata:\n\n```go\nj := jsonic.Make(jsonic.Options{MapRef: boolp(true)})\nresult, _ := j.Parse(\"a:1\")\n// result: jsonic.MapRef{Val: map[string]any{\"a\": 1.0}, Implicit: true}\n```\n\n## Plugin Differences\n\n| Area | TypeScript | Go |\n|---|---|---|\n| Plugin signature | `(jsonic, opts?) => void` | `func(j *Jsonic, opts map[string]any)` |\n| Rule definer | Receives `RuleSpec` + `Parser` | Receives `*RuleSpec` only |\n| State actions | Can return error tokens | No return value |\n| Option namespacing | Plugin options merged by name | No namespacing |\n| Custom matchers | Via `match` option | Via `options.lex.match` (keyed by name, same shape) |\n\n## Error Handling Differences\n\n| Area | TypeScript | Go |\n|---|---|---|\n| Parse errors | Thrown as exceptions | Returned as `error` |\n| Error messages | Template variable injection | Static messages |\n| ANSI colors | Supported | Not supported |\n| Error hints | Rich suffix with source context | Simple `Hint` string field |\n\n## Type System\n\nTypeScript returns untyped `any`. Go returns `any` but the concrete types are\npredictable:\n\n| Value | Go Type |\n|---|---|\n| Objects | `map[string]any` (or `MapRef` with option) |\n| Arrays | `[]any` (or `ListRef` with option) |\n| Strings | `string` (or `Text` with option) |\n| Numbers | `float64` |\n| Booleans | `bool` |\n| Null | `nil` |\n"
  },
  {
    "path": "go/doc/options.md",
    "content": "# Options Reference (Go)\n\nOptions are passed to `Make()` to configure a parser instance. All fields use\npointer types -- `nil` means \"use default\".\n\n```go\nj := jsonic.Make(jsonic.Options{\n    Comment: &jsonic.CommentOptions{Lex: boolp(false)},\n    Number:  &jsonic.NumberOptions{Hex: boolp(false)},\n})\n```\n\nOption fields use pointer types so that `nil` means \"use default\". Define\nsmall helpers to create pointer values:\n\n```go\nfunc boolp(b bool) *bool { return &b }\nfunc intp(i int) *int    { return &i }\n```\n\n## `Fixed`\n\nControls fixed structural tokens (`{`, `}`, `[`, `]`, `:`, `,`).\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable fixed token recognition |\n\n## `Space`\n\nControls whitespace handling.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable space recognition |\n| `Chars` | `string` | `\" \\t\"` | Characters treated as space |\n\n## `Line`\n\nControls line ending handling.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable line recognition |\n| `Chars` | `string` | `\"\\r\\n\"` | Line ending characters |\n| `RowChars` | `string` | `\"\\n\"` | Characters that increment the row counter |\n| `Single` | `*bool` | `false` | Separate token per newline |\n\n## `Text`\n\nControls unquoted text lexing.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable text matching |\n| `Modify` | `[]ValModifier` | `nil` | Pipeline of value transformers |\n\n`ValModifier` signature: `func(val any) any`\n\n## `Number`\n\nControls numeric literal parsing.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable number matching |\n| `Hex` | `*bool` | `true` | Support `0x` hexadecimal |\n| `Oct` | `*bool` | `true` | Support `0o` octal |\n| `Bin` | `*bool` | `true` | Support `0b` binary |\n| `Sep` | `string` | `\"_\"` | Separator character (empty to disable) |\n| `Exclude` | `func(string) bool` | `nil` | Return true to reject a number-like string |\n\n## `Comment`\n\nControls comment handling.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable all comment lexing |\n| `Def` | `map[string]*CommentDef` | (see below) | Comment type definitions |\n\nDefault definitions:\n\n```go\nmap[string]*CommentDef{\n    \"hash\":  {Line: true, Start: \"#\"},\n    \"slash\": {Line: true, Start: \"//\"},\n    \"block\": {Line: false, Start: \"/*\", End: \"*/\"},\n}\n```\n\n### `CommentDef`\n\n| Field | Type | Description |\n|---|---|---|\n| `Line` | `bool` | `true` for line comments, `false` for block |\n| `Start` | `string` | Start marker |\n| `End` | `string` | End marker (block only) |\n| `Lex` | `*bool` | Enable this definition (default: true) |\n| `EatLine` | `*bool` | Consume trailing newline (default: false) |\n\n## `String`\n\nControls quoted string parsing.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable string matching |\n| `Chars` | `string` | `\"'\\\"\\`` | Quote characters |\n| `MultiChars` | `string` | `` \"`\" `` | Multiline quote characters |\n| `EscapeChar` | `string` | `\"\\\\\"` | Escape character |\n| `Escape` | `map[string]string` | (standard) | Escape sequence mappings |\n| `AllowUnknown` | `*bool` | `true` | Allow unknown escape sequences |\n| `Abandon` | `*bool` | `false` | On error, return nil to let next matcher try |\n| `Replace` | `map[rune]string` | `nil` | Character replacements during scanning |\n\n## `Map`\n\nControls object/map behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Extend` | `*bool` | `true` | Deep-merge duplicate keys |\n| `Merge` | `MapMergeFunc` | `nil` | Custom merge: `func(prev, val any, r *Rule, ctx *Context) any` |\n| `Child` | `*bool` | `false` | Parse bare colon as `child$` key |\n\n## `List`\n\nControls array/list behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Property` | `*bool` | `true` | Allow key-value pairs in arrays |\n| `Pair` | `*bool` | `false` | Push pairs as object elements |\n| `Child` | `*bool` | `false` | Parse bare colon as child value |\n\n## `Value`\n\nControls keyword recognition.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Lex` | `*bool` | `true` | Enable value matching |\n| `Def` | `map[string]*ValueDef` | (see below) | Keyword definitions |\n\nDefault definitions:\n\n```go\nmap[string]*ValueDef{\n    \"true\":  {Val: true},\n    \"false\": {Val: false},\n    \"null\":  {Val: nil},\n}\n```\n\n## `Rule`\n\nControls parser rule behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Start` | `string` | `\"val\"` | Starting rule name |\n| `Finish` | `*bool` | `true` | Auto-close at EOF |\n| `MaxMul` | `*int` | `3` | Rule occurrence multiplier |\n| `Exclude` | `string` | `\"\"` | Comma-separated group tags to exclude |\n\n## `Lex`\n\nControls global lexer behavior.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Empty` | `*bool` | `true` | Allow empty source |\n| `EmptyResult` | `any` | `nil` | Value for empty source |\n\n## `Parser`\n\nCustom parser override.\n\n| Field | Type | Description |\n|---|---|---|\n| `Start` | `func(src string, j *Jsonic, meta map[string]any) (any, error)` | Replace the entire parse |\n\n## `Safe`\n\nControls security features.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `Key` | `*bool` | `true` | Block `__proto__` and `constructor` keys |\n\n## Go-Only Options\n\nThese options are specific to the Go version:\n\n### `TextInfo`\n\n| Type | Default | Description |\n|---|---|---|\n| `*bool` | `false` | Wrap string/text values in `Text{Quote, Str}` structs |\n\n### `ListRef`\n\n| Type | Default | Description |\n|---|---|---|\n| `*bool` | `false` | Wrap arrays in `ListRef{Val, Implicit, ...}` structs |\n\nAutomatically enabled when `List.Child` is true.\n\n### `MapRef`\n\n| Type | Default | Description |\n|---|---|---|\n| `*bool` | `false` | Wrap objects in `MapRef{Val, Implicit}` structs |\n\n## Other Fields\n\n| Field | Type | Description |\n|---|---|---|\n| `Ender` | `[]string` | Additional characters that end text tokens |\n| `Error` | `map[string]string` | Custom error message templates |\n| `Hint` | `map[string]string` | Additional error explanations |\n| `ConfigModify` | `map[string]ConfigModifier` | Post-config callbacks |\n| `Tag` | `string` | Instance identifier tag |\n"
  },
  {
    "path": "go/doc/plugins.md",
    "content": "# Writing Plugins (Go)\n\nPlugins extend jsonic by modifying the grammar, adding token types,\nregistering custom matchers, or subscribing to parse events.\n\n## Plugin Structure\n\nA plugin is a function with signature `Plugin`:\n\n```go\ntype Plugin func(j *Jsonic, opts map[string]any)\n```\n\nRegister a plugin with `Use`:\n\n```go\nfunc myPlugin(j *jsonic.Jsonic, opts map[string]any) {\n    // modify the parser\n}\n\nj := jsonic.Make()\nj.Use(myPlugin, map[string]any{\"key\": \"value\"})\n```\n\nPlugins are re-applied when `Derive()` creates a child instance.\n\n## Adding Tokens\n\nRegister a new fixed token:\n\n```go\nfunc tildePlugin(j *jsonic.Jsonic, opts map[string]any) {\n    TL := j.Token(\"#TL\", \"~\")\n    _ = TL\n}\n```\n\nToken names use `#XX` format by convention. Built-in tokens:\n\n| Name | Src | Description |\n|---|---|---|\n| `#OB` | `{` | Open brace |\n| `#CB` | `}` | Close brace |\n| `#OS` | `[` | Open square bracket |\n| `#CS` | `]` | Close square bracket |\n| `#CL` | `:` | Colon |\n| `#CA` | `,` | Comma |\n| `#NR` | -- | Number |\n| `#ST` | -- | String |\n| `#TX` | -- | Text |\n| `#VL` | -- | Value (keyword) |\n| `#SP` | -- | Space |\n| `#LN` | -- | Line ending |\n| `#CM` | -- | Comment |\n| `#BD` | -- | Bad (error) |\n| `#ZZ` | -- | End of input |\n\n## Modifying Rules\n\nThe parser has named rules, each with `Open` and `Close` alternate lists.\n\n```go\nfunc myPlugin(j *jsonic.Jsonic, opts map[string]any) {\n    TL := j.Token(\"#TL\", \"~\")\n\n    j.Rule(\"val\", func(rs *jsonic.RuleSpec, p *jsonic.Parser) {\n        // Prepend a new alternate to the open phase\n        rs.Open = append([]*jsonic.AltSpec{{\n            S: [][]jsonic.Tin{{TL}},\n            A: func(r *jsonic.Rule, ctx *jsonic.Context) {\n                r.Node = 42\n            },\n        }}, rs.Open...)\n    })\n}\n```\n\n### AltSpec Fields\n\n| Field | Type | Description |\n|---|---|---|\n| `S` | `[][]Tin` | Token pattern to match |\n| `A` | `StateAction` | Action: `func(r *Rule, ctx *Context)` |\n| `P` | `string` | Push a new rule by name |\n| `R` | `string` | Replace current rule |\n| `B` | `int` | Backtrack: tokens to put back |\n| `G` | `string` | Group tag (e.g., `\"json\"`, `\"jsonic,map\"`) |\n| `H` | `AltModifier` | Custom handler: `func(alt *AltSpec, r *Rule, ctx *Context) *AltSpec` |\n| `E` | `func(r *Rule, ctx *Context) *Token` | Error function |\n| `PF` | `func(r *Rule, ctx *Context) string` | Dynamic push |\n| `RF` | `func(r *Rule, ctx *Context) string` | Dynamic replace |\n| `BF` | `func(r *Rule, ctx *Context) int` | Dynamic backtrack |\n\n### State Actions\n\nEach `RuleSpec` has four hook points:\n\n| Hook | When |\n|---|---|\n| `BO` | Before open alternates are tried |\n| `AO` | After an open alternate matches |\n| `BC` | Before close alternates are tried |\n| `AC` | After a close alternate matches |\n\n```go\nj.Rule(\"map\", func(rs *jsonic.RuleSpec, p *jsonic.Parser) {\n    originalAO := rs.AO\n    rs.AO = func(r *jsonic.Rule, ctx *jsonic.Context) {\n        if originalAO != nil {\n            originalAO(r, ctx)\n        }\n        fmt.Println(\"opened a map\")\n    }\n})\n```\n\n## Custom Matchers\n\nFor syntax beyond the built-in matchers, register a matcher via\n`options.lex.match` (mirrors TS `jsonic.options({ lex: { match: ... } })`):\n\n```go\nj.SetOptions(jsonic.Options{Lex: &jsonic.LexOptions{\n    Match: map[string]*jsonic.MatchSpec{\n        \"date\": {Order: 1_000_000, Make: func(_ *jsonic.LexConfig, _ *jsonic.Options) jsonic.LexMatcher {\n            return func(lex *jsonic.Lex, rule *jsonic.Rule) *jsonic.Token {\n                // Read from lex.Cursor(), advance if matched, return *Token or nil\n                return nil\n            }\n        }},\n    },\n}})\n```\n\n`Order` determines ordering (lower runs first). Built-in priorities:\nfixed=2M, space=3M, line=4M, string=5M, comment=6M, number=7M, text=8M.\nSetting a spec under an existing name replaces it.\n\n## Subscribing to Events\n\n```go\nj.Sub(\n    func(tkn *jsonic.Token, rule *jsonic.Rule, ctx *jsonic.Context) {\n        fmt.Println(\"lexed:\", tkn)\n    },\n    func(rule *jsonic.Rule, ctx *jsonic.Context) {\n        fmt.Println(\"rule:\", rule.Name)\n    },\n)\n```\n\nPass `nil` for either subscriber to skip it.\n\n## Token Sets\n\nAccess groups of tokens:\n\n```go\nignore := j.TokenSet(\"IGNORE\") // [#SP, #LN, #CM]\nvals   := j.TokenSet(\"VAL\")    // [#TX, #NR, #ST, #VL]\nkeys   := j.TokenSet(\"KEY\")    // [#TX, #NR, #ST, #VL]\n```\n\n## Differences from TypeScript Plugins\n\n- No plugin option namespacing or defaults merging\n- `StateAction` has no return value (cannot return error tokens)\n- Custom matchers register via `options.lex.match` (same key/order shape as TS)\n- See [differences.md](differences.md) for the full list\n"
  },
  {
    "path": "go/doc/syntax.md",
    "content": "# Syntax Reference (Go)\n\nThe Go version of jsonic supports the same core syntax as the TypeScript\nversion. See the [top-level syntax reference](../../doc/syntax.md) for the\nfull specification.\n\nThis page notes Go-specific behavior. For a complete list of differences, see\n[differences.md](differences.md).\n\n## Return Types\n\njsonic maps parsed values to Go types:\n\n| JSON Type | Go Type |\n|---|---|\n| Object | `map[string]any` |\n| Array | `[]any` |\n| String | `string` |\n| Number (integer) | `float64` |\n| Number (float) | `float64` |\n| Boolean | `bool` |\n| Null | `nil` |\n\n### Extended Return Types\n\nWith options enabled, richer types are returned:\n\n- **`TextInfo: true`** -- string values become `jsonic.Text{Quote rune, Str string}`,\n  preserving which quote character was used.\n- **`ListRef: true`** -- arrays become `jsonic.ListRef{Val []any, Implicit bool, ...}`,\n  indicating whether the array was implicit (no brackets).\n- **`MapRef: true`** -- objects become `jsonic.MapRef{Val map[string]any, Implicit bool}`,\n  indicating whether the object was implicit (no braces).\n\n## Number Handling\n\nAll numbers are returned as `float64`, matching `encoding/json` conventions.\n\nThe `number+text` edge case differs from TypeScript: input like `123abc` is\nrejected as not-a-number in Go (parsed as text), while TypeScript produces\nseparate number and text tokens. See [differences.md](differences.md) for details.\n"
  },
  {
    "path": "go/feature_tsv_test.go",
    "content": "package jsonic\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\n// stripRefs recursively unwraps ListRef, MapRef, and Text back to plain\n// Go values ([]any, map[string]any, string) so they can be compared against\n// JSON-unmarshaled expected values from TSV files.\nfunc stripRefs(v any) any {\n\tif v == nil {\n\t\treturn nil\n\t}\n\tswitch val := v.(type) {\n\tcase ListRef:\n\t\tresult := make([]any, len(val.Val))\n\t\tfor i, elem := range val.Val {\n\t\t\tresult[i] = stripRefs(elem)\n\t\t}\n\t\treturn result\n\tcase MapRef:\n\t\tresult := make(map[string]any)\n\t\tfor k, elem := range val.Val {\n\t\t\tresult[k] = stripRefs(elem)\n\t\t}\n\t\treturn result\n\tcase Text:\n\t\treturn val.Str\n\tcase map[string]any:\n\t\tresult := make(map[string]any)\n\t\tfor k, elem := range val {\n\t\t\tresult[k] = stripRefs(elem)\n\t\t}\n\t\treturn result\n\tcase []any:\n\t\tresult := make([]any, len(val))\n\t\tfor i, elem := range val {\n\t\t\tresult[i] = stripRefs(elem)\n\t\t}\n\t\treturn result\n\tdefault:\n\t\treturn v\n\t}\n}\n\n// --- Standard parser TSV runner with custom options ---\n\n// runParserTSV runs a standard 2-column TSV (input, expected) with a custom parser.\nfunc runParserTSV(t *testing.T, file string, j *Jsonic) {\n\tt.Helper()\n\tpath := filepath.Join(specDir(), file)\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load %s: %v\", file, err)\n\t}\n\n\tfor _, row := range rows {\n\t\tif len(row.cols) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tinput := row.cols[0]\n\t\texpectedStr := row.cols[1]\n\n\t\texpected, err := parseExpected(expectedStr)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"line %d: failed to parse expected %q: %v\", row.lineNo, expectedStr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot, err := j.Parse(preprocessEscapes(input))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error: %v\", row.lineNo, input, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Strip ListRef/MapRef/Text wrappers for JSON comparison.\n\t\tgotPlain := stripRefs(got)\n\t\tif !valuesEqual(gotPlain, expected) {\n\t\t\tt.Errorf(\"line %d: Parse(%q)\\n  got:      %s\\n  expected: %s\",\n\t\t\t\trow.lineNo, input, formatValue(gotPlain), formatValue(expected))\n\t\t}\n\t}\n}\n\n// --- List-child TSV runner (3-column: input, expected_array, expected_child) ---\n\n// runListChildTSV runs a list-child TSV file.\nfunc runListChildTSV(t *testing.T, file string, j *Jsonic) {\n\tt.Helper()\n\tpath := filepath.Join(specDir(), file)\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load %s: %v\", file, err)\n\t}\n\n\tfor _, row := range rows {\n\t\tif len(row.cols) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tinput := row.cols[0]\n\t\texpectedArrStr := row.cols[1]\n\t\texpectedChildStr := \"\"\n\t\tif len(row.cols) >= 3 {\n\t\t\texpectedChildStr = row.cols[2]\n\t\t}\n\n\t\texpectedArr, err := parseExpected(expectedArrStr)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"line %d: failed to parse expected_array %q: %v\", row.lineNo, expectedArrStr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tvar expectedChild any\n\t\tif expectedChildStr != \"\" {\n\t\t\texpectedChild, err = parseExpected(expectedChildStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"line %d: failed to parse expected_child %q: %v\", row.lineNo, expectedChildStr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tgot, err := j.Parse(preprocessEscapes(input))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"line %d: Parse(%q) error: %v\", row.lineNo, input, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tlr, ok := got.(ListRef)\n\t\tif !ok {\n\t\t\tt.Errorf(\"line %d: Parse(%q) expected ListRef, got %T: %s\",\n\t\t\t\trow.lineNo, input, got, formatValue(got))\n\t\t\tcontinue\n\t\t}\n\n\t\t// Compare Val (strip inner refs for JSON comparison).\n\t\tgotArr := stripRefs(lr.Val)\n\t\tif !valuesEqual(gotArr, expectedArr) {\n\t\t\tt.Errorf(\"line %d: Parse(%q) Val\\n  got:      %s\\n  expected: %s\",\n\t\t\t\trow.lineNo, input, formatValue(gotArr), formatValue(expectedArr))\n\t\t}\n\n\t\t// Compare Child.\n\t\tgotChild := stripRefs(lr.Child)\n\t\tif !valuesEqual(gotChild, expectedChild) {\n\t\t\tt.Errorf(\"line %d: Parse(%q) Child\\n  got:      %s\\n  expected: %s\",\n\t\t\t\trow.lineNo, input, formatValue(gotChild), formatValue(expectedChild))\n\t\t}\n\t}\n}\n\n// --- feature-list-child.tsv ---\n\nfunc TestTSVFeatureListChild(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{Child: boolPtr(true)}})\n\trunListChildTSV(t, \"feature-list-child.tsv\", j)\n}\n\n// --- feature-list-child-deep.tsv ---\n\nfunc TestTSVFeatureListChildDeep(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{Child: boolPtr(true)}})\n\trunListChildTSV(t, \"feature-list-child-deep.tsv\", j)\n}\n\n// --- feature-list-child-pair.tsv ---\n\nfunc TestTSVFeatureListChildPair(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{\n\t\tChild: boolPtr(true),\n\t\tPair:  boolPtr(true),\n\t}})\n\trunListChildTSV(t, \"feature-list-child-pair.tsv\", j)\n}\n\n// --- feature-list-child-pair-deep.tsv ---\n\nfunc TestTSVFeatureListChildPairDeep(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{\n\t\tChild: boolPtr(true),\n\t\tPair:  boolPtr(true),\n\t}})\n\trunListChildTSV(t, \"feature-list-child-pair-deep.tsv\", j)\n}\n\n// --- feature-list-pair.tsv ---\n\nfunc TestTSVFeatureListPair(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{Pair: boolPtr(true)}})\n\trunParserTSV(t, \"feature-list-pair.tsv\", j)\n}\n\n// --- feature-map-child.tsv ---\n\nfunc TestTSVFeatureMapChild(t *testing.T) {\n\tj := Make(Options{Map: &MapOptions{Child: boolPtr(true)}})\n\trunParserTSV(t, \"feature-map-child.tsv\", j)\n}\n\n// --- feature-map-child-deep.tsv ---\n\nfunc TestTSVFeatureMapChildDeep(t *testing.T) {\n\t// Deep tests combine map.child with list.child for nested structures.\n\tj := Make(Options{\n\t\tMap:  &MapOptions{Child: boolPtr(true)},\n\t\tList: &ListOptions{Child: boolPtr(true)},\n\t})\n\trunParserTSV(t, \"feature-map-child-deep.tsv\", j)\n}\n\n// --- Verify formatValue handles ListRef for debugging ---\n\nfunc TestStripRefsBasic(t *testing.T) {\n\tlr := ListRef{\n\t\tVal:      []any{MapRef{Val: map[string]any{\"a\": 1.0}, Implicit: false}, \"hello\"},\n\t\tImplicit: true,\n\t\tChild:    Text{Str: \"world\", Quote: `\"`},\n\t}\n\tgot := stripRefs(lr)\n\texpected := []any{map[string]any{\"a\": 1.0}, \"hello\"}\n\tif !valuesEqual(got, expected) {\n\t\tb, _ := json.Marshal(got)\n\t\tt.Errorf(\"stripRefs: got %s, expected %s\", string(b), fmt.Sprintf(\"%v\", expected))\n\t}\n}\n"
  },
  {
    "path": "go/fnref_identity_test.go",
    "content": "package jsonic\n\nimport \"testing\"\n\n// TestFnrefDedupeByFunctionIdentity verifies that registering the same\n// StateAction twice via Grammar() installs it only once, while distinct\n// functions for the same phase both install. Mirrors JS behaviour.\nfunc TestFnrefDedupeByFunctionIdentity(t *testing.T) {\n\tj := Make()\n\n\tsameFnCount := 0\n\tsameFn := StateAction(func(r *Rule, ctx *Context) { sameFnCount++ })\n\n\tdiffFnCount := 0\n\tdiffFn := StateAction(func(r *Rule, ctx *Context) { diffFnCount++ })\n\n\t// Register sameFn twice (two Grammar() calls, same function) — should install once.\n\tif err := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\"pair\": {}},\n\t\tRef:  map[FuncRef]any{\"@pair-bc\": sameFn},\n\t}); err != nil {\n\t\tt.Fatalf(\"Grammar 1: %v\", err)\n\t}\n\tif err := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\"pair\": {}},\n\t\tRef:  map[FuncRef]any{\"@pair-bc\": sameFn},\n\t}); err != nil {\n\t\tt.Fatalf(\"Grammar 2: %v\", err)\n\t}\n\n\t// Register a DIFFERENT function — should install additionally.\n\tif err := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\"pair\": {}},\n\t\tRef:  map[FuncRef]any{\"@pair-bc\": diffFn},\n\t}); err != nil {\n\t\tt.Fatalf(\"Grammar 3: %v\", err)\n\t}\n\n\tif _, err := j.Parse(\"a:1\"); err != nil {\n\t\tt.Fatalf(\"Parse: %v\", err)\n\t}\n\tif sameFnCount != 1 {\n\t\tt.Errorf(\"sameFn fired %d times, want 1 (dedup by identity)\", sameFnCount)\n\t}\n\tif diffFnCount != 1 {\n\t\tt.Errorf(\"diffFn fired %d times, want 1 (distinct function installs)\", diffFnCount)\n\t}\n}\n"
  },
  {
    "path": "go/fnref_reinstall_test.go",
    "content": "package jsonic\n\nimport \"testing\"\n\n// TestFnrefNoReinstallOnSubsequentCall is the Go counterpart of the JS\n// fnref-no-reinstall regression test. It verifies that a second Grammar()\n// call registering an unrelated reserved handler (e.g. @pair-ao) does not\n// re-install previously-registered reserved handlers (e.g. @pair-bc), and\n// that map.merge fires exactly once per duplicate key.\nfunc TestFnrefNoReinstallOnSubsequentCall(t *testing.T) {\n\tmergeCalls := 0\n\tmerge := func(prev, val any, _ *Rule, _ *Context) any {\n\t\tmergeCalls++\n\t\tif arr, ok := prev.([]any); ok {\n\t\t\treturn append(arr, val)\n\t\t}\n\t\treturn []any{prev, val}\n\t}\n\n\tj := Make(Options{Map: &MapOptions{Merge: merge}})\n\n\tvar pairRS *RuleSpec\n\tj.Rule(\"pair\", func(rs *RuleSpec, _ *Parser) { pairRS = rs })\n\tpairBefore := len(pairRS.BC)\n\n\t// Register an unrelated @pair-ao via Grammar. This must not touch BC.\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\"pair\": {}},\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@pair-ao\": StateAction(func(_ *Rule, _ *Context) {}),\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Grammar returned error: %v\", err)\n\t}\n\n\tj.Rule(\"pair\", func(rs *RuleSpec, _ *Parser) { pairRS = rs })\n\tpairAfter := len(pairRS.BC)\n\tif pairAfter != pairBefore {\n\t\tt.Fatalf(\"pair BC count changed: before=%d after=%d (unrelated @pair-ao must not re-install @pair-bc)\",\n\t\t\tpairBefore, pairAfter)\n\t}\n\n\tmergeCalls = 0\n\tif _, err := j.Parse(\"a:{x:1},a:{y:2},a:{z:3}\"); err != nil {\n\t\tt.Fatalf(\"Parse error: %v\", err)\n\t}\n\tif mergeCalls != 2 {\n\t\tt.Fatalf(\"merge called %d times for 3 duplicate keys, want 2\", mergeCalls)\n\t}\n}\n"
  },
  {
    "path": "go/go.mod",
    "content": "module github.com/jsonicjs/jsonic/go\n\ngo 1.24.7\n"
  },
  {
    "path": "go/grammar.go",
    "content": "package jsonic\n\n// buildGrammar populates the default jsonic grammar rules using declarative GrammarAltSpec.\n// This is a faithful port of grammar.ts, matching the exact alternate orderings\n// produced by the JSON phase followed by the Jsonic extension phase.\nfunc buildGrammar(rsm map[string]*RuleSpec, cfg *LexConfig) {\n\t// Named function references for the grammar.\n\t// These closures capture cfg for runtime configuration access.\n\tref := map[FuncRef]any{\n\t\t\"@finish\": AltError(func(r *Rule, ctx *Context) *Token {\n\t\t\tif !cfg.FinishRule {\n\t\t\t\treturn ctx.T0\n\t\t\t}\n\t\t\treturn nil\n\t\t}),\n\n\t\t\"@pairkey\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tkeyToken := r.O0\n\t\t\tvar key string\n\t\t\tif keyToken.Tin == TinST || keyToken.Tin == TinTX {\n\t\t\t\tkey, _ = keyToken.Val.(string)\n\t\t\t} else {\n\t\t\t\tkey = keyToken.Src\n\t\t\t}\n\t\t\tr.U[\"key\"] = key\n\t\t}),\n\n\t\t\"@pairval\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\tkey, _ := r.U[\"key\"].(string)\n\t\t\tval := r.Child.Node\n\t\t\tif IsUndefined(val) {\n\t\t\t\tval = nil\n\t\t\t}\n\t\t\tif cfg.SafeKey && r.U[\"list\"] == true {\n\t\t\t\tif key == \"__proto__\" || key == \"constructor\" {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Drop keys that match the info marker to preserve metadata.\n\t\t\tif cfg.InfoMarker != \"\" && key == cfg.InfoMarker {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tprev := r.U[\"prev\"]\n\t\t\tif prev == nil {\n\t\t\t\tnodeMapSet(r.Node, key, val)\n\t\t\t} else if cfg.MapMerge != nil {\n\t\t\t\tnodeMapSet(r.Node, key, cfg.MapMerge(prev, val, r, ctx))\n\t\t\t} else if cfg.MapExtend {\n\t\t\t\tnodeMapSet(r.Node, key, Deep(prev, val))\n\t\t\t} else {\n\t\t\t\tnodeMapSet(r.Node, key, val)\n\t\t\t}\n\t\t}),\n\n\t\t// val rule state actions\n\t\t\"@val-bo\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tr.Node = Undefined\n\t\t}),\n\n\t\t\"@val-bc\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif IsUndefined(r.Node) {\n\t\t\t\tif IsUndefined(r.Child.Node) {\n\t\t\t\t\tif r.OS == 0 {\n\t\t\t\t\t\tr.Node = Undefined\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval := r.O0.ResolveVal(r, ctx)\n\t\t\t\t\t\t// A nil value from a non-value token (e.g. #CS, #CB)\n\t\t\t\t\t\t// means \"no value\", not \"null\". Keep Undefined to match\n\t\t\t\t\t\t// TS where resolveVal returns undefined for such tokens.\n\t\t\t\t\t\tif val == nil && r.O0.Tin != TinVL {\n\t\t\t\t\t\t\tr.Node = Undefined\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif cfg.TextInfo && (r.O0.Tin == TinST || r.O0.Tin == TinTX) {\n\t\t\t\t\t\t\t\tquote := \"\"\n\t\t\t\t\t\t\t\tif r.O0.Tin == TinST && len(r.O0.Src) > 0 {\n\t\t\t\t\t\t\t\t\tquote = string(r.O0.Src[0])\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstr, _ := val.(string)\n\t\t\t\t\t\t\t\tval = Text{Quote: quote, Str: str}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tr.Node = val\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tr.Node = r.Child.Node\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t// map rule state actions\n\t\t\"@map-bo\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif cfg.MapRef {\n\t\t\t\tr.Node = MapRef{\n\t\t\t\t\tVal:  make(map[string]any),\n\t\t\t\t\tMeta: make(map[string]any),\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tr.Node = make(map[string]any)\n\t\t\t}\n\t\t}),\n\n\t\t\"@map-bo-jsonic\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif v, ok := r.N[\"dmap\"]; ok {\n\t\t\t\tr.N[\"dmap\"] = v + 1\n\t\t\t} else {\n\t\t\t\tr.N[\"dmap\"] = 1\n\t\t\t}\n\t\t}),\n\n\t\t\"@map-bc\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif cfg.MapRef {\n\t\t\t\timplicit := !(r.O0 != NoToken && r.O0.Tin == TinOB)\n\t\t\t\tif mr, ok := r.Node.(MapRef); ok {\n\t\t\t\t\tmr.Implicit = implicit\n\t\t\t\t\tr.Node = mr\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t// list rule state actions\n\t\t\"@list-bo\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif cfg.ListRef {\n\t\t\t\tr.Node = ListRef{\n\t\t\t\t\tVal:  make([]any, 0),\n\t\t\t\t\tMeta: make(map[string]any),\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tr.Node = make([]any, 0)\n\t\t\t}\n\t\t}),\n\n\t\t\"@list-bo-jsonic\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif v, ok := r.N[\"dlist\"]; ok {\n\t\t\t\tr.N[\"dlist\"] = v + 1\n\t\t\t} else {\n\t\t\t\tr.N[\"dlist\"] = 1\n\t\t\t}\n\t\t\tif r.Prev != NoRule && r.Prev != nil {\n\t\t\t\tif implist, ok := r.Prev.U[\"implist\"]; ok && implist == true {\n\t\t\t\t\tprevNode := r.Prev.Node\n\t\t\t\t\tif IsUndefined(prevNode) {\n\t\t\t\t\t\tprevNode = nil\n\t\t\t\t\t}\n\t\t\t\t\tr.Node = nodeListAppend(r.Node, prevNode)\n\t\t\t\t\tr.Prev.Node = r.Node\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t\"@list-bc\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif cfg.ListRef {\n\t\t\t\timplicit := !(r.O0 != NoToken && r.O0.Tin == TinOS)\n\t\t\t\tif lr, ok := r.Node.(ListRef); ok {\n\t\t\t\t\tlr.Implicit = implicit\n\t\t\t\t\tif c, ok := r.U[\"child$\"]; ok {\n\t\t\t\t\t\tlr.Child = c\n\t\t\t\t\t}\n\t\t\t\t\tr.Node = lr\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t// pair rule state actions\n\t\t\"@pair-bc-json\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\tif _, ok := r.U[\"pair\"]; ok {\n\t\t\t\tkey, _ := r.U[\"key\"].(string)\n\t\t\t\tif cfg.SafeKey && r.U[\"list\"] == true && (key == \"__proto__\" || key == \"constructor\") {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Drop keys that match the info marker to preserve metadata.\n\t\t\t\tif cfg.InfoMarker != \"\" && key == cfg.InfoMarker {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tr.U[\"prev\"] = nodeMapGetVal(r.Node, r.U[\"key\"])\n\t\t\t\tnodeMapSet(r.Node, key, r.Child.Node)\n\t\t\t}\n\t\t}),\n\n\t\t\"@pair-bc-jsonic\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\tif _, ok := r.U[\"pair\"]; ok {\n\t\t\t\tkey, _ := r.U[\"key\"].(string)\n\t\t\t\tval := r.Child.Node\n\t\t\t\tif IsUndefined(val) {\n\t\t\t\t\tval = nil\n\t\t\t\t}\n\t\t\t\tif cfg.SafeKey && r.U[\"list\"] == true {\n\t\t\t\t\tif key == \"__proto__\" || key == \"constructor\" {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Drop keys that match the info marker to preserve metadata.\n\t\t\t\tif cfg.InfoMarker != \"\" && key == cfg.InfoMarker {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tprev := r.U[\"prev\"]\n\t\t\t\tif prev == nil {\n\t\t\t\t\tnodeMapSet(r.Node, key, val)\n\t\t\t\t} else if cfg.MapMerge != nil {\n\t\t\t\t\tnodeMapSet(r.Node, key, cfg.MapMerge(prev, val, r, ctx))\n\t\t\t\t} else if cfg.MapExtend {\n\t\t\t\t\tnodeMapSet(r.Node, key, Deep(prev, val))\n\t\t\t\t} else {\n\t\t\t\t\tnodeMapSet(r.Node, key, val)\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t\"@pair-bc-child\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\tif childFlag, ok := r.U[\"child\"]; !ok || childFlag != true {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval := r.Child.Node\n\t\t\tif IsUndefined(val) {\n\t\t\t\tval = nil\n\t\t\t}\n\t\t\tprev, hasPrev := nodeMapGet(r.Node, \"child$\")\n\t\t\tif !hasPrev {\n\t\t\t\tnodeMapSet(r.Node, \"child$\", val)\n\t\t\t} else if cfg.MapMerge != nil {\n\t\t\t\tnodeMapSet(r.Node, \"child$\", cfg.MapMerge(prev, val, r, ctx))\n\t\t\t} else if cfg.MapExtend {\n\t\t\t\tnodeMapSet(r.Node, \"child$\", Deep(prev, val))\n\t\t\t} else {\n\t\t\t\tnodeMapSet(r.Node, \"child$\", val)\n\t\t\t}\n\t\t}),\n\n\t\t// elem rule state actions\n\t\t\"@elem-bc-json\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tdone, _ := r.U[\"done\"].(bool)\n\t\t\tif !done && !IsUndefined(r.Child.Node) {\n\t\t\t\tif _, ok := nodeListVal(r.Node); ok {\n\t\t\t\t\tr.Node = nodeListAppend(r.Node, r.Child.Node)\n\t\t\t\t\tif r.Parent != NoRule && r.Parent != nil {\n\t\t\t\t\t\tr.Parent.Node = r.Node\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t\"@elem-bc-pair\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\tif pair, ok := r.U[\"pair\"]; !ok || pair != true {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif cfg.ListPair {\n\t\t\t\tkey := r.U[\"key\"].(string)\n\t\t\t\tval := r.Child.Node\n\t\t\t\tif IsUndefined(val) {\n\t\t\t\t\tval = nil\n\t\t\t\t}\n\t\t\t\tpairObj := map[string]any{key: val}\n\t\t\t\tif _, ok := nodeListVal(r.Node); ok {\n\t\t\t\t\tr.Node = nodeListAppend(r.Node, pairObj)\n\t\t\t\t\tif r.Parent != NoRule && r.Parent != nil {\n\t\t\t\t\t\tr.Parent.Node = r.Node\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tr.U[\"prev\"] = nodeMapGetVal(r.Node, r.U[\"key\"])\n\t\t\t\tkey, _ := r.U[\"key\"].(string)\n\t\t\t\tval := r.Child.Node\n\t\t\t\tif IsUndefined(val) {\n\t\t\t\t\tval = nil\n\t\t\t\t}\n\t\t\t\tif cfg.SafeKey && r.U[\"list\"] == true {\n\t\t\t\t\tif key == \"__proto__\" || key == \"constructor\" {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Drop keys that match the info marker to preserve metadata.\n\t\t\t\tif cfg.InfoMarker != \"\" && key == cfg.InfoMarker {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tprev := r.U[\"prev\"]\n\t\t\t\tif prev == nil {\n\t\t\t\t\tnodeMapSet(r.Node, key, val)\n\t\t\t\t} else if cfg.MapMerge != nil {\n\t\t\t\t\tnodeMapSet(r.Node, key, cfg.MapMerge(prev, val, r, ctx))\n\t\t\t\t} else if cfg.MapExtend {\n\t\t\t\t\tnodeMapSet(r.Node, key, Deep(prev, val))\n\t\t\t\t} else {\n\t\t\t\t\tnodeMapSet(r.Node, key, val)\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t\"@elem-bc-child\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif childFlag, ok := r.U[\"child\"]; !ok || childFlag != true {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval := r.Child.Node\n\t\t\tif IsUndefined(val) {\n\t\t\t\tval = nil\n\t\t\t}\n\t\t\tif r.Parent != NoRule && r.Parent != nil {\n\t\t\t\tprev, hasPrev := r.Parent.U[\"child$\"]\n\t\t\t\tif !hasPrev {\n\t\t\t\t\tr.Parent.U[\"child$\"] = val\n\t\t\t\t} else if cfg.MapExtend {\n\t\t\t\t\tr.Parent.U[\"child$\"] = Deep(prev, val)\n\t\t\t\t} else {\n\t\t\t\t\tr.Parent.U[\"child$\"] = val\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t// Inline actions used in alts\n\t\t\"@val-close-err\": AltError(func(r *Rule, ctx *Context) *Token {\n\t\t\tif r.D == 0 {\n\t\t\t\treturn ctx.T0\n\t\t\t}\n\t\t\treturn nil\n\t\t}),\n\n\t\t\"@implist-cond\": AltCond(func(r *Rule, ctx *Context) bool {\n\t\t\treturn r.Prev != NoRule && r.Prev != nil && r.Prev.U[\"implist\"] == true\n\t\t}),\n\n\t\t\"@elem-double-comma\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif _, ok := nodeListVal(r.Node); ok {\n\t\t\t\tr.Node = nodeListAppend(r.Node, nil)\n\t\t\t\tif r.Parent != NoRule && r.Parent != nil {\n\t\t\t\t\tr.Parent.Node = r.Node\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t\"@elem-single-comma\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t_ = ctx\n\t\t\tif _, ok := nodeListVal(r.Node); ok {\n\t\t\t\tr.Node = nodeListAppend(r.Node, nil)\n\t\t\t\tif r.Parent != NoRule && r.Parent != nil {\n\t\t\t\t\tr.Parent.Node = r.Node\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\t\"@elem-pair-err\": AltError(func(r *Rule, ctx *Context) *Token {\n\t\t\tif cfg.ListProperty || cfg.ListPair {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn ctx.T0\n\t\t}),\n\n\t\t\"@elem-close-err\": AltError(func(r *Rule, ctx *Context) *Token {\n\t\t\treturn r.C0\n\t\t}),\n\t}\n\n\t// Helper to resolve a GrammarAltSpec slice to []*AltSpec.\n\tresolve := func(gas []*GrammarAltSpec) []*AltSpec {\n\t\talts := make([]*AltSpec, len(gas))\n\t\tfor i, ga := range gas {\n\t\t\talts[i] = resolveGrammarAltStatic(ga, ref)\n\t\t}\n\t\treturn alts\n\t}\n\n\t// ====== VAL rule ======\n\tvalSpec := &RuleSpec{Name: \"val\"}\n\n\tvalSpec.BO = []StateAction{ref[\"@val-bo\"].(StateAction)}\n\tvalSpec.BC = []StateAction{ref[\"@val-bc\"].(StateAction)}\n\n\tvalOpen := resolve([]*GrammarAltSpec{\n\t\t{S: \"#OB\", P: \"map\", B: 1, G: \"map,json\"},\n\t\t{S: \"#OS\", P: \"list\", B: 1, G: \"list,json\"},\n\t\t{S: \"#KEY #CL\", C: map[string]any{\"d\": 0}, P: \"map\", B: 2, G: \"pair,jsonic,top\"},\n\t\t{S: \"#KEY #CL\", P: \"map\", B: 2, N: map[string]int{\"pk\": 1}, G: \"pair,jsonic\"},\n\t\t{S: \"#VAL\", G: \"val,json\"},\n\t})\n\t// CB|CS in single slot:\n\tvalOpen = append(valOpen, resolveGrammarAltStatic(\n\t\t&GrammarAltSpec{S: []string{\"#CB #CS\"}, C: map[string]any{\"d\": CGt(0)}, B: 1, G: \"val,imp,null,jsonic\"}, ref))\n\tvalOpen = append(valOpen, resolve([]*GrammarAltSpec{\n\t\t{S: \"#CA\", C: map[string]any{\"d\": 0}, P: \"list\", B: 1, G: \"list,imp,jsonic\"},\n\t\t{S: \"#CA\", B: 1, G: \"list,val,imp,null,jsonic\"},\n\t\t{S: \"#ZZ\", G: \"jsonic\"},\n\t})...)\n\tvalSpec.Open = valOpen\n\n\tvalClose := resolve([]*GrammarAltSpec{\n\t\t{S: \"#ZZ\", G: \"end,json\"},\n\t})\n\t// CB|CS in single slot:\n\tvalClose = append(valClose, resolveGrammarAltStatic(\n\t\t&GrammarAltSpec{S: []string{\"#CB #CS\"}, B: 1, E: \"@val-close-err\", G: \"val,json,close\"}, ref))\n\tvalClose = append(valClose, resolve([]*GrammarAltSpec{\n\t\t{S: \"#CA\", C: map[string]any{\"n.dlist\": CLte(0), \"n.dmap\": CLte(0)},\n\t\t\tR: \"list\", U: map[string]any{\"implist\": true}, G: \"list,val,imp,comma,jsonic\"},\n\t\t{C: map[string]any{\"n.dlist\": CLte(0), \"n.dmap\": CLte(0)},\n\t\t\tR: \"list\", U: map[string]any{\"implist\": true}, B: 1, G: \"list,val,imp,space,jsonic\"},\n\t\t{S: \"#ZZ\", G: \"jsonic\"},\n\t\t{B: 1, G: \"more,json\"},\n\t})...)\n\tvalSpec.Close = valClose\n\n\t// ====== MAP rule ======\n\tmapSpec := &RuleSpec{Name: \"map\"}\n\n\tmapSpec.BO = []StateAction{\n\t\tref[\"@map-bo\"].(StateAction),\n\t\tref[\"@map-bo-jsonic\"].(StateAction),\n\t}\n\tmapSpec.BC = []StateAction{ref[\"@map-bc\"].(StateAction)}\n\n\tmapSpec.Open = resolve([]*GrammarAltSpec{\n\t\t{S: \"#OB #ZZ\", B: 1, E: \"@finish\", G: \"end,jsonic\"},\n\t\t{S: \"#OB #CB\", B: 1, N: map[string]int{\"pk\": 0}, G: \"map,json\"},\n\t\t{S: \"#OB\", P: \"pair\", N: map[string]int{\"pk\": 0}, G: \"map,json,pair\"},\n\t\t{S: \"#KEY #CL\", P: \"pair\", B: 2, G: \"pair,list,val,imp,jsonic\"},\n\t})\n\n\t// For map.Close, we need to merge token sets for the third alt.\n\t// \"#CA #CS\" + VAL tokens in a single slot → need raw AltSpec for that one.\n\tmapClose := resolve([]*GrammarAltSpec{\n\t\t{S: \"#CB\", C: map[string]any{\"n.pk\": CLte(0)}, G: \"end,json\"},\n\t\t{S: \"#CB\", B: 1, G: \"path,jsonic\"},\n\t\t// slot 0 = merge(CA, CS, VAL) — handled below\n\t})\n\t// Third alt: CA|CS|VAL tokens in single slot\n\tmapClose = append(mapClose, resolveGrammarAltStatic(\n\t\t&GrammarAltSpec{S: []string{\"#CA #CS #VAL\"}, B: 1, G: \"end,path,jsonic\"}, ref))\n\tmapClose = append(mapClose, resolveGrammarAltStatic(\n\t\t&GrammarAltSpec{S: \"#ZZ\", E: \"@finish\", G: \"end,jsonic\"}, ref))\n\tmapSpec.Close = mapClose\n\n\t// ====== LIST rule ======\n\tlistSpec := &RuleSpec{Name: \"list\"}\n\n\tlistSpec.BO = []StateAction{\n\t\tref[\"@list-bo\"].(StateAction),\n\t\tref[\"@list-bo-jsonic\"].(StateAction),\n\t}\n\tlistSpec.BC = []StateAction{ref[\"@list-bc\"].(StateAction)}\n\n\t// First alt uses a condition function directly (not declarative).\n\tlistOpen := []*AltSpec{\n\t\tresolveGrammarAltStatic(&GrammarAltSpec{C: \"@implist-cond\", P: \"elem\"}, ref),\n\t}\n\tlistOpen = append(listOpen, resolve([]*GrammarAltSpec{\n\t\t{S: \"#OS #CS\", B: 1, G: \"list,json\"},\n\t\t{S: \"#OS\", P: \"elem\", G: \"list,elem,json\"},\n\t\t{S: \"#CA\", P: \"elem\", B: 1, G: \"list,elem,val,imp,jsonic\"},\n\t\t{P: \"elem\", G: \"list,elem,jsonic\"},\n\t})...)\n\tlistSpec.Open = listOpen\n\n\tlistSpec.Close = resolve([]*GrammarAltSpec{\n\t\t{S: \"#CS\", G: \"end,json\"},\n\t\t{S: \"#ZZ\", E: \"@finish\", G: \"end,jsonic\"},\n\t})\n\n\t// ====== PAIR rule ======\n\tpairSpec := &RuleSpec{Name: \"pair\"}\n\n\tpairSpec.BC = []StateAction{\n\t\tref[\"@pair-bc-json\"].(StateAction),\n\t\tref[\"@pair-bc-jsonic\"].(StateAction),\n\t\tref[\"@pair-bc-child\"].(StateAction),\n\t}\n\n\tpairOpen := resolve([]*GrammarAltSpec{\n\t\t{S: \"#KEY #CL\", P: \"val\", U: map[string]any{\"pair\": true}, A: \"@pairkey\", G: \"map,pair,key,json\"},\n\t\t{S: \"#CA\", G: \"map,pair,comma,jsonic\"},\n\t})\n\tif cfg.MapChild {\n\t\tpairOpen = append(pairOpen, resolveGrammarAltStatic(\n\t\t\t&GrammarAltSpec{S: \"#CL\", P: \"val\",\n\t\t\t\tU: map[string]any{\"done\": true, \"child\": true}}, ref))\n\t}\n\tpairSpec.Open = pairOpen\n\n\tpairSpec.Close = resolve([]*GrammarAltSpec{\n\t\t{S: \"#CB\", C: map[string]any{\"n.pk\": CLte(0)}, B: 1, G: \"map,pair,json\"},\n\t\t{S: \"#CA #CB\", C: map[string]any{\"n.pk\": CLte(0)}, B: 1, G: \"map,pair,comma,jsonic\"},\n\t\t{S: \"#CA #ZZ\", G: \"end,jsonic\"},\n\t\t{S: \"#CA\", C: map[string]any{\"n.pk\": CLte(0)}, R: \"pair\", G: \"map,pair,json\"},\n\t\t{S: \"#CA\", C: map[string]any{\"n.dmap\": CLte(1)}, R: \"pair\", G: \"map,pair,jsonic\"},\n\t\t{S: \"#KEY\", C: map[string]any{\"n.dmap\": CLte(1)}, R: \"pair\", B: 1, G: \"map,pair,imp,jsonic\"},\n\t})\n\n\t// CB|CA|CS|KEY in single slot\n\tpairSpec.Close = append(pairSpec.Close, resolveGrammarAltStatic(\n\t\t&GrammarAltSpec{S: []string{\"#CB #CA #CS #KEY\"}, C: map[string]any{\"n.pk\": CGt(0)},\n\t\t\tB: 1, G: \"map,pair,imp,path,jsonic\"}, ref))\n\t// Remaining pair close alts.\n\tpairSpec.Close = append(pairSpec.Close, resolve([]*GrammarAltSpec{\n\t\t{S: \"#CS\", E: \"@elem-close-err\", G: \"end,jsonic\"},\n\t\t{S: \"#ZZ\", E: \"@finish\", G: \"map,pair,json\"},\n\t\t{R: \"pair\", B: 1, G: \"map,pair,imp,jsonic\"},\n\t})...)\n\n\t// ====== ELEM rule ======\n\telemSpec := &RuleSpec{Name: \"elem\"}\n\n\telemSpec.BC = []StateAction{\n\t\tref[\"@elem-bc-json\"].(StateAction),\n\t\tref[\"@elem-bc-pair\"].(StateAction),\n\t\tref[\"@elem-bc-child\"].(StateAction),\n\t}\n\n\telemOpen := resolve([]*GrammarAltSpec{\n\t\t{S: \"#CA #CA\", B: 2, U: map[string]any{\"done\": true}, A: \"@elem-double-comma\",\n\t\t\tG: \"list,elem,imp,null,jsonic\"},\n\t\t{S: \"#CA\", U: map[string]any{\"done\": true}, A: \"@elem-single-comma\",\n\t\t\tG: \"list,elem,imp,null,jsonic\"},\n\t\t{S: \"#KEY #CL\", P: \"val\",\n\t\t\tN: map[string]int{\"pk\": 1, \"dmap\": 1},\n\t\t\tU: map[string]any{\"done\": true, \"pair\": true, \"list\": true},\n\t\t\tA: \"@pairkey\", E: \"@elem-pair-err\", G: \"elem,pair,jsonic\"},\n\t})\n\tif cfg.ListChild {\n\t\telemOpen = append(elemOpen, resolveGrammarAltStatic(\n\t\t\t&GrammarAltSpec{S: \"#CL\", P: \"val\",\n\t\t\t\tU: map[string]any{\"done\": true, \"child\": true, \"list\": true},\n\t\t\t\tG: \"elem,child,jsonic\"}, ref))\n\t}\n\telemOpen = append(elemOpen, resolveGrammarAltStatic(\n\t\t&GrammarAltSpec{P: \"val\", G: \"list,elem,val,json\"}, ref))\n\telemSpec.Open = elemOpen\n\n\telemClose := []*AltSpec{\n\t\t// CA in slot 0, CS|ZZ in slot 1:\n\t\tresolveGrammarAltStatic(&GrammarAltSpec{S: []string{\"#CA\", \"#CS #ZZ\"}, B: 1, G: \"list,elem,comma,jsonic\"}, ref),\n\t}\n\telemClose = append(elemClose, resolve([]*GrammarAltSpec{\n\t\t{S: \"#CA\", R: \"elem\", G: \"list,elem,json\"},\n\t\t{S: \"#CS\", B: 1, G: \"list,elem,json\"},\n\t\t{S: \"#ZZ\", E: \"@finish\", G: \"list,elem,json\"},\n\t\t{S: \"#CB\", E: \"@elem-close-err\", G: \"end,jsonic\"},\n\t\t{R: \"elem\", B: 1, G: \"list,elem,imp,jsonic\"},\n\t})...)\n\telemSpec.Close = elemClose\n\n\trsm[\"val\"] = valSpec\n\trsm[\"map\"] = mapSpec\n\trsm[\"list\"] = listSpec\n\trsm[\"pair\"] = pairSpec\n\trsm[\"elem\"] = elemSpec\n}\n\n// nodeListAppend appends a value to a list node (plain []any or ListRef).\nfunc nodeListAppend(node any, val any) any {\n\tif lr, ok := node.(ListRef); ok {\n\t\tlr.Val = append(lr.Val, val)\n\t\treturn lr\n\t}\n\tif arr, ok := node.([]any); ok {\n\t\treturn append(arr, val)\n\t}\n\treturn node\n}\n\n// nodeListVal extracts the []any from a list node.\nfunc nodeListVal(node any) ([]any, bool) {\n\tif lr, ok := node.(ListRef); ok {\n\t\treturn lr.Val, true\n\t}\n\tif arr, ok := node.([]any); ok {\n\t\treturn arr, true\n\t}\n\treturn nil, false\n}\n\n// nodeListSetVal updates the []any inside a list node.\nfunc nodeListSetVal(node any, arr []any) any {\n\tif lr, ok := node.(ListRef); ok {\n\t\tlr.Val = arr\n\t\treturn lr\n\t}\n\treturn arr\n}\n\n// nodeMapSet sets a key on a map node.\nfunc nodeMapSet(node any, key any, val any) {\n\tk, _ := key.(string)\n\tif m, ok := node.(map[string]any); ok {\n\t\tm[k] = val\n\t} else if mr, ok := node.(MapRef); ok {\n\t\tmr.Val[k] = val\n\t}\n}\n\n// nodeMapGet gets a value from a map node.\nfunc nodeMapGet(node any, key any) (any, bool) {\n\tk, _ := key.(string)\n\tif m, ok := node.(map[string]any); ok {\n\t\tv, exists := m[k]\n\t\treturn v, exists\n\t}\n\tif mr, ok := node.(MapRef); ok {\n\t\tv, exists := mr.Val[k]\n\t\treturn v, exists\n\t}\n\treturn nil, false\n}\n\n// nodeMapGetVal returns the value or nil.\nfunc nodeMapGetVal(node any, key any) any {\n\tv, _ := nodeMapGet(node, key)\n\treturn v\n}\n"
  },
  {
    "path": "go/grammar_decl_test.go",
    "content": "package jsonic\n\nimport (\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// mustGrammar calls Grammar and fails the test on error.\nfunc mustGrammar(t *testing.T, j *Jsonic, gs *GrammarSpec) {\n\tt.Helper()\n\tif err := j.Grammar(gs); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n// --- Skip sentinel ---\n\nfunc TestSkipSentinel(t *testing.T) {\n\tif !IsSkip(Skip) {\n\t\tt.Error(\"IsSkip(Skip) should be true\")\n\t}\n\tif IsSkip(nil) {\n\t\tt.Error(\"IsSkip(nil) should be false\")\n\t}\n\tif IsSkip(\"@SKIP\") {\n\t\tt.Error(\"IsSkip(string) should be false\")\n\t}\n}\n\nfunc TestSkipInDeepMerge(t *testing.T) {\n\tbase := map[string]any{\"a\": 1, \"b\": 2}\n\tover := map[string]any{\"a\": Skip, \"b\": 3}\n\tresult := Deep(base, over).(map[string]any)\n\n\tif result[\"a\"] != 1 {\n\t\tt.Errorf(\"Skip should preserve base: got a=%v\", result[\"a\"])\n\t}\n\tif result[\"b\"] != 3 {\n\t\tt.Errorf(\"non-Skip should overwrite: got b=%v\", result[\"b\"])\n\t}\n}\n\nfunc TestSkipInDeepMergeArray(t *testing.T) {\n\tbase := []any{\"x\", \"y\", \"z\"}\n\tover := []any{Skip, Skip, \"w\"}\n\tresult := Deep(base, over).([]any)\n\n\tif result[0] != \"x\" || result[1] != \"y\" || result[2] != \"w\" {\n\t\tt.Errorf(\"expected [x y w], got %v\", result)\n\t}\n}\n\n// --- ResolveFuncRefs ---\n\nfunc TestResolveFuncRefsAtEscape(t *testing.T) {\n\tresult := ResolveFuncRefs(\"@@myTag\", nil)\n\tif result != \"@myTag\" {\n\t\tt.Errorf(\"expected @myTag, got %v\", result)\n\t}\n}\n\nfunc TestResolveFuncRefsAtSkip(t *testing.T) {\n\tresult := ResolveFuncRefs(\"@SKIP\", nil)\n\tif !IsSkip(result) {\n\t\tt.Errorf(\"expected Skip sentinel, got %v\", result)\n\t}\n}\n\nfunc TestResolveFuncRefsRegex(t *testing.T) {\n\tresult := ResolveFuncRefs(\"@/^foo$/i\", nil)\n\tre, ok := result.(*regexp.Regexp)\n\tif !ok {\n\t\tt.Fatalf(\"expected *regexp.Regexp, got %T\", result)\n\t}\n\tif !re.MatchString(\"FOO\") {\n\t\tt.Error(\"regex should match FOO (case insensitive)\")\n\t}\n\tif re.MatchString(\"bar\") {\n\t\tt.Error(\"regex should not match bar\")\n\t}\n}\n\nfunc TestResolveFuncRefsRegexNoFlags(t *testing.T) {\n\tresult := ResolveFuncRefs(\"@/^test$/\", nil)\n\tre, ok := result.(*regexp.Regexp)\n\tif !ok {\n\t\tt.Fatalf(\"expected *regexp.Regexp, got %T\", result)\n\t}\n\tif !re.MatchString(\"test\") {\n\t\tt.Error(\"regex should match test\")\n\t}\n\tif re.MatchString(\"TEST\") {\n\t\tt.Error(\"regex without flags should not match TEST\")\n\t}\n}\n\nfunc TestResolveFuncRefsFuncLookup(t *testing.T) {\n\tref := map[FuncRef]any{\n\t\t\"@myFunc\": \"hello\",\n\t}\n\tresult := ResolveFuncRefs(\"@myFunc\", ref)\n\tif result != \"hello\" {\n\t\tt.Errorf(\"expected hello, got %v\", result)\n\t}\n}\n\nfunc TestResolveFuncRefsNestedMap(t *testing.T) {\n\tref := map[FuncRef]any{\n\t\t\"@fn\": \"resolved\",\n\t}\n\tinput := map[string]any{\n\t\t\"a\": \"@fn\",\n\t\t\"b\": \"@@literal\",\n\t\t\"c\": \"@SKIP\",\n\t\t\"d\": map[string]any{\"nested\": \"@fn\"},\n\t}\n\tresult := ResolveFuncRefs(input, ref).(map[string]any)\n\n\tif result[\"a\"] != \"resolved\" {\n\t\tt.Errorf(\"a: expected resolved, got %v\", result[\"a\"])\n\t}\n\tif result[\"b\"] != \"@literal\" {\n\t\tt.Errorf(\"b: expected @literal, got %v\", result[\"b\"])\n\t}\n\tif !IsSkip(result[\"c\"]) {\n\t\tt.Errorf(\"c: expected Skip, got %v\", result[\"c\"])\n\t}\n\tnested := result[\"d\"].(map[string]any)\n\tif nested[\"nested\"] != \"resolved\" {\n\t\tt.Errorf(\"d.nested: expected resolved, got %v\", nested[\"nested\"])\n\t}\n}\n\nfunc TestResolveFuncRefsArray(t *testing.T) {\n\tref := map[FuncRef]any{\"@fn\": 42}\n\tinput := []any{\"@fn\", \"@SKIP\", \"@@at\"}\n\tresult := ResolveFuncRefs(input, ref).([]any)\n\n\tif result[0] != 42 {\n\t\tt.Errorf(\"[0]: expected 42, got %v\", result[0])\n\t}\n\tif !IsSkip(result[1]) {\n\t\tt.Errorf(\"[1]: expected Skip, got %v\", result[1])\n\t}\n\tif result[2] != \"@at\" {\n\t\tt.Errorf(\"[2]: expected @at, got %v\", result[2])\n\t}\n}\n\n// --- Grammar() method: options ---\n\nfunc TestGrammarOptionsValueDef(t *testing.T) {\n\tj := Make()\n\tyes := true\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tValue: &ValueOptions{\n\t\t\t\tLex: &yes,\n\t\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\t\"yes\": {Val: true},\n\t\t\t\t\t\"no\":  {Val: false},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:yes,b:no,c:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true || m[\"b\"] != false {\n\t\tt.Errorf(\"expected a:true, b:false, got %v\", m)\n\t}\n}\n\nfunc TestGrammarOptionsNumberHex(t *testing.T) {\n\tj := Make()\n\tyes := true\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tNumber: &NumberOptions{Hex: &yes},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:0xFF\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(255) {\n\t\tt.Errorf(\"expected a:255, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarOptionsNumberSep(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tNumber: &NumberOptions{Sep: \"_\"},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000, got %v\", m[\"a\"])\n\t}\n}\n\n// --- Grammar() method: rules ---\n\nfunc TestGrammarRuleConditionFuncRef(t *testing.T) {\n\tj := Make()\n\tcondCalls := 0\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@topOnly\": AltCond(func(r *Rule, ctx *Context) bool {\n\t\t\t\tcondCalls++\n\t\t\t\treturn r.D == 0\n\t\t\t}),\n\t\t\t\"@wrapArr\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = []any{r.Node}\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{C: \"@topOnly\", A: \"@wrapArr\", G: \"custom\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tarr, ok := result.([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected []any, got %T: %v\", result, result)\n\t}\n\tinner, ok := arr[0].(map[string]any)\n\tif !ok || inner[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected [{a:1}], got %v\", result)\n\t}\n\tif condCalls == 0 {\n\t\tt.Error(\"condition function was not called\")\n\t}\n}\n\nfunc TestGrammarRuleConditionFalseSkips(t *testing.T) {\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@never\": AltCond(func(r *Rule, ctx *Context) bool {\n\t\t\t\treturn false\n\t\t\t}),\n\t\t\t\"@boom\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tpanic(\"should not fire\")\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{C: \"@never\", A: \"@boom\", G: \"custom\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\t// The @boom action never fires because @never blocks the alt.\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected {a:1}, got %v\", result)\n\t}\n}\n\nfunc TestGrammarOptionsAndRulesCombined(t *testing.T) {\n\tj := Make()\n\tyes := true\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@upper\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tif s, ok := r.Node.(string); ok {\n\t\t\t\t\tr.Node = s + \"!\"\n\t\t\t\t}\n\t\t\t}),\n\t\t},\n\t\tOptions: &Options{\n\t\t\tValue: &ValueOptions{\n\t\t\t\tLex: &yes,\n\t\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\t\"on\":  {Val: true},\n\t\t\t\t\t\"off\": {Val: false},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{A: \"@upper\", G: \"custom\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:on\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarMultipleCalls(t *testing.T) {\n\tj := Make()\n\tyes := true\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tValue: &ValueOptions{\n\t\t\t\tLex: &yes,\n\t\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\t\"yes\": {Val: true},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tValue: &ValueOptions{\n\t\t\t\tLex: &yes,\n\t\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\t\"no\": {Val: false},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:yes,b:no\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true || m[\"b\"] != false {\n\t\tt.Errorf(\"expected a:true, b:false, got %v\", m)\n\t}\n}\n\nfunc TestGrammarOptionsOnly(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tNumber: &NumberOptions{Sep: \"_\"},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected 1000, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarRulesOnly(t *testing.T) {\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@tag\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tif s, ok := r.Node.(string); ok {\n\t\t\t\t\tr.Node = \"<\" + s + \">\"\n\t\t\t\t}\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{A: \"@tag\", G: \"custom\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:hello\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"<hello>\" {\n\t\tt.Errorf(\"expected <hello>, got %v\", m[\"a\"])\n\t}\n}\n\n// --- Token string resolution ---\n\nfunc TestResolveTokenSpecStatic(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\twant  [][]Tin\n\t}{\n\t\t{\"#OB\", [][]Tin{{TinOB}}},\n\t\t{\"#ZZ\", [][]Tin{{TinZZ}}},\n\t\t{\"#OB #CB\", [][]Tin{{TinOB}, {TinCB}}},\n\t\t{\"#KEY #CL\", [][]Tin{TinSetKEY, {TinCL}}},\n\t\t{\"#VAL\", [][]Tin{TinSetVAL}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tgot := resolveTokenSpecStatic(tt.input)\n\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\tt.Errorf(\"resolveTokenSpecStatic(%q) = %v, want %v\", tt.input, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestResolveTokenFieldStaticSlice(t *testing.T) {\n\t// []string form: each element is a slot, space-separated names are alternatives.\n\ttests := []struct {\n\t\tinput []string\n\t\twant  [][]Tin\n\t}{\n\t\t// Single slot with two alternatives: CB or CS\n\t\t{[]string{\"#CB #CS\"}, [][]Tin{{TinCB, TinCS}}},\n\t\t// Two slots: CA in slot 0, CS or ZZ in slot 1\n\t\t{[]string{\"#CA\", \"#CS #ZZ\"}, [][]Tin{{TinCA}, {TinCS, TinZZ}}},\n\t\t// Single slot with token set + individual tokens\n\t\t{[]string{\"#CA #CS #VAL\"}, [][]Tin{{TinCA, TinCS, TinTX, TinNR, TinST, TinVL}}},\n\t\t// Single token in single slot (equivalent to string form)\n\t\t{[]string{\"#OB\"}, [][]Tin{{TinOB}}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tgot := resolveTokenFieldStatic(tt.input)\n\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\tt.Errorf(\"resolveTokenFieldStatic(%v) = %v, want %v\", tt.input, got, tt.want)\n\t\t}\n\t}\n}\n\n// --- State action wiring ---\n\nfunc TestGrammarStateActionWiring(t *testing.T) {\n\tj := Make()\n\tboCalled := false\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@val-bo\": StateAction(func(r *Rule, ctx *Context) {\n\t\t\t\tboCalled = true\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {}, // Trigger rule processing to wire @val-bo\n\t\t},\n\t})\n\n\t_, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !boCalled {\n\t\tt.Error(\"@val-bo state action was not called\")\n\t}\n}\n\n// --- Declarative conditions in grammar ---\n\nfunc TestGrammarDeclarativeCondition(t *testing.T) {\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@mark\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = \"marked\"\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t// Only fire at depth 0 using declarative condition.\n\t\t\t\t\t{C: map[string]any{\"d\": 0}, A: \"@mark\", G: \"custom\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"hello\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif result != \"marked\" {\n\t\tt.Errorf(\"expected marked, got %v\", result)\n\t}\n}\n\n// --- Fixed tokens via Grammar ---\n\nfunc TestGrammarFixedToken(t *testing.T) {\n\tj := Make()\n\n\t// Register a custom fixed token via the instance API.\n\tarrow := j.Token(\"#ARROW\", \"=>\")\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@arrowAction\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = \"<arrow>\"\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tOpen: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ARROW\", A: \"@arrowAction\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\t_ = arrow // token registered above\n\n\tresult, err := j.Parse(\"a:=>\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"<arrow>\" {\n\t\tt.Errorf(\"expected <arrow>, got %v\", m[\"a\"])\n\t}\n}\n\n// --- Parity fix: missing FuncRef returns error ---\n\nfunc TestGrammarMissingFuncRefReturnsError(t *testing.T) {\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRef: map[FuncRef]any{},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{A: \"@missing\", G: \"custom\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"expected error for missing FuncRef, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"@missing\") {\n\t\tt.Errorf(\"error should mention @missing, got: %s\", err)\n\t}\n}\n\n// --- Parity fix: inject modifiers ---\n\nfunc TestGrammarInjectAppend(t *testing.T) {\n\t// With Append: true, new alts go after existing ones.\n\tj := Make()\n\torigCloseLen := len(j.RSM()[\"val\"].Close)\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@noop\": AltAction(func(r *Rule, ctx *Context) {}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: &GrammarAltListSpec{\n\t\t\t\t\tAlts: []*GrammarAltSpec{\n\t\t\t\t\t\t{S: \"#ZZ\", A: \"@noop\", G: \"appended\"},\n\t\t\t\t\t},\n\t\t\t\t\tInject: &GrammarInjectSpec{Append: true},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tvalClose := j.RSM()[\"val\"].Close\n\t// The appended alt should be at the end.\n\tlast := valClose[len(valClose)-1]\n\tif last.G != \"appended\" {\n\t\tt.Errorf(\"expected last alt group=appended, got %q\", last.G)\n\t}\n\tif len(valClose) != origCloseLen+1 {\n\t\tt.Errorf(\"expected %d close alts, got %d\", origCloseLen+1, len(valClose))\n\t}\n}\n\nfunc TestGrammarInjectPrepend(t *testing.T) {\n\t// Default (no inject or Append:false) prepends new alts before existing ones.\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@noop\": AltAction(func(r *Rule, ctx *Context) {}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", A: \"@noop\", G: \"first\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@noop2\": AltAction(func(r *Rule, ctx *Context) {}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", A: \"@noop2\", G: \"second\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tvalClose := j.RSM()[\"val\"].Close\n\t// Second prepend goes before first prepend.\n\tif valClose[0].G != \"second\" {\n\t\tt.Errorf(\"expected first alt group=second, got %q\", valClose[0].G)\n\t}\n\tif valClose[1].G != \"first\" {\n\t\tt.Errorf(\"expected second alt group=first, got %q\", valClose[1].G)\n\t}\n}\n\n// --- Parity fix: OptionsMap with FuncRef resolution ---\n\nfunc TestGrammarOptionsMapMerge(t *testing.T) {\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@addMerge\": func(prev, curr any, r *Rule, ctx *Context) any {\n\t\t\t\tpf, pok := prev.(float64)\n\t\t\t\tcf, cok := curr.(float64)\n\t\t\t\tif pok && cok {\n\t\t\t\t\treturn pf + cf\n\t\t\t\t}\n\t\t\t\treturn curr\n\t\t\t},\n\t\t},\n\t\tOptionsMap: map[string]any{\n\t\t\t\"map\": map[string]any{\n\t\t\t\t\"merge\": \"@addMerge\",\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1,a:2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(3) {\n\t\tt.Errorf(\"expected a:3, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarOptionsMapValueDef(t *testing.T) {\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"value\": map[string]any{\n\t\t\t\t\"lex\": true,\n\t\t\t\t\"def\": map[string]any{\n\t\t\t\t\t\"yes\": map[string]any{\"val\": true},\n\t\t\t\t\t\"no\":  map[string]any{\"val\": false},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:yes,b:no\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true || m[\"b\"] != false {\n\t\tt.Errorf(\"expected a:true b:false, got %v\", m)\n\t}\n}\n\nfunc TestGrammarOptionsMapSkip(t *testing.T) {\n\tj := Make()\n\n\t// First: set tag\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"tag\": \"original\",\n\t\t},\n\t})\n\tif j.Options().Tag != \"original\" {\n\t\tt.Fatalf(\"expected tag=original, got %v\", j.Options().Tag)\n\t}\n\n\t// Second: @SKIP preserves existing tag\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"tag\": \"@SKIP\",\n\t\t},\n\t})\n\tif j.Options().Tag != \"original\" {\n\t\tt.Errorf(\"expected tag=original (preserved by @SKIP), got %v\", j.Options().Tag)\n\t}\n}\n\nfunc TestGrammarOptionsMapAtEscape(t *testing.T) {\n\tj := Make()\n\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"tag\": \"@@literal-at\",\n\t\t},\n\t})\n\tif j.Options().Tag != \"@literal-at\" {\n\t\tt.Errorf(\"expected @literal-at, got %v\", j.Options().Tag)\n\t}\n}\n\n// --- SetOptions preserves rule modifications (clone/inherit parity) ---\n\nfunc TestSetOptionsPreservesRuleModifications(t *testing.T) {\n\t// In TS, j.rule() then j.options() preserves the rule modification.\n\t// This test verifies Go matches that behavior.\n\tj := Make()\n\n\t// Add a custom close alt to val via Rule().\n\ttagged := false\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Close = append([]*AltSpec{{\n\t\t\tA: func(r *Rule, ctx *Context) { tagged = true },\n\t\t\tG: \"custom-tag\",\n\t\t}}, rs.Close...)\n\t})\n\n\t// Now call SetOptions — this must NOT destroy the rule modification.\n\tyes := true\n\tj.SetOptions(Options{\n\t\tNumber: &NumberOptions{Hex: &yes},\n\t})\n\n\t// The custom alt should still be in val.Close.\n\tvalClose := j.RSM()[\"val\"].Close\n\tfound := false\n\tfor _, alt := range valClose {\n\t\tif alt.G == \"custom-tag\" {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\tt.Error(\"rule modification lost after SetOptions — val.Close missing custom-tag alt\")\n\t}\n\n\t// Parse should work and trigger the custom action.\n\t_, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !tagged {\n\t\tt.Error(\"custom action did not fire after SetOptions\")\n\t}\n}\n\nfunc TestSetOptionsPreservesGrammarModifications(t *testing.T) {\n\t// Grammar() then SetOptions() should preserve the grammar modifications.\n\tj := Make()\n\n\tmarked := false\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@mark\": AltAction(func(r *Rule, ctx *Context) {\n\t\t\t\tmarked = true\n\t\t\t}),\n\t\t},\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{A: \"@mark\", G: \"grammar-mod\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\t// SetOptions after Grammar should NOT destroy the grammar modification.\n\tsep := \"_\"\n\tj.SetOptions(Options{\n\t\tNumber: &NumberOptions{Sep: sep},\n\t})\n\n\t_, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !marked {\n\t\tt.Error(\"grammar modification lost after SetOptions\")\n\t}\n}\n\nfunc TestRuleThenOptionsThenParse(t *testing.T) {\n\t// The exact pattern from the user report: Rule() before Options().\n\tj := Make()\n\n\tcustomVal := \"\"\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Close = append([]*AltSpec{{\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tif s, ok := r.Node.(string); ok {\n\t\t\t\t\tcustomVal = s\n\t\t\t\t}\n\t\t\t},\n\t\t\tG: \"plugin-mod\",\n\t\t}}, rs.Close...)\n\t})\n\n\t// Options change after rule modification — must not lose the modification.\n\tyes := true\n\tj.SetOptions(Options{\n\t\tValue: &ValueOptions{\n\t\t\tLex: &yes,\n\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\"yes\": {Val: true},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:hello\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"hello\" {\n\t\tt.Errorf(\"expected a:hello, got %v\", m)\n\t}\n\tif customVal != \"hello\" {\n\t\tt.Errorf(\"custom rule action should have captured hello, got %q\", customVal)\n\t}\n}\n\n// === Regexp support tests (parity with TS grammar.test.js) ===\n\n// TestGrammarRegexNumberExclude mirrors TS \"options-regex-number-exclude\".\n// Uses @/…/ in OptionsMap to specify a RegExp for number.exclude.\n// Uses ^0[0-9]+$ which correctly matches leading-zero numbers like \"01\".\nfunc TestGrammarRegexNumberExclude(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"number\": map[string]any{\n\t\t\t\t\"exclude\": \"@/^0[0-9]+$/\",\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(0) {\n\t\tt.Errorf(\"expected a:0, got %v\", m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:01\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != \"01\" {\n\t\tt.Errorf(\"expected a:'01' (text), got %v (%T)\", m[\"a\"], m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:123\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != float64(123) {\n\t\tt.Errorf(\"expected a:123, got %v\", m[\"a\"])\n\t}\n}\n\n// TestGrammarRegexNumberExcludeTyped tests number.exclude with a *regexp.Regexp\n// via the typed Options API (not OptionsMap).\nfunc TestGrammarRegexNumberExcludeTyped(t *testing.T) {\n\tre := regexp.MustCompile(`^0[0-9]+$`)\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tNumber: &NumberOptions{\n\t\t\t\tExclude: func(s string) bool {\n\t\t\t\t\treturn re.MatchString(s)\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:01\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"01\" {\n\t\tt.Errorf(\"expected a:'01' (text), got %v (%T)\", m[\"a\"], m[\"a\"])\n\t}\n}\n\n// TestGrammarRegexValueMatch mirrors TS \"options-regex-value-match\".\n// Uses @/…/ in value.def with a FuncRef val for regex-matched values.\nfunc TestGrammarRegexValueMatch(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@valOn\":  func(match []string) any { return true },\n\t\t\t\"@valOff\": func(match []string) any { return false },\n\t\t},\n\t\tOptionsMap: map[string]any{\n\t\t\t\"value\": map[string]any{\n\t\t\t\t\"def\": map[string]any{\n\t\t\t\t\t\"on\":  map[string]any{\"val\": \"@valOn\", \"match\": \"@/^on$/i\"},\n\t\t\t\t\t\"off\": map[string]any{\"val\": \"@valOff\", \"match\": \"@/^off$/i\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:ON,b:Off,c:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v\", m[\"b\"])\n\t}\n\tif m[\"c\"] != float64(1) {\n\t\tt.Errorf(\"expected c:1, got %v\", m[\"c\"])\n\t}\n\n\tresult, err = j.Parse(\"a:on,b:OFF\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v\", m[\"b\"])\n\t}\n}\n\n// TestGrammarRegexValueMatchTyped tests value.def Match via typed API.\nfunc TestGrammarRegexValueMatchTyped(t *testing.T) {\n\tj := Make()\n\tlex := true\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tValue: &ValueOptions{\n\t\t\t\tLex: &lex,\n\t\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\t\"on\": {\n\t\t\t\t\t\tMatch:   regexp.MustCompile(`(?i)^on$`),\n\t\t\t\t\t\tValFunc: func(m []string) any { return true },\n\t\t\t\t\t},\n\t\t\t\t\t\"off\": {\n\t\t\t\t\t\tMatch:   regexp.MustCompile(`(?i)^off$`),\n\t\t\t\t\t\tValFunc: func(m []string) any { return false },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:ON,b:Off\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v\", m[\"b\"])\n\t}\n}\n\n// TestGrammarRegexWithFlags mirrors TS \"options-regex-with-flags\".\nfunc TestGrammarRegexWithFlags(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@valYes\": func(match []string) any { return \"YES!\" },\n\t\t},\n\t\tOptionsMap: map[string]any{\n\t\t\t\"value\": map[string]any{\n\t\t\t\t\"def\": map[string]any{\n\t\t\t\t\t\"yes\": map[string]any{\"val\": \"@valYes\", \"match\": \"@/^yes$/i\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\t// The /i flag makes it case-insensitive.\n\tresult, err := j.Parse(\"a:YES\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"YES!\" {\n\t\tt.Errorf(\"expected a:YES!, got %v\", m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:Yes\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != \"YES!\" {\n\t\tt.Errorf(\"expected a:YES!, got %v\", m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:yes\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != \"YES!\" {\n\t\tt.Errorf(\"expected a:YES!, got %v\", m[\"a\"])\n\t}\n}\n\n// TestGrammarRegexNoFlags mirrors TS \"options-regex-no-flags\".\nfunc TestGrammarRegexNoFlags(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"number\": map[string]any{\n\t\t\t\t\"exclude\": \"@/^0[0-9]+$/\",\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(0) {\n\t\tt.Errorf(\"expected a:0, got %v\", m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:42\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != float64(42) {\n\t\tt.Errorf(\"expected a:42, got %v\", m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:01\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != \"01\" {\n\t\tt.Errorf(\"expected a:'01' (text), got %v (%T)\", m[\"a\"], m[\"a\"])\n\t}\n}\n\n// TestGrammarRegexMixedWithFuncref mirrors TS \"options-regex-mixed-with-funcref\".\nfunc TestGrammarRegexMixedWithFuncref(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@prepend\": func(prev, curr any, r *Rule, ctx *Context) any {\n\t\t\t\tps, pok := prev.(string)\n\t\t\t\tcs, cok := curr.(string)\n\t\t\t\tif pok && cok {\n\t\t\t\t\treturn ps + cs\n\t\t\t\t}\n\t\t\t\treturn curr\n\t\t\t},\n\t\t},\n\t\tOptionsMap: map[string]any{\n\t\t\t\"map\": map[string]any{\n\t\t\t\t\"merge\": \"@prepend\",\n\t\t\t},\n\t\t\t\"number\": map[string]any{\n\t\t\t\t\"exclude\": \"@/^0[0-9]+$/\",\n\t\t\t},\n\t\t},\n\t})\n\n\t// FuncRef merge: duplicate keys concatenate.\n\tresult, err := j.Parse(\"a:x,a:y\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"xy\" {\n\t\tt.Errorf(\"expected a:xy, got %v\", m[\"a\"])\n\t}\n\n\t// RegExp exclude: leading-zero numbers become text.\n\tresult, err = j.Parse(\"a:007\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != \"007\" {\n\t\tt.Errorf(\"expected a:'007' (text), got %v (%T)\", m[\"a\"], m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"a:42\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != float64(42) {\n\t\tt.Errorf(\"expected a:42, got %v\", m[\"a\"])\n\t}\n}\n\n// TestGrammarRegexInArray mirrors TS \"options-regex-in-array\".\n// @/…/ resolution should work inside arrays (ResolveFuncRefs recurses into slices).\nfunc TestGrammarRegexInArray(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@valT\": func(match []string) any { return true },\n\t\t\t\"@valF\": func(match []string) any { return false },\n\t\t},\n\t\tOptionsMap: map[string]any{\n\t\t\t\"value\": map[string]any{\n\t\t\t\t\"def\": map[string]any{\n\t\t\t\t\t\"t\": map[string]any{\"val\": \"@valT\", \"match\": \"@/^t$/i\"},\n\t\t\t\t\t\"f\": map[string]any{\"val\": \"@valF\", \"match\": \"@/^f$/i\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"[T, F, 1]\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tarr := result.([]any)\n\tif len(arr) != 3 {\n\t\tt.Fatalf(\"expected 3 elements, got %d\", len(arr))\n\t}\n\tif arr[0] != true {\n\t\tt.Errorf(\"expected [0]=true, got %v\", arr[0])\n\t}\n\tif arr[1] != false {\n\t\tt.Errorf(\"expected [1]=false, got %v\", arr[1])\n\t}\n\tif arr[2] != float64(1) {\n\t\tt.Errorf(\"expected [2]=1, got %v\", arr[2])\n\t}\n}\n\n// TestGrammarRegexEscapeAtPrefix mirrors TS \"options-escape-at-regex-like\".\n// @@ prevents @/…/ from being interpreted as a regex.\nfunc TestGrammarRegexEscapeAtPrefix(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"tag\": \"@@/not-a-regex/\",\n\t\t},\n\t})\n\n\tif j.Options().Tag != \"@/not-a-regex/\" {\n\t\tt.Errorf(\"expected @/not-a-regex/, got %v\", j.Options().Tag)\n\t}\n}\n\n// TestGrammarRegexMatchToken mirrors TS \"options-regex-match-token\".\n// Uses @/…/ to specify a RegExp for a custom match token.\nfunc TestGrammarRegexMatchToken(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptionsMap: map[string]any{\n\t\t\t\"match\": map[string]any{\n\t\t\t\t\"token\": map[string]any{\n\t\t\t\t\t\"#ID\": \"@/^[a-zA-Z_][a-zA-Z_0-9]*/\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"tokenSet\": map[string]any{\n\t\t\t\t\"KEY\": []any{\"#ST\", \"#ID\"},\n\t\t\t\t\"VAL\": []any{\"#TX\", \"#NR\", \"#ST\", \"#VL\", \"#ID\"},\n\t\t\t},\n\t\t},\n\t})\n\n\t// 'a' matches #ID and #ID is in KEY, so a:1 parses as a pair.\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n\n\tresult, err = j.Parse(\"foo:bar\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected foo:bar, got %v\", m[\"foo\"])\n\t}\n}\n\n// TestGrammarRegexMatchTokenTyped tests match.token via typed Options API.\nfunc TestGrammarRegexMatchTokenTyped(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tMatch: &MatchOptions{\n\t\t\t\tToken: map[string]*regexp.Regexp{\n\t\t\t\t\t\"#ID\": regexp.MustCompile(`^[a-zA-Z_][a-zA-Z_0-9]*`),\n\t\t\t\t},\n\t\t\t},\n\t\t\tTokenSet: map[string][]string{\n\t\t\t\t\"KEY\": {\"#ST\", \"#ID\"},\n\t\t\t\t\"VAL\": {\"#TX\", \"#NR\", \"#ST\", \"#VL\", \"#ID\"},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n}\n\n// TestGrammarRegexMatchValue tests match.value via OptionsMap (declarative form).\n// Note: match.value runs against the forward source (remaining input), so regexps\n// should NOT use $ anchor. Use value.def[name].match for extracted-text matching.\nfunc TestGrammarRegexMatchValue(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tRef: map[FuncRef]any{\n\t\t\t\"@valOn\":  func(match []string) any { return true },\n\t\t\t\"@valOff\": func(match []string) any { return false },\n\t\t},\n\t\tOptionsMap: map[string]any{\n\t\t\t\"match\": map[string]any{\n\t\t\t\t\"value\": map[string]any{\n\t\t\t\t\t// No $ anchor — matches against forward source, not extracted text.\n\t\t\t\t\t\"on\":  map[string]any{\"match\": \"@/^on/i\", \"val\": \"@valOn\"},\n\t\t\t\t\t\"off\": map[string]any{\"match\": \"@/^off/i\", \"val\": \"@valOff\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:ON,b:off\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v\", m[\"b\"])\n\t}\n}\n\n// TestGrammarRegexMatchValueTyped tests match.value via typed Options API.\nfunc TestGrammarRegexMatchValueTyped(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tMatch: &MatchOptions{\n\t\t\t\tValue: map[string]*MatchValueSpec{\n\t\t\t\t\t\"on\": {\n\t\t\t\t\t\tMatch: regexp.MustCompile(`(?i)^on`),\n\t\t\t\t\t\tVal:   func(m []string) any { return true },\n\t\t\t\t\t},\n\t\t\t\t\t\"off\": {\n\t\t\t\t\t\tMatch: regexp.MustCompile(`(?i)^off`),\n\t\t\t\t\t\tVal:   func(m []string) any { return false },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:ON,b:off\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v\", m[\"b\"])\n\t}\n}\n\n// TestResolveFuncRefsRegexInNestedMap verifies @/…/ resolution in nested structures.\nfunc TestResolveFuncRefsRegexInNestedMap(t *testing.T) {\n\tinput := map[string]any{\n\t\t\"number\": map[string]any{\n\t\t\t\"exclude\": \"@/^0[0-9]+/\",\n\t\t},\n\t\t\"value\": map[string]any{\n\t\t\t\"def\": map[string]any{\n\t\t\t\t\"on\": map[string]any{\n\t\t\t\t\t\"match\": \"@/^on$/i\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tresult := ResolveFuncRefs(input, nil).(map[string]any)\n\n\tnum := result[\"number\"].(map[string]any)\n\tif _, ok := num[\"exclude\"].(*regexp.Regexp); !ok {\n\t\tt.Errorf(\"number.exclude should be *regexp.Regexp, got %T\", num[\"exclude\"])\n\t}\n\n\tval := result[\"value\"].(map[string]any)\n\tdef := val[\"def\"].(map[string]any)\n\ton := def[\"on\"].(map[string]any)\n\tif _, ok := on[\"match\"].(*regexp.Regexp); !ok {\n\t\tt.Errorf(\"value.def.on.match should be *regexp.Regexp, got %T\", on[\"match\"])\n\t}\n}\n\n// TestResolveFuncRefsRegexInSlice verifies @/…/ resolution inside slices.\nfunc TestResolveFuncRefsRegexInSlice(t *testing.T) {\n\tinput := []any{\"@/^test$/i\", \"@SKIP\", \"@@at\"}\n\tresult := ResolveFuncRefs(input, nil).([]any)\n\n\tif _, ok := result[0].(*regexp.Regexp); !ok {\n\t\tt.Errorf(\"[0] should be *regexp.Regexp, got %T\", result[0])\n\t}\n\tre := result[0].(*regexp.Regexp)\n\tif !re.MatchString(\"TEST\") {\n\t\tt.Error(\"regex should match TEST (case insensitive)\")\n\t}\n\tif !IsSkip(result[1]) {\n\t\tt.Errorf(\"[1] should be Skip, got %v\", result[1])\n\t}\n\tif result[2] != \"@at\" {\n\t\tt.Errorf(\"[2] expected @at, got %v\", result[2])\n\t}\n}\n\n// TestMatchTokenNilRegexpNoPanic verifies that a nil *regexp.Regexp entry\n// in Match.Token is skipped during parsing instead of causing a panic.\nfunc TestMatchTokenNilRegexpNoPanic(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tMatch: &MatchOptions{\n\t\t\t\tToken: map[string]*regexp.Regexp{\n\t\t\t\t\t\"#ID\": nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTokenSet: map[string][]string{\n\t\t\t\t\"KEY\": {\"#ST\", \"#ID\"},\n\t\t\t\t\"VAL\": {\"#TX\", \"#NR\", \"#ST\", \"#VL\", \"#ID\"},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n}\n\n// --- GrammarText: string grammar ---\n\nfunc TestGrammarTextNumberSep(t *testing.T) {\n\tj := Make()\n\terr := j.GrammarText(`options: { number: { sep: \"_\" } }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarTextNumberExclude(t *testing.T) {\n\tj := Make()\n\terr := j.GrammarText(`options: { number: { exclude: '@/^0[0-9]+$/' } }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresult, err := j.Parse(\"a:01\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"01\" {\n\t\tt.Errorf(\"expected a:'01' (text), got %v (%T)\", m[\"a\"], m[\"a\"])\n\t}\n}\n\nfunc TestGrammarTextFlatOptions(t *testing.T) {\n\t// When there's no \"options\" wrapper, the entire parsed map is treated as options.\n\tj := Make()\n\terr := j.GrammarText(`number: { sep: \"_\" }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarTextOptionsAndRules(t *testing.T) {\n\t// GrammarText processes both options and rule definitions from text.\n\tj := Make()\n\terr := j.GrammarText(`\n\t\toptions: { number: { sep: \"_\" } },\n\t\trule: {\n\t\t\tval: {\n\t\t\t\tclose: [\n\t\t\t\t\t{ s: \"#ZZ\", g: \"test,jsonic\" }\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Options took effect.\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarTextRulesWithFuncRef(t *testing.T) {\n\t// GrammarText can define rules with @funcRef actions when Ref is\n\t// provided separately via Grammar after GrammarText sets up the structure.\n\tj := Make()\n\n\t// First apply rules via text with a group tag.\n\terr := j.GrammarText(`\n\t\trule: {\n\t\t\tval: {\n\t\t\t\tclose: [\n\t\t\t\t\t{ s: \"#ZZ\", g: \"custom-from-text\" }\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Verify the rule alt was added.\n\tfound := false\n\tfor _, alt := range j.RSM()[\"val\"].Close {\n\t\tif alt.G == \"custom-from-text\" {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\tt.Error(\"expected rule alt with group 'custom-from-text'\")\n\t}\n}\n\nfunc TestGrammarTextWithInjectAndExclude(t *testing.T) {\n\t// Regression: GrammarText with {alts, inject} form was not parsed,\n\t// so rule alts were silently dropped, breaking string matching when\n\t// combined with rule.exclude.\n\tj := Make()\n\terr := j.GrammarText(`\n\t\toptions: { text:{lex:false}, string:{chars:'\"'}, rule:{finish:false} },\n\t\trule: { val: { open: { alts:[{s:'#ZZ', g:jsonc}], inject:{append:true} } } }\n\t`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tj.SetOptions(Options{Rule: &RuleOptions{Exclude: \"jsonic,imp\"}})\n\n\t// Complete string should parse.\n\tresult, err := j.Parse(`\"test\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"complete string failed: %v\", err)\n\t}\n\tif result != \"test\" {\n\t\tt.Errorf(\"expected 'test', got %v\", result)\n\t}\n\n\t// Unterminated string should produce unterminated_string, not unexpected.\n\t_, err = j.Parse(`\"test`)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for unterminated string\")\n\t}\n\tif je, ok := err.(*JsonicError); ok {\n\t\tif je.Code != \"unterminated_string\" {\n\t\t\tt.Errorf(\"expected unterminated_string, got %s\", je.Code)\n\t\t}\n\t}\n\n\t// JSON structures should work.\n\tresult, err = j.Parse(`{\"a\":\"b\",\"c\":1}`)\n\tif err != nil {\n\t\tt.Fatalf(\"JSON object failed: %v\", err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != \"b\" || m[\"c\"] != float64(1) {\n\t\tt.Errorf(\"expected {a:b, c:1}, got %v\", m)\n\t}\n}\n\nfunc TestExcludeCommaTrailingComma(t *testing.T) {\n\t// With only \"comma\" excluded, trailing commas in lists should produce\n\t// clean output (no nil element), matching TS behavior.\n\tj := Make(Options{Rule: &RuleOptions{Exclude: \"comma\"}})\n\n\tresult, err := j.Parse(\"[1,2,]\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tarr := result.([]any)\n\tif len(arr) != 2 || arr[0] != float64(1) || arr[1] != float64(2) {\n\t\tt.Errorf(\"expected [1,2], got %v\", arr)\n\t}\n\n\t// Trailing comma in map should fail.\n\t_, err = j.Parse(`{\"a\":1,}`)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for trailing comma in map\")\n\t}\n\n\t// Normal cases still work.\n\tresult, err = j.Parse(`{\"a\":null}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != nil {\n\t\tt.Errorf(\"expected a:nil, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarTextThenSetOptionsPreserved(t *testing.T) {\n\t// GrammarText sets options and rules. A subsequent SetOptions must\n\t// preserve both the options (via deep merge) and the rule modifications.\n\tj := Make()\n\n\t// Apply options and a rule alt via GrammarText.\n\terr := j.GrammarText(`\n\t\toptions: { number: { sep: \"_\" } },\n\t\trule: {\n\t\t\tval: {\n\t\t\t\tclose: [\n\t\t\t\t\t{ s: \"#ZZ\", g: \"from-text\" }\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Now call SetOptions with an unrelated option change.\n\tyes := true\n\tj.SetOptions(Options{Number: &NumberOptions{Hex: &yes}})\n\n\t// Options from GrammarText should still be in effect (number.sep).\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000 (number.sep preserved), got %v\", m[\"a\"])\n\t}\n\n\t// Rule alt from GrammarText should still exist.\n\tfound := false\n\tfor _, alt := range j.RSM()[\"val\"].Close {\n\t\tif alt.G == \"from-text\" {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\tt.Error(\"rule alt 'from-text' lost after SetOptions\")\n\t}\n}\n\nfunc TestGrammarTextInvalidSource(t *testing.T) {\n\tj := Make()\n\t// Empty string should not error (jsonic allows empty source by default).\n\terr := j.GrammarText(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n// --- MapToOptions coverage for previously missing options ---\n\nfunc TestGrammarTextRuleExclude(t *testing.T) {\n\tj := Make()\n\terr := j.GrammarText(`options: { rule: { exclude: \"jsonic\" } }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Strict JSON should work.\n\tresult, err := j.Parse(`{\"a\":1}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n\t// Implicit map (jsonic extension) should fail.\n\t_, err = j.Parse(\"a:1\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for implicit map after excluding jsonic via GrammarText\")\n\t}\n}\n\nfunc TestGrammarTextTextLex(t *testing.T) {\n\tj := Make()\n\terr := j.GrammarText(`options: { text: { lex: false } }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Bare text should fail, but value keywords still work.\n\tresult, err := j.Parse(`{\"a\":true}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestGrammarTextLexEmpty(t *testing.T) {\n\tj := Make()\n\terr := j.GrammarText(`options: { lex: { empty: false } }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err = j.Parse(\"\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for empty source after disabling via GrammarText\")\n\t}\n}\n\nfunc TestMapToOptionsListChild(t *testing.T) {\n\t// list.child is a grammar-level option, so must be set at Make() time.\n\t// This tests that MapToOptions correctly parses the list options.\n\tj := Make(MapToOptions(map[string]any{\n\t\t\"list\": map[string]any{\"child\": true},\n\t}))\n\tresult, err := j.Parse(\"[:1,a,b]\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlr := result.(ListRef)\n\tif lr.Child != float64(1) {\n\t\tt.Errorf(\"expected child=1, got %v\", lr.Child)\n\t}\n}\n\nfunc TestMapToOptionsMapChild(t *testing.T) {\n\tj := Make(MapToOptions(map[string]any{\n\t\t\"map\": map[string]any{\"child\": true},\n\t}))\n\tresult, err := j.Parse(\"{:1,a:2}\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"child$\"] != float64(1) {\n\t\tt.Errorf(\"expected child$=1, got %v\", m[\"child$\"])\n\t}\n}\n\nfunc TestMapToOptionsEnder(t *testing.T) {\n\t// Verify MapToOptions parses the ender option correctly.\n\topts := MapToOptions(map[string]any{\"ender\": \";\"})\n\tif len(opts.Ender) != 1 || opts.Ender[0] != \";\" {\n\t\tt.Errorf(\"expected Ender=[;], got %v\", opts.Ender)\n\t}\n}\n\n// --- SetOptions applies lex.empty ---\n\nfunc TestSetOptionsLexEmpty(t *testing.T) {\n\t// lex.empty can be set via SetOptions after Make(), matching TS behavior.\n\tj := Make()\n\n\t// Default: empty source is allowed.\n\tresult, err := j.Parse(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"empty source should be allowed by default: %v\", err)\n\t}\n\tif result != nil {\n\t\tt.Errorf(\"expected nil for empty source, got %v\", result)\n\t}\n\n\t// Disable empty source via SetOptions.\n\tno := false\n\tj.SetOptions(Options{Lex: &LexOptions{Empty: &no}})\n\n\t_, err = j.Parse(\"\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for empty source after disabling lex.empty\")\n\t}\n\n\t// Re-enable via SetOptions.\n\tyes := true\n\tj.SetOptions(Options{Lex: &LexOptions{Empty: &yes}})\n\n\tresult, err = j.Parse(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"empty source should be allowed again: %v\", err)\n\t}\n\tif result != nil {\n\t\tt.Errorf(\"expected nil, got %v\", result)\n\t}\n}\n\n// --- text.lex=false should not disable value keywords ---\n\nfunc TestTextLexFalseValueKeywordsStillWork(t *testing.T) {\n\t// In TS, text.lex:false disables bare text tokens but value keywords\n\t// (true, false, null) still match. Go must behave the same way.\n\tno := false\n\tj := Make(Options{Text: &TextOptions{Lex: &no}})\n\n\t// Value keywords should still work.\n\tresult, err := j.Parse(`{\"a\":true,\"b\":false,\"c\":null}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v (%T)\", m[\"b\"], m[\"b\"])\n\t}\n\tif m[\"c\"] != nil {\n\t\tt.Errorf(\"expected c:nil, got %v\", m[\"c\"])\n\t}\n\n\t// Bare text should NOT work (should error or not parse as text).\n\t_, err = j.Parse(\"hello\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for bare text when text.lex=false\")\n\t}\n}\n\nfunc TestTextLexFalseCustomValueDef(t *testing.T) {\n\t// Custom value.def keywords should also work with text.lex=false.\n\tno := false\n\tyes := true\n\tj := Make(Options{\n\t\tText: &TextOptions{Lex: &no},\n\t\tValue: &ValueOptions{\n\t\t\tLex: &yes,\n\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\"yes\": {Val: true},\n\t\t\t\t\"no\":  {Val: false},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(`{\"a\":yes,\"b\":no}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v\", m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v\", m[\"b\"])\n\t}\n}\n\nfunc TestGroupTagsValidFormat(t *testing.T) {\n\t// All group tags (G fields) in grammar alts must be comma-separated\n\t// lowercase identifiers [a-z] only. No dots, spaces, or other chars.\n\t// Regression guard for the \"elem.jsonic\" typo (TS grammar.ts:737).\n\tj := Make()\n\ttagRe := regexp.MustCompile(`^[a-z]+$`)\n\tfor name, rs := range j.RSM() {\n\t\tfor _, alt := range rs.Open {\n\t\t\tif alt.G != \"\" {\n\t\t\t\tfor _, tag := range strings.Split(alt.G, \",\") {\n\t\t\t\t\tif !tagRe.MatchString(tag) {\n\t\t\t\t\t\tt.Errorf(\"rule %s open: invalid group tag %q in G=%q \"+\n\t\t\t\t\t\t\t\"(must be comma-separated lowercase [a-z] identifiers)\",\n\t\t\t\t\t\t\tname, tag, alt.G)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, alt := range rs.Close {\n\t\t\tif alt.G != \"\" {\n\t\t\t\tfor _, tag := range strings.Split(alt.G, \",\") {\n\t\t\t\t\tif !tagRe.MatchString(tag) {\n\t\t\t\t\t\tt.Errorf(\"rule %s close: invalid group tag %q in G=%q \"+\n\t\t\t\t\t\t\t\"(must be comma-separated lowercase [a-z] identifiers)\",\n\t\t\t\t\t\t\tname, tag, alt.G)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestSetOptionsRuleExclude(t *testing.T) {\n\t// rule.exclude can be set via SetOptions after Make().\n\tj := Make()\n\n\t// Before exclude: jsonic extensions are active (implicit maps work).\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n\n\t// Exclude jsonic extensions: only strict JSON syntax works.\n\tj.SetOptions(Options{Rule: &RuleOptions{Exclude: \"jsonic\"}})\n\n\t// Strict JSON should still work.\n\tresult, err = j.Parse(`{\"a\":1}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm = result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n\n\t// Implicit map (jsonic extension) should now fail.\n\t_, err = j.Parse(\"a:1\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for implicit map after excluding jsonic\")\n\t}\n}\n\nfunc TestGrammarLexEmpty(t *testing.T) {\n\t// lex.empty can be set via Grammar, not just Make().\n\tj := Make()\n\tno := false\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{Lex: &LexOptions{Empty: &no}},\n\t})\n\n\t_, err := j.Parse(\"\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for empty source after Grammar sets lex.empty=false\")\n\t}\n}\n\n// --- Info marker key protection ---\n\nfunc TestInfoMarkerKeyDropped(t *testing.T) {\n\t// User keys matching the info marker are dropped when info.map is enabled.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\tresult, err := j.Parse(`a:1,__info__:2,b:3`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmr := result.(MapRef)\n\tif mr.Val[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", mr.Val[\"a\"])\n\t}\n\tif mr.Val[\"b\"] != float64(3) {\n\t\tt.Errorf(\"expected b:3, got %v\", mr.Val[\"b\"])\n\t}\n\tif _, exists := mr.Val[\"__info__\"]; exists {\n\t\tt.Error(\"__info__ key should have been dropped\")\n\t}\n}\n\nfunc TestInfoMarkerKeyDroppedJSON(t *testing.T) {\n\t// Also works in strict JSON syntax path.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\tresult, err := j.Parse(`{\"a\":1,\"__info__\":2}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmr := result.(MapRef)\n\tif _, exists := mr.Val[\"__info__\"]; exists {\n\t\tt.Error(\"__info__ key should have been dropped in JSON path\")\n\t}\n}\n\nfunc TestInfoMarkerKeyNotDroppedWhenOff(t *testing.T) {\n\t// When info.map is off, the key is NOT dropped.\n\tj := Make()\n\tresult, err := j.Parse(`a:1,__info__:2`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"__info__\"] != float64(2) {\n\t\tt.Errorf(\"expected __info__:2, got %v\", m[\"__info__\"])\n\t}\n}\n\n// TestValueDefReUsesValWhenValFuncNil verifies that regex-based value.def\n// entries return ValueDef.Val (not the matched source text) when ValFunc is nil.\nfunc TestValueDefReUsesValWhenValFuncNil(t *testing.T) {\n\tj := Make()\n\tlex := true\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tValue: &ValueOptions{\n\t\t\t\tLex: &lex,\n\t\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\t\"yes\": {\n\t\t\t\t\t\tVal:   true,\n\t\t\t\t\t\tMatch: regexp.MustCompile(`(?i)^yes$`),\n\t\t\t\t\t},\n\t\t\t\t\t\"no\": {\n\t\t\t\t\t\tVal:   false,\n\t\t\t\t\t\tMatch: regexp.MustCompile(`(?i)^no$`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:YES,b:No\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != true {\n\t\tt.Errorf(\"expected a:true, got %v (%T)\", m[\"a\"], m[\"a\"])\n\t}\n\tif m[\"b\"] != false {\n\t\tt.Errorf(\"expected b:false, got %v (%T)\", m[\"b\"], m[\"b\"])\n\t}\n}\n\n// TestMatchValueNilSpecNoPanic verifies that nil entries in Match.Value\n// are skipped rather than causing a nil-pointer panic in buildConfig.\nfunc TestMatchValueNilSpecNoPanic(t *testing.T) {\n\tj := Make()\n\tmustGrammar(t, j, &GrammarSpec{\n\t\tOptions: &Options{\n\t\t\tMatch: &MatchOptions{\n\t\t\t\tValue: map[string]*MatchValueSpec{\n\t\t\t\t\t\"x\": nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm := result.(map[string]any)\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n}\n"
  },
  {
    "path": "go/grammar_setting_test.go",
    "content": "package jsonic\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// --- Helpers ---\n\n// altGTags returns, for the named rule and state (\"open\"/\"close\"), a slice\n// of sorted tag slices — one per alt. Simplifies assertions that don't\n// care about tag ordering.\nfunc altGTags(t *testing.T, j *Jsonic, rulename, state string) [][]string {\n\tt.Helper()\n\trs, ok := j.RSM()[rulename]\n\tif !ok {\n\t\tt.Fatalf(\"rule %q not found\", rulename)\n\t}\n\tvar alts []*AltSpec\n\tswitch state {\n\tcase \"open\":\n\t\talts = rs.Open\n\tcase \"close\":\n\t\talts = rs.Close\n\tdefault:\n\t\tt.Fatalf(\"bad state %q\", state)\n\t}\n\tout := make([][]string, 0, len(alts))\n\tfor _, a := range alts {\n\t\tif a == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttags := splitGroupTags(a.G)\n\t\tsort.Strings(tags)\n\t\tout = append(out, tags)\n\t}\n\treturn out\n}\n\n// hasTags checks whether the alt list contains exactly one alt whose sorted\n// tag slice equals the provided expected slice.\nfunc containsTagSet(tagSets [][]string, want []string) bool {\n\tsort.Strings(want)\n\tfor _, ts := range tagSets {\n\t\tif len(ts) != len(want) {\n\t\t\tcontinue\n\t\t}\n\t\teq := true\n\t\tfor i := range ts {\n\t\t\tif ts[i] != want[i] {\n\t\t\t\teq = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif eq {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// --- Grammar setting: rule.alt.g append (typed) ---\n\nfunc TestGrammarSettingAltGStringAppended(t *testing.T) {\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"alpha\"},\n\t\t\t\t\t{S: \"#CA\", G: \"beta,gamma\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, &GrammarSetting{\n\t\tRule: &GrammarSettingRule{\n\t\t\tAlt: &GrammarSettingAlt{G: \"extraa,extrab\"},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttags := altGTags(t, j, \"val\", \"close\")\n\tif !containsTagSet(tags, []string{\"alpha\", \"extraa\", \"extrab\"}) {\n\t\tt.Errorf(\"missing alpha+extras, got %v\", tags)\n\t}\n\tif !containsTagSet(tags, []string{\"beta\", \"extraa\", \"extrab\", \"gamma\"}) {\n\t\tt.Errorf(\"missing beta/gamma+extras, got %v\", tags)\n\t}\n}\n\nfunc TestGrammarSettingAltGArrayAppended(t *testing.T) {\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{{S: \"#ZZ\", G: \"alpha\"}, {S: \"#CA\"}},\n\t\t\t},\n\t\t},\n\t}, &GrammarSetting{\n\t\tRule: &GrammarSettingRule{\n\t\t\tAlt: &GrammarSettingAlt{G: []string{\"p1\", \"p2\"}},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttags := altGTags(t, j, \"val\", \"close\")\n\tif !containsTagSet(tags, []string{\"alpha\", \"p1\", \"p2\"}) {\n\t\tt.Errorf(\"missing alpha+extras, got %v\", tags)\n\t}\n\tif !containsTagSet(tags, []string{\"p1\", \"p2\"}) {\n\t\tt.Errorf(\"missing pure-extras alt, got %v\", tags)\n\t}\n}\n\nfunc TestGrammarSettingNilIsNoop(t *testing.T) {\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{{S: \"#ZZ\", G: \"alpha,beta\"}},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttags := altGTags(t, j, \"val\", \"close\")\n\tif !containsTagSet(tags, []string{\"alpha\", \"beta\"}) {\n\t\tt.Errorf(\"nil setting mutated tags: %v\", tags)\n\t}\n}\n\nfunc TestGrammarSettingEmptyGIsNoop(t *testing.T) {\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{{S: \"#ZZ\", G: \"alpha\"}},\n\t\t\t},\n\t\t},\n\t}, &GrammarSetting{Rule: &GrammarSettingRule{Alt: &GrammarSettingAlt{G: \"\"}}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttags := altGTags(t, j, \"val\", \"close\")\n\tif !containsTagSet(tags, []string{\"alpha\"}) {\n\t\tt.Errorf(\"empty-string setting mutated tags: %v\", tags)\n\t}\n}\n\nfunc TestGrammarSettingPreservesInput(t *testing.T) {\n\tgs := &GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{{S: \"#ZZ\", G: \"alpha\"}},\n\t\t\t},\n\t\t},\n\t}\n\toriginal := gs.Rule[\"val\"].Close.([]*GrammarAltSpec)[0].G\n\n\tj := Make()\n\terr := j.Grammar(gs, &GrammarSetting{\n\t\tRule: &GrammarSettingRule{Alt: &GrammarSettingAlt{G: \"extra\"}},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tafter := gs.Rule[\"val\"].Close.([]*GrammarAltSpec)[0].G\n\tif after != original {\n\t\tt.Errorf(\"Grammar mutated caller's GrammarAltSpec.G: %q -> %q\", original, after)\n\t}\n}\n\nfunc TestGrammarSettingInjectFormApplied(t *testing.T) {\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: &GrammarAltListSpec{\n\t\t\t\t\tAlts:   []*GrammarAltSpec{{S: \"#ZZ\", G: \"solo\"}},\n\t\t\t\t\tInject: &GrammarInjectSpec{Append: true},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, &GrammarSetting{\n\t\tRule: &GrammarSettingRule{Alt: &GrammarSettingAlt{G: \"shared\"}},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttags := altGTags(t, j, \"val\", \"close\")\n\tif !containsTagSet(tags, []string{\"shared\", \"solo\"}) {\n\t\tt.Errorf(\"expected shared+solo alt, got %v\", tags)\n\t}\n}\n\n// --- GrammarText setting ---\n\nfunc TestGrammarTextSettingAppendsTags(t *testing.T) {\n\tj := Make()\n\terr := j.GrammarText(`\n\t\trule: {\n\t\t\tval: {\n\t\t\t\tclose: [\n\t\t\t\t\t{ s: \"#ZZ\", g: \"first\" },\n\t\t\t\t\t{ s: \"#CA\", g: \"second\" }\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t`, &GrammarSetting{\n\t\tRule: &GrammarSettingRule{Alt: &GrammarSettingAlt{G: \"common\"}},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttags := altGTags(t, j, \"val\", \"close\")\n\tif !containsTagSet(tags, []string{\"common\", \"first\"}) {\n\t\tt.Errorf(\"missing first+common, got %v\", tags)\n\t}\n\tif !containsTagSet(tags, []string{\"common\", \"second\"}) {\n\t\tt.Errorf(\"missing second+common, got %v\", tags)\n\t}\n}\n\n// --- g tag validation in NormAlt ---\n\nfunc TestValidateGroupTagsRejects(t *testing.T) {\n\t// Bad: uppercase, leading digit, punctuation, spaces, single-char.\n\tbad := []string{\"Foo\", \"1foo\", \"foo!\", \"foo bar\", \"FOO\", \"a\", \"x\"}\n\tfor _, b := range bad {\n\t\tif err := ValidateGroupTags(b); err == nil {\n\t\t\tt.Errorf(\"expected rejection of %q\", b)\n\t\t}\n\t}\n}\n\nfunc TestValidateGroupTagsAccepts(t *testing.T) {\n\t// Good: letter + one or more letters/digits/hyphens.\n\tgood := []string{\n\t\t\"\", \"a1\", \"ab\", \"abc\", \"a1b2\", \"foo\", \"z99\",\n\t\t\"fo-o\", \"custom-from-text\", \"foo,bar\", \"a1,b2\", \"alpha-beta,gamma\",\n\t}\n\tfor _, g := range good {\n\t\tif err := ValidateGroupTags(g); err != nil {\n\t\t\tt.Errorf(\"expected %q to be accepted, got %v\", g, err)\n\t\t}\n\t}\n}\n\nfunc TestNormAltReturnsErrorOnInvalidTag(t *testing.T) {\n\terr := NormAlt(&AltSpec{G: \"BAD\"})\n\tif err == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n\tif !strings.Contains(err.Error(), \"invalid group tag\") {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestGrammarInvalidGTagReturnsError(t *testing.T) {\n\t// An invalid tag supplied via Grammar surfaces as an error — no panic.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {Close: []*GrammarAltSpec{{S: \"#ZZ\", G: \"BAD\"}}},\n\t\t},\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"expected error for invalid grammar tag\")\n\t}\n\tif !strings.Contains(err.Error(), \"invalid group tag\") {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestGrammarSettingInvalidTagReturnsError(t *testing.T) {\n\t// A setting-supplied bad tag is also validated via the error return.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {Close: []*GrammarAltSpec{{S: \"#ZZ\", G: \"ok1\"}}},\n\t\t},\n\t}, &GrammarSetting{\n\t\tRule: &GrammarSettingRule{Alt: &GrammarSettingAlt{G: \"BAD\"}},\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"expected error for invalid setting tag\")\n\t}\n\tif !strings.Contains(err.Error(), \"invalid group tag\") {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n}\n\n// --- SetOptionsText ---\n\nfunc TestSetOptionsTextBasic(t *testing.T) {\n\tj := Make()\n\tif _, err := j.SetOptionsText(`number: { sep: \"_\" }`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif m := result.(map[string]any); m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestSetOptionsTextEmptyIsNoop(t *testing.T) {\n\tj := Make()\n\tif _, err := j.SetOptionsText(\"\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresult, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif m := result.(map[string]any); m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a:1, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestSetOptionsTextMergesWithSetOptions(t *testing.T) {\n\tj := Make()\n\tif _, err := j.SetOptionsText(`number: { sep: \"_\" }`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tyes := true\n\tj.SetOptions(Options{Number: &NumberOptions{Hex: &yes}})\n\n\t// Separator from text still applies.\n\tresult, err := j.Parse(\"a:1_000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif m := result.(map[string]any); m[\"a\"] != float64(1000) {\n\t\tt.Errorf(\"expected a:1000 after merge, got %v\", m[\"a\"])\n\t}\n\n\t// Hex from later SetOptions also applies.\n\tresult2, err := j.Parse(\"b:0xff\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif m := result2.(map[string]any); m[\"b\"] != float64(255) {\n\t\tt.Errorf(\"expected b:255, got %v\", m[\"b\"])\n\t}\n}\n\nfunc TestSetOptionsTextInvalidSource(t *testing.T) {\n\t// Jsonic is lenient about missing closing braces, so the error must\n\t// come from a lexer-level failure — here, an unterminated string.\n\tj := Make()\n\tif _, err := j.SetOptionsText(`number: { sep: \"`); err == nil {\n\t\tt.Error(\"expected parse error on malformed options text\")\n\t}\n}\n\nfunc TestSetOptionsTextReturnsInstance(t *testing.T) {\n\tj := Make()\n\treturned, err := j.SetOptionsText(`tag: \"abc\"`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif returned != j {\n\t\tt.Error(\"SetOptionsText should return the receiver\")\n\t}\n}\n\n"
  },
  {
    "path": "go/grammarspec.go",
    "content": "package jsonic\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n)\n\n// FuncRef is a string starting with \"@\" that references a function in a Ref map.\ntype FuncRef = string\n\n// GrammarSpec defines a declarative grammar specification.\n// Mirrors the TypeScript GrammarSpec type.\ntype GrammarSpec struct {\n\t// Ref maps FuncRef strings (like \"@finish\") to Go functions.\n\tRef map[FuncRef]any\n\n\t// Options to merge into the Jsonic instance before processing rules.\n\t// Applied via SetOptions.\n\tOptions *Options\n\n\t// OptionsMap is an alternative to Options that accepts a map[string]any.\n\t// FuncRef strings in values are resolved via Ref before applying.\n\t// Use for JSON-serializable grammars where function fields use \"@name\" refs.\n\tOptionsMap map[string]any\n\n\t// Rule defines open/close alternates per rule name.\n\tRule map[string]*GrammarRuleSpec\n}\n\n// GrammarRuleSpec defines open and close alternates for a single rule.\n// Open and Close can be a plain []*GrammarAltSpec or a *GrammarAltListSpec\n// with inject modifiers (append, delete, move).\ntype GrammarRuleSpec struct {\n\tOpen  any // []*GrammarAltSpec or *GrammarAltListSpec\n\tClose any // []*GrammarAltSpec or *GrammarAltListSpec\n}\n\n// GrammarAltListSpec wraps alt specs with injection modifiers.\ntype GrammarAltListSpec struct {\n\tAlts   []*GrammarAltSpec\n\tInject *GrammarInjectSpec\n}\n\n// GrammarInjectSpec controls how alts are merged into existing rule alternates.\ntype GrammarInjectSpec struct {\n\tAppend bool  // If true, append; if false, prepend (default).\n\tDelete []int // Indices to delete (supports negative).\n\tMove   []int // Pairs: [from, to, from, to, ...].\n}\n\n// GrammarSetting carries optional settings applied when a grammar\n// spec (Grammar/GrammarText) is installed. If Rule.Alt.G is defined,\n// its tag(s) are appended to every rule-alt G property in the grammar\n// before the alts are installed.\n//\n// G can be a comma-separated string or a []string of tag names.\ntype GrammarSetting struct {\n\tRule *GrammarSettingRule\n}\n\n// GrammarSettingRule wraps alt-level settings.\ntype GrammarSettingRule struct {\n\tAlt *GrammarSettingAlt\n}\n\n// GrammarSettingAlt carries per-alt settings (currently only G).\n// G accepts string (comma-separated) or []string.\ntype GrammarSettingAlt struct {\n\tG any\n}\n\n// GrammarAltSpec is a declarative alternate specification using string references.\n// Token fields use \"#NAME\" strings resolved via Token/TokenSet lookup.\n// Function fields use \"@name\" FuncRef strings resolved via the Ref map.\ntype GrammarAltSpec struct {\n\t// Token spec. Either a string or []string.\n\t//   string:   \"#OB\", \"#KEY #CL\" — each space-separated name is a slot.\n\t//   []string: [\"#CB #CS\"] — each element is a slot; within an element,\n\t//             space-separated names are alternatives for that slot.\n\tS any\n\tB any             // Backtrack: int or FuncRef string\n\tP string          // Push rule name or FuncRef\n\tR string          // Replace rule name or FuncRef\n\tA FuncRef         // Action function ref\n\tE FuncRef         // Error function ref\n\tH FuncRef         // Modifier function ref\n\tC any             // Condition: FuncRef string or map[string]any for declarative\n\tN map[string]int  // Counter increments\n\tU map[string]any  // Custom props\n\tK map[string]any  // Propagated custom props\n\tG string          // Group tags (comma-separated)\n}\n\n// Grammar applies a declarative grammar specification to this Jsonic instance.\n// Options are applied first, then rules are processed.\n// An optional *GrammarSetting may be supplied to append a tag (or tags) to\n// every rule-alt G property in the spec.\n// Returns an error if any FuncRef is missing or has the wrong type.\nfunc (j *Jsonic) Grammar(gs *GrammarSpec, setting ...*GrammarSetting) error {\n\t// Apply typed Options directly.\n\tif gs.Options != nil {\n\t\tj.SetOptions(*gs.Options)\n\t}\n\n\t// Apply OptionsMap with FuncRef resolution.\n\tif gs.OptionsMap != nil {\n\t\tresolved := ResolveFuncRefs(gs.OptionsMap, gs.Ref)\n\t\tif resolvedMap, ok := resolved.(map[string]any); ok {\n\t\t\topts := MapToOptions(resolvedMap)\n\t\t\tj.SetOptions(opts)\n\t\t}\n\t}\n\n\t// Resolve the optional grammar setting's alt.g tags once.\n\taltGTags := extractSettingAltG(setting)\n\n\tif gs.Rule != nil {\n\t\tfor rulename, rulespec := range gs.Rule {\n\t\t\tref := gs.Ref\n\t\t\tvar resolveErr error\n\t\t\tj.Rule(rulename, func(rs *RuleSpec, _ *Parser) {\n\t\t\t\t// Process Open alts.\n\t\t\t\tif rulespec.Open != nil {\n\t\t\t\t\tif err := applyGrammarAlts(j, rs, rulespec.Open, ref, true, altGTags); err != nil {\n\t\t\t\t\t\tresolveErr = err\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Process Close alts.\n\t\t\t\tif rulespec.Close != nil {\n\t\t\t\t\tif err := applyGrammarAlts(j, rs, rulespec.Close, ref, false, altGTags); err != nil {\n\t\t\t\t\t\tresolveErr = err\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Auto-wire reserved FuncRef names for state actions.\n\t\t\t\tif ref != nil {\n\t\t\t\t\twireStateActions(rs, ref)\n\t\t\t\t}\n\t\t\t})\n\t\t\tif resolveErr != nil {\n\t\t\t\treturn resolveErr\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// extractSettingAltG returns the list of tag strings from the variadic\n// setting slice (first non-nil entry wins).  Returns nil when no tags\n// are supplied.  Accepts string (comma-separated) or []string.\nfunc extractSettingAltG(setting []*GrammarSetting) []string {\n\tfor _, s := range setting {\n\t\tif s == nil || s.Rule == nil || s.Rule.Alt == nil || s.Rule.Alt.G == nil {\n\t\t\tcontinue\n\t\t}\n\t\tswitch v := s.Rule.Alt.G.(type) {\n\t\tcase string:\n\t\t\treturn splitGroupTags(v)\n\t\tcase []string:\n\t\t\tout := make([]string, 0, len(v))\n\t\t\tfor _, t := range v {\n\t\t\t\tt = strings.TrimSpace(t)\n\t\t\t\tif t != \"\" {\n\t\t\t\t\tout = append(out, t)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\t}\n\treturn nil\n}\n\n// splitGroupTags splits a comma-separated tag string into a []string,\n// trimming surrounding whitespace and discarding empty entries.\nfunc splitGroupTags(s string) []string {\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(s, \",\")\n\tout := make([]string, 0, len(parts))\n\tfor _, p := range parts {\n\t\tp = strings.TrimSpace(p)\n\t\tif p != \"\" {\n\t\t\tout = append(out, p)\n\t\t}\n\t}\n\treturn out\n}\n\n// mergeG combines the existing g tag string with the supplied extra tags,\n// returning a single comma-separated string.\nfunc mergeG(existing string, extra []string) string {\n\tif len(extra) == 0 {\n\t\treturn existing\n\t}\n\ttags := splitGroupTags(existing)\n\ttags = append(tags, extra...)\n\treturn strings.Join(tags, \",\")\n}\n\n// GrammarText parses a jsonic grammar text string into a GrammarSpec\n// and applies it. The text is parsed using a default Jsonic instance,\n// and the resulting map is used as the OptionsMap of a GrammarSpec.\n// An optional *GrammarSetting may be supplied to append a tag (or tags)\n// to every rule-alt G property in the spec.\n// This is a convenience that replaces:\n//\n//\tgs := jsonic.Make()\n//\tparsed, _ := gs.Parse(text)\n//\tj.Grammar(&GrammarSpec{OptionsMap: parsed.(map[string]any)})\nfunc (j *Jsonic) GrammarText(text string, setting ...*GrammarSetting) error {\n\tparsed, err := Make().Parse(text)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif parsed == nil {\n\t\treturn nil\n\t}\n\tgsMap, ok := parsed.(map[string]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"GrammarText: expected map, got %T\", parsed)\n\t}\n\tgs := &GrammarSpec{}\n\tif optionsMap, ok := gsMap[\"options\"].(map[string]any); ok {\n\t\tgs.OptionsMap = optionsMap\n\t} else if _, hasRule := gsMap[\"rule\"]; !hasRule {\n\t\t// No \"options\" wrapper and no \"rule\" key — treat the entire map as options.\n\t\tgs.OptionsMap = gsMap\n\t}\n\tif ruleMap, ok := gsMap[\"rule\"].(map[string]any); ok {\n\t\tgs.Rule = mapToGrammarRules(ruleMap)\n\t}\n\treturn j.Grammar(gs, setting...)\n}\n\n// mapToGrammarRules converts a parsed rule map into typed GrammarRuleSpec map.\nfunc mapToGrammarRules(ruleMap map[string]any) map[string]*GrammarRuleSpec {\n\trules := make(map[string]*GrammarRuleSpec, len(ruleMap))\n\tfor name, v := range ruleMap {\n\t\trm, ok := v.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tspec := &GrammarRuleSpec{}\n\t\tif open, ok := rm[\"open\"]; ok {\n\t\t\tspec.Open = parseGrammarAltsOrSpec(open)\n\t\t}\n\t\tif close, ok := rm[\"close\"]; ok {\n\t\t\tspec.Close = parseGrammarAltsOrSpec(close)\n\t\t}\n\t\trules[name] = spec\n\t}\n\treturn rules\n}\n\n// parseGrammarAltsOrSpec handles both forms:\n//   - []any (plain alt array) → []*GrammarAltSpec\n//   - map[string]any with \"alts\" and \"inject\" → *GrammarAltListSpec\nfunc parseGrammarAltsOrSpec(v any) any {\n\t// Plain array form.\n\tif arr, ok := v.([]any); ok {\n\t\treturn parseGrammarAlts(arr)\n\t}\n\t// Map form with alts + inject.\n\tif m, ok := v.(map[string]any); ok {\n\t\taltsRaw, hasAlts := m[\"alts\"]\n\t\tif !hasAlts {\n\t\t\treturn nil\n\t\t}\n\t\taltsArr, ok := altsRaw.([]any)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\talts := parseGrammarAlts(altsArr)\n\t\tspec := &GrammarAltListSpec{Alts: alts}\n\t\tif injectRaw, ok := m[\"inject\"].(map[string]any); ok {\n\t\t\tspec.Inject = &GrammarInjectSpec{}\n\t\t\tif append_, ok := injectRaw[\"append\"].(bool); ok {\n\t\t\t\tspec.Inject.Append = append_\n\t\t\t}\n\t\t\tif del, ok := injectRaw[\"delete\"].([]any); ok {\n\t\t\t\tfor _, d := range del {\n\t\t\t\t\tif f, ok := d.(float64); ok {\n\t\t\t\t\t\tspec.Inject.Delete = append(spec.Inject.Delete, int(f))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif mv, ok := injectRaw[\"move\"].([]any); ok {\n\t\t\t\tfor _, m := range mv {\n\t\t\t\t\tif f, ok := m.(float64); ok {\n\t\t\t\t\t\tspec.Inject.Move = append(spec.Inject.Move, int(f))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn spec\n\t}\n\treturn nil\n}\n\n// parseGrammarAlts converts a parsed alt array ([]any of maps) to []*GrammarAltSpec.\nfunc parseGrammarAlts(arr []any) []*GrammarAltSpec {\n\talts := make([]*GrammarAltSpec, 0, len(arr))\n\tfor _, item := range arr {\n\t\tm, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\talt := mapToGrammarAltSpec(m)\n\t\talts = append(alts, alt)\n\t}\n\treturn alts\n}\n\n// mapToGrammarAltSpec converts a parsed map to a GrammarAltSpec.\nfunc mapToGrammarAltSpec(m map[string]any) *GrammarAltSpec {\n\talt := &GrammarAltSpec{}\n\tif v, ok := m[\"s\"]; ok {\n\t\talt.S = v // string or []string ([]any of strings)\n\t}\n\tif v, ok := m[\"b\"]; ok {\n\t\talt.B = v // int (float64 from parse) or FuncRef string\n\t}\n\tif v, ok := m[\"p\"].(string); ok {\n\t\talt.P = v\n\t}\n\tif v, ok := m[\"r\"].(string); ok {\n\t\talt.R = v\n\t}\n\tif v, ok := m[\"a\"].(string); ok {\n\t\talt.A = v\n\t}\n\tif v, ok := m[\"e\"].(string); ok {\n\t\talt.E = v\n\t}\n\tif v, ok := m[\"h\"].(string); ok {\n\t\talt.H = v\n\t}\n\tif v, ok := m[\"c\"]; ok {\n\t\talt.C = v // FuncRef string or map[string]any\n\t}\n\tif v, ok := m[\"n\"].(map[string]any); ok {\n\t\talt.N = make(map[string]int, len(v))\n\t\tfor k, val := range v {\n\t\t\tif f, ok := val.(float64); ok {\n\t\t\t\talt.N[k] = int(f)\n\t\t\t}\n\t\t}\n\t}\n\tif v, ok := m[\"u\"].(map[string]any); ok {\n\t\talt.U = v\n\t}\n\tif v, ok := m[\"k\"].(map[string]any); ok {\n\t\talt.K = v\n\t}\n\tif v, ok := m[\"g\"].(string); ok {\n\t\talt.G = v\n\t}\n\treturn alt\n}\n\n// applyGrammarAlts resolves and applies grammar alts to a rule spec.\n// Handles both plain []*GrammarAltSpec and *GrammarAltListSpec with inject.\n// When extraG is non-empty, those tags are appended to every alt's G field.\nfunc applyGrammarAlts(j *Jsonic, rs *RuleSpec, spec any, ref map[FuncRef]any, isOpen bool, extraG []string) error {\n\tvar gas []*GrammarAltSpec\n\tvar inject *GrammarInjectSpec\n\n\tswitch v := spec.(type) {\n\tcase []*GrammarAltSpec:\n\t\tgas = v\n\tcase *GrammarAltListSpec:\n\t\tgas = v.Alts\n\t\tinject = v.Inject\n\tdefault:\n\t\treturn nil\n\t}\n\n\t// Append the setting's alt-g tags to each alt's G prior to resolution.\n\tif len(extraG) > 0 {\n\t\tmerged := make([]*GrammarAltSpec, len(gas))\n\t\tfor i, ga := range gas {\n\t\t\tif ga == nil {\n\t\t\t\tmerged[i] = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcp := *ga\n\t\t\tcp.G = mergeG(ga.G, extraG)\n\t\t\tmerged[i] = &cp\n\t\t}\n\t\tgas = merged\n\t}\n\n\tresolved, err := j.resolveGrammarAlts(gas, ref)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdest := &rs.Close\n\tif isOpen {\n\t\tdest = &rs.Open\n\t}\n\n\t// Apply inject modifiers (delete, move) to existing alts first.\n\tif inject != nil && (len(inject.Delete) > 0 || len(inject.Move) > 0) {\n\t\t*dest = modifyAltList(*dest, &AltModListOpts{\n\t\t\tDelete: inject.Delete,\n\t\t\tMove:   inject.Move,\n\t\t})\n\t}\n\n\t// Insert resolved alts: append or prepend (default: prepend).\n\tif inject != nil && inject.Append {\n\t\t*dest = append(*dest, resolved...)\n\t} else {\n\t\t*dest = append(resolved, *dest...)\n\t}\n\n\treturn nil\n}\n\n// resolveGrammarAlts converts a slice of GrammarAltSpec to concrete AltSpec.\nfunc (j *Jsonic) resolveGrammarAlts(gas []*GrammarAltSpec, ref map[FuncRef]any) ([]*AltSpec, error) {\n\talts := make([]*AltSpec, 0, len(gas))\n\tfor _, ga := range gas {\n\t\talt, err := j.resolveGrammarAlt(ga, ref)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\talts = append(alts, alt)\n\t}\n\treturn alts, nil\n}\n\n// resolveGrammarAlt converts a single GrammarAltSpec to a concrete AltSpec.\nfunc (j *Jsonic) resolveGrammarAlt(ga *GrammarAltSpec, ref map[FuncRef]any) (*AltSpec, error) {\n\talt := &AltSpec{}\n\n\t// Resolve S (token spec: string or []string → [][]Tin)\n\tif ga.S != nil {\n\t\talt.S = j.resolveTokenField(ga.S)\n\t}\n\n\t// Resolve B (backtrack: int or FuncRef)\n\tswitch v := ga.B.(type) {\n\tcase int:\n\t\talt.B = v\n\tcase float64:\n\t\talt.B = int(v)\n\tcase string:\n\t\tfn, err := RequireRef(ref, v, \"backtrack\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif bf, ok := fn.(func(*Rule, *Context) int); ok {\n\t\t\talt.BF = bf\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not a backtrack function\", v)\n\t\t}\n\t}\n\n\t// Resolve P (push: rule name or FuncRef)\n\tif ga.P != \"\" {\n\t\tif IsFuncRef(ga.P) {\n\t\t\tfn, err := RequireRef(ref, ga.P, \"push\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif pf, ok := fn.(func(*Rule, *Context) string); ok {\n\t\t\t\talt.PF = pf\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not a push function\", ga.P)\n\t\t\t}\n\t\t} else {\n\t\t\talt.P = ga.P\n\t\t}\n\t}\n\n\t// Resolve R (replace: rule name or FuncRef)\n\tif ga.R != \"\" {\n\t\tif IsFuncRef(ga.R) {\n\t\t\tfn, err := RequireRef(ref, ga.R, \"replace\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif rf, ok := fn.(func(*Rule, *Context) string); ok {\n\t\t\t\talt.RF = rf\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not a replace function\", ga.R)\n\t\t\t}\n\t\t} else {\n\t\t\talt.R = ga.R\n\t\t}\n\t}\n\n\t// Resolve A (action)\n\tif ga.A != \"\" {\n\t\tfn, err := RequireRef(ref, ga.A, \"action\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif af, ok := fn.(AltAction); ok {\n\t\t\talt.A = af\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not an AltAction\", ga.A)\n\t\t}\n\t}\n\n\t// Resolve E (error)\n\tif ga.E != \"\" {\n\t\tfn, err := RequireRef(ref, ga.E, \"error\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ef, ok := fn.(AltError); ok {\n\t\t\talt.E = ef\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not an AltError\", ga.E)\n\t\t}\n\t}\n\n\t// Resolve H (modifier)\n\tif ga.H != \"\" {\n\t\tfn, err := RequireRef(ref, ga.H, \"modifier\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif hf, ok := fn.(AltModifier); ok {\n\t\t\talt.H = hf\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not an AltModifier\", ga.H)\n\t\t}\n\t}\n\n\t// Resolve C (condition: FuncRef or declarative map)\n\tswitch cv := ga.C.(type) {\n\tcase string:\n\t\tfn, err := RequireRef(ref, cv, \"condition\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif cf, ok := fn.(AltCond); ok {\n\t\t\talt.C = cf\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"Grammar: ref %q is not an AltCond\", cv)\n\t\t}\n\tcase map[string]any:\n\t\talt.CD = cv\n\t}\n\n\t// Copy simple fields\n\tif ga.N != nil {\n\t\talt.N = make(map[string]int, len(ga.N))\n\t\tfor k, v := range ga.N {\n\t\t\talt.N[k] = v\n\t\t}\n\t}\n\tif ga.U != nil {\n\t\talt.U = make(map[string]any, len(ga.U))\n\t\tfor k, v := range ga.U {\n\t\t\talt.U[k] = v\n\t\t}\n\t}\n\tif ga.K != nil {\n\t\talt.K = make(map[string]any, len(ga.K))\n\t\tfor k, v := range ga.K {\n\t\t\talt.K[k] = v\n\t\t}\n\t}\n\talt.G = ga.G\n\n\t// Normalize declarative conditions and validate group tags.\n\tif err := NormAlt(alt); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn alt, nil\n}\n\n// resolveTokenField resolves the S field of a GrammarAltSpec.\n// Accepts string or []string.\n//\n//\tstring:     \"#KEY #CL\" — each space-separated name is a separate slot.\n//\t[]string:   [\"#CB #CS\"] — each element is a slot; within an element,\n//\t            space-separated names are alternatives for that slot.\nfunc (j *Jsonic) resolveTokenField(s any) [][]Tin {\n\tswitch v := s.(type) {\n\tcase string:\n\t\tif v == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn j.resolveTokenSpec(v)\n\tcase []string:\n\t\tresult := make([][]Tin, len(v))\n\t\tfor i, slot := range v {\n\t\t\tvar tins []Tin\n\t\t\tfor _, name := range strings.Fields(slot) {\n\t\t\t\ttins = append(tins, j.resolveTokenName(name)...)\n\t\t\t}\n\t\t\tresult[i] = tins\n\t\t}\n\t\treturn result\n\t}\n\treturn nil\n}\n\n// resolveTokenSpec resolves a token spec string into [][]Tin.\n// Each space-separated name becomes a separate slot.\nfunc (j *Jsonic) resolveTokenSpec(s string) [][]Tin {\n\tparts := strings.Fields(s)\n\tif len(parts) == 0 {\n\t\treturn nil\n\t}\n\tresult := make([][]Tin, len(parts))\n\tfor i, part := range parts {\n\t\tresult[i] = j.resolveTokenName(part)\n\t}\n\treturn result\n}\n\n// resolveTokenName resolves a single token name (like \"#OB\" or \"#KEY\") to a []Tin.\nfunc (j *Jsonic) resolveTokenName(name string) []Tin {\n\tsetName := strings.TrimPrefix(name, \"#\")\n\tif tins := j.TokenSet(setName); tins != nil {\n\t\treturn tins\n\t}\n\ttin := j.Token(name)\n\treturn []Tin{tin}\n}\n\n// wireStateActions auto-wires reserved FuncRef names to state action slices.\n// Names: @{rulename}-bo, @{rulename}-ao, @{rulename}-bc, @{rulename}-ac\n// Variants: /prepend prepends, /append or plain appends.\n//\n// Dedupe by function identity per phase: registering the same StateAction\n// twice (directly, or via a later Grammar() call that also passes it)\n// installs only one action, but distinct functions for the same phase all\n// install. Mirrors the TypeScript fnref behaviour — accommodates grammars\n// that layer handlers while preventing unrelated Grammar() calls from\n// re-stacking previously-registered reserved handlers.\nfunc wireStateActions(rs *RuleSpec, ref map[FuncRef]any) {\n\ttype target struct {\n\t\tsuffix string\n\t\tdest   *[]StateAction\n\t}\n\ttargets := []target{\n\t\t{\"bo\", &rs.BO},\n\t\t{\"ao\", &rs.AO},\n\t\t{\"bc\", &rs.BC},\n\t\t{\"ac\", &rs.AC},\n\t}\n\tif rs.fnrefInstalled == nil {\n\t\trs.fnrefInstalled = map[string]map[uintptr]bool{}\n\t}\n\tfor _, t := range targets {\n\t\tbase := \"@\" + rs.Name + \"-\" + t.suffix\n\t\tphaseSet, ok := rs.fnrefInstalled[base]\n\t\tif !ok {\n\t\t\tphaseSet = map[uintptr]bool{}\n\t\t\trs.fnrefInstalled[base] = phaseSet\n\t\t}\n\n\t\t// Check /prepend first, then /append, then plain.\n\t\ttype variant struct {\n\t\t\tkey    string\n\t\t\tappend bool\n\t\t}\n\t\tfor _, v := range []variant{\n\t\t\t{base + \"/prepend\", false},\n\t\t\t{base + \"/append\", true},\n\t\t\t{base, true},\n\t\t} {\n\t\t\tfn, present := ref[v.key]\n\t\t\tif !present || fn == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsa, ok := fn.(StateAction)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Dedupe by function pointer so the same StateAction can't\n\t\t\t// be wired twice for the same phase across Grammar() calls.\n\t\t\tptr := reflect.ValueOf(sa).Pointer()\n\t\t\tif phaseSet[ptr] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tphaseSet[ptr] = true\n\t\t\tif v.append {\n\t\t\t\t*t.dest = append(*t.dest, sa)\n\t\t\t} else {\n\t\t\t\t*t.dest = append([]StateAction{sa}, *t.dest...)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// builtinTins maps standard token names to their Tin values.\nvar builtinTins = map[string]Tin{\n\t\"#BD\": TinBD, \"#ZZ\": TinZZ, \"#UK\": TinUK, \"#AA\": TinAA,\n\t\"#SP\": TinSP, \"#LN\": TinLN, \"#CM\": TinCM, \"#NR\": TinNR,\n\t\"#ST\": TinST, \"#TX\": TinTX, \"#VL\": TinVL, \"#OB\": TinOB,\n\t\"#CB\": TinCB, \"#OS\": TinOS, \"#CS\": TinCS, \"#CL\": TinCL,\n\t\"#CA\": TinCA,\n}\n\n// builtinTokenSets maps standard token set names to their Tin slices.\nvar builtinTokenSets = map[string][]Tin{\n\t\"VAL\": TinSetVAL,\n\t\"KEY\": TinSetKEY,\n}\n\n// resolveTokenFieldStatic resolves a string or []string S field using built-in tokens.\nfunc resolveTokenFieldStatic(s any) [][]Tin {\n\tswitch v := s.(type) {\n\tcase string:\n\t\tif v == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn resolveTokenSpecStatic(v)\n\tcase []string:\n\t\tresult := make([][]Tin, len(v))\n\t\tfor i, slot := range v {\n\t\t\tvar tins []Tin\n\t\t\tfor _, name := range strings.Fields(slot) {\n\t\t\t\ttins = append(tins, resolveTokenNameStatic(name)...)\n\t\t\t}\n\t\t\tresult[i] = tins\n\t\t}\n\t\treturn result\n\t}\n\treturn nil\n}\n\n// resolveTokenSpecStatic resolves a token spec string using built-in tokens only.\nfunc resolveTokenSpecStatic(s string) [][]Tin {\n\tparts := strings.Fields(s)\n\tif len(parts) == 0 {\n\t\treturn nil\n\t}\n\tresult := make([][]Tin, len(parts))\n\tfor i, part := range parts {\n\t\tresult[i] = resolveTokenNameStatic(part)\n\t}\n\treturn result\n}\n\nfunc resolveTokenNameStatic(name string) []Tin {\n\tsetName := strings.TrimPrefix(name, \"#\")\n\tif tins, ok := builtinTokenSets[setName]; ok {\n\t\tresult := make([]Tin, len(tins))\n\t\tcopy(result, tins)\n\t\treturn result\n\t}\n\tif tin, ok := builtinTins[name]; ok {\n\t\treturn []Tin{tin}\n\t}\n\t// Unknown tokens in static context are programming errors in the internal grammar.\n\t// Return empty slice rather than panicking.\n\treturn nil\n}\n\n// resolveGrammarAltStatic converts a GrammarAltSpec to a concrete AltSpec\n// using only built-in token resolution. Used by the internal Grammar().\n// Errors cause the returned alt to have nil fields (best-effort).\nfunc resolveGrammarAltStatic(ga *GrammarAltSpec, ref map[FuncRef]any) *AltSpec {\n\talt := &AltSpec{}\n\n\tif ga.S != nil {\n\t\talt.S = resolveTokenFieldStatic(ga.S)\n\t}\n\n\tswitch v := ga.B.(type) {\n\tcase int:\n\t\talt.B = v\n\tcase float64:\n\t\talt.B = int(v)\n\tcase string:\n\t\tif fn := LookupRef(ref, v); fn != nil {\n\t\t\tif bf, ok := fn.(func(*Rule, *Context) int); ok {\n\t\t\t\talt.BF = bf\n\t\t\t}\n\t\t}\n\t}\n\n\tif ga.P != \"\" {\n\t\tif IsFuncRef(ga.P) {\n\t\t\tif fn := LookupRef(ref, ga.P); fn != nil {\n\t\t\t\tif pf, ok := fn.(func(*Rule, *Context) string); ok {\n\t\t\t\t\talt.PF = pf\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\talt.P = ga.P\n\t\t}\n\t}\n\n\tif ga.R != \"\" {\n\t\tif IsFuncRef(ga.R) {\n\t\t\tif fn := LookupRef(ref, ga.R); fn != nil {\n\t\t\t\tif rf, ok := fn.(func(*Rule, *Context) string); ok {\n\t\t\t\t\talt.RF = rf\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\talt.R = ga.R\n\t\t}\n\t}\n\n\tif ga.A != \"\" {\n\t\tif fn := LookupRef(ref, ga.A); fn != nil {\n\t\t\talt.A = fn.(AltAction)\n\t\t}\n\t}\n\tif ga.E != \"\" {\n\t\tif fn := LookupRef(ref, ga.E); fn != nil {\n\t\t\talt.E = fn.(AltError)\n\t\t}\n\t}\n\tif ga.H != \"\" {\n\t\tif fn := LookupRef(ref, ga.H); fn != nil {\n\t\t\talt.H = fn.(AltModifier)\n\t\t}\n\t}\n\n\tswitch cv := ga.C.(type) {\n\tcase string:\n\t\tif fn := LookupRef(ref, cv); fn != nil {\n\t\t\talt.C = fn.(AltCond)\n\t\t}\n\tcase map[string]any:\n\t\talt.CD = cv\n\t}\n\n\tif ga.N != nil {\n\t\talt.N = ga.N\n\t}\n\tif ga.U != nil {\n\t\talt.U = ga.U\n\t}\n\tif ga.K != nil {\n\t\talt.K = ga.K\n\t}\n\talt.G = ga.G\n\n\t// Internal grammar is constructed from trusted tag literals, so a\n\t// validation error here indicates a programming bug rather than bad\n\t// user input. Ignore the error — it will surface if a library author\n\t// ever adds a malformed tag, via the regular Grammar/GrammarText path.\n\t_ = NormAlt(alt)\n\treturn alt\n}\n"
  },
  {
    "path": "go/jsonic.go",
    "content": "// Package jsonic provides a lenient JSON parser that supports relaxed syntax\n// including unquoted keys, implicit objects/arrays, comments, trailing commas,\n// single-quoted strings, path diving (nested object shorthand), and more.\n//\n// It is a Go port of the jsonic TypeScript library, faithfully implementing\n// the same matcher-based lexer and rule-based parser architecture.\npackage jsonic\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Version is the current version of the jsonic Go module.\nconst Version = \"0.1.22\"\n\n// Error message templates matching TypeScript defaults.\nvar errorMessages = map[string]string{\n\t\"unexpected\":           \"unexpected character(s): \",\n\t\"unterminated_string\":  \"unterminated string: \",\n\t\"unterminated_comment\": \"unterminated comment: \",\n\t\"unknown\":              \"unknown error: \",\n}\n\n// JsonicError is the error type returned by Parse when parsing fails.\n// It includes structured details about the error location and cause.\ntype JsonicError struct {\n\tCode   string // Error code: \"unterminated_string\", \"unterminated_comment\", \"unexpected\"\n\tDetail string // Human-readable detail message (e.g. \"unterminated string: \\\"abc\")\n\tPos    int    // 0-based character position in source\n\tRow    int    // 1-based line number\n\tCol    int    // 1-based column number\n\tSrc    string // Source fragment at the error (the token text)\n\tHint   string // Additional explanatory text for this error code\n\n\tfullSource string      // Complete input source (for generating site extract)\n\ttag        string      // Custom error tag name (TS: errmsg.name), defaults to \"jsonic\"\n\tcolor      ColorConfig // ANSI palette applied by Error(); zero value disables colour\n}\n\n// Error returns a formatted error message matching the TypeScript JsonicError format:\n//\n//\t[jsonic/<code>]: <message>\n//\t  --> <row>:<col>\n//\t <line-2> | <source>\n//\t <line-1> | <source>\n//\t    <line> | <source with error>\n//\t             ^^^^ <message>\n//\t <line+1> | <source>\n//\t <line+2> | <source>\n//\n// When e.color.Active is true the header, arrow, caret, and line-number\n// gutter are wrapped in ANSI escapes — matching TS error.ts output.\nfunc (e *JsonicError) Error() string {\n\tmsg := e.Detail\n\n\thi, _, line, reset := e.color.Codes()\n\n\tvar b strings.Builder\n\n\t// Line 1: [tag/<code>]: <message>\n\ttag := e.tag\n\tif tag == \"\" {\n\t\ttag = \"jsonic\"\n\t}\n\tb.WriteString(hi)\n\tb.WriteString(\"[\")\n\tb.WriteString(tag)\n\tb.WriteString(\"/\")\n\tb.WriteString(e.Code)\n\tb.WriteString(\"]:\")\n\tb.WriteString(reset)\n\tb.WriteString(\" \")\n\tb.WriteString(msg)\n\n\t// Line 2: --> <row>:<col>\n\tb.WriteString(\"\\n  \")\n\tb.WriteString(line)\n\tb.WriteString(\"-->\")\n\tb.WriteString(reset)\n\tb.WriteString(\" \")\n\tb.WriteString(strconv.Itoa(e.Row))\n\tb.WriteString(\":\")\n\tb.WriteString(strconv.Itoa(e.Col))\n\n\t// Source site extract\n\tif e.fullSource != \"\" {\n\t\tsite := errsite(e.fullSource, e.Src, msg, e.Row, e.Col, e.color)\n\t\tif site != \"\" {\n\t\t\tb.WriteString(\"\\n\")\n\t\t\tb.WriteString(site)\n\t\t}\n\t}\n\n\t// Hint\n\tif e.Hint != \"\" {\n\t\tb.WriteString(\"\\n  Hint: \")\n\t\tb.WriteString(e.Hint)\n\t}\n\n\treturn b.String()\n}\n\n// errsite generates a source code extract showing the error location,\n// matching the TypeScript errsite() function output format.  When color\n// is active, line-number gutters and the caret row are wrapped in the\n// configured ANSI Line/Reset codes.\nfunc errsite(src, sub, msg string, row, col int, color ColorConfig) string {\n\tif row < 1 {\n\t\trow = 1\n\t}\n\tif col < 1 {\n\t\tcol = 1\n\t}\n\n\t_, _, line, reset := color.Codes()\n\n\tlines := strings.Split(src, \"\\n\")\n\n\t// row is 1-based, convert to 0-based index\n\tlineIdx := row - 1\n\tif lineIdx >= len(lines) {\n\t\tlineIdx = len(lines) - 1\n\t}\n\n\t// Determine padding width based on largest line number shown\n\tmaxLineNum := row + 2\n\tpad := len(strconv.Itoa(maxLineNum)) + 2\n\n\t// Build context lines: 2 before, error line, caret line, 2 after\n\tvar result []string\n\n\tln := func(num int, text string) string {\n\t\tnumStr := strconv.Itoa(num)\n\t\treturn line + strings.Repeat(\" \", pad-len(numStr)) + numStr + \" | \" + reset + text\n\t}\n\n\t// 2 lines before\n\tif lineIdx-2 >= 0 {\n\t\tresult = append(result, ln(row-2, lines[lineIdx-2]))\n\t}\n\tif lineIdx-1 >= 0 {\n\t\tresult = append(result, ln(row-1, lines[lineIdx-1]))\n\t}\n\n\t// Error line\n\tif lineIdx >= 0 && lineIdx < len(lines) {\n\t\tresult = append(result, ln(row, lines[lineIdx]))\n\t}\n\n\t// Caret line\n\tcaretCount := len(sub)\n\tif caretCount < 1 {\n\t\tcaretCount = 1\n\t}\n\tindent := strings.Repeat(\" \", pad) + \"   \" + strings.Repeat(\" \", col-1)\n\tresult = append(result, indent+line+strings.Repeat(\"^\", caretCount)+\" \"+msg+reset)\n\n\t// 2 lines after\n\tif lineIdx+1 < len(lines) {\n\t\tresult = append(result, ln(row+1, lines[lineIdx+1]))\n\t}\n\tif lineIdx+2 < len(lines) {\n\t\tresult = append(result, ln(row+2, lines[lineIdx+2]))\n\t}\n\n\treturn strings.Join(result, \"\\n\")\n}\n\n// makeJsonicError creates a JsonicError with the proper Detail message.\n// The colour palette defaults to disabled (zero-value ColorConfig); for\n// parser-path errors use (*Parser).makeError instead, which injects the\n// resolved palette from config.\nfunc makeJsonicError(code, src, fullSource string, pos, row, col int) *JsonicError {\n\ttmpl, ok := errorMessages[code]\n\tif !ok {\n\t\ttmpl = errorMessages[\"unknown\"]\n\t}\n\tdetail := tmpl + src\n\n\treturn &JsonicError{\n\t\tCode:       code,\n\t\tDetail:     detail,\n\t\tPos:        pos,\n\t\tRow:        row,\n\t\tCol:        col,\n\t\tSrc:        src,\n\t\tfullSource: fullSource,\n\t}\n}\n\n// Parse parses a jsonic string and returns the resulting Go value.\n// The returned value can be:\n//   - map[string]any for objects\n//   - []any for arrays\n//   - float64 for numbers\n//   - string for strings\n//   - bool for booleans\n//   - nil for null or empty input\n//\n// Returns a *JsonicError if the input contains a syntax error.\nfunc Parse(src string) (any, error) {\n\tp := NewParser()\n\treturn p.Start(src)\n}\n\n// preprocessEscapes replaces literal backslash-n sequences with real newlines, etc.\n// This handles the case where TSV test files contain literal \"\\n\" in the input.\nfunc preprocessEscapes(s string) string {\n\tif len(s) == 0 {\n\t\treturn s\n\t}\n\n\trunes := []rune(s)\n\tvar out []rune\n\ti := 0\n\tfor i < len(runes) {\n\t\tif runes[i] == '\\\\' && i+1 < len(runes) {\n\t\t\tswitch runes[i+1] {\n\t\t\tcase 'n':\n\t\t\t\tout = append(out, '\\n')\n\t\t\t\ti += 2\n\t\t\tcase 'r':\n\t\t\t\tout = append(out, '\\r')\n\t\t\t\ti += 2\n\t\t\tcase 't':\n\t\t\t\tout = append(out, '\\t')\n\t\t\t\ti += 2\n\t\t\tdefault:\n\t\t\t\tout = append(out, runes[i])\n\t\t\t\ti++\n\t\t\t}\n\t\t} else {\n\t\t\tout = append(out, runes[i])\n\t\t\ti++\n\t\t}\n\t}\n\treturn string(out)\n}\n"
  },
  {
    "path": "go/jsonic_nontsv_test.go",
    "content": "package jsonic\n\n// Non-TSV tests ported from the TypeScript test suite.\n// Tests that rely on TS-specific features (plugins, regex-based custom values,\n// error position checking, array named properties) are NOT ported.\n// Custom config tests using Make() are in alignment_test.go and below.\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\n// --- helpers ---\n\n// expectParse asserts Parse(input) returns expected with no error.\nfunc expectParse(t *testing.T, input string, expected any) {\n\tt.Helper()\n\tgot, err := Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tif !valuesEqual(got, expected) {\n\t\tt.Errorf(\"Parse(%q)\\n  got:      %s\\n  expected: %s\",\n\t\t\tinput, formatValue(got), formatValue(expected))\n\t}\n}\n\n// expectParseNil asserts Parse(input) returns nil with no error.\nfunc expectParseNil(t *testing.T, input string) {\n\tt.Helper()\n\tgot, err := Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"Parse(%q)\\n  got:      %s\\n  expected: nil\",\n\t\t\tinput, formatValue(got))\n\t}\n}\n\n// expectParseError asserts Parse(input) returns a *JsonicError.\nfunc expectParseError(t *testing.T, input string) {\n\tt.Helper()\n\t_, err := Parse(input)\n\tif err == nil {\n\t\tt.Errorf(\"Parse(%q) should have returned an error but did not\", input)\n\t\treturn\n\t}\n\tif _, ok := err.(*JsonicError); !ok {\n\t\tt.Errorf(\"Parse(%q) error should be *JsonicError, got %T: %v\", input, err, err)\n\t}\n}\n\n// m is shorthand for map[string]any.\nfunc m(args ...any) map[string]any {\n\tresult := make(map[string]any)\n\tfor i := 0; i+1 < len(args); i += 2 {\n\t\tkey, _ := args[i].(string)\n\t\tresult[key] = args[i+1]\n\t}\n\treturn result\n}\n\n// a is shorthand for []any.\nfunc a(args ...any) []any {\n\treturn args\n}\n\n// --- Comment tests (from comment.test.js) ---\n\nfunc TestCommentSingleLine(t *testing.T) {\n\t// # comment\n\texpectParse(t, \"a#b\", \"a\")\n\texpectParse(t, \"a:1#b\", m(\"a\", 1.0))\n\texpectParseNil(t, \"#a:1\")\n\texpectParse(t, \"#a:1\\nb:2\", m(\"b\", 2.0))\n\texpectParse(t, \"b:2\\n#a:1\", m(\"b\", 2.0))\n\texpectParse(t, \"b:2,\\n#a:1\\nc:3\", m(\"b\", 2.0, \"c\", 3.0))\n\n\t// // comment\n\texpectParseNil(t, \"//a:1\")\n\texpectParse(t, \"//a:1\\nb:2\", m(\"b\", 2.0))\n\texpectParse(t, \"b:2\\n//a:1\", m(\"b\", 2.0))\n\texpectParse(t, \"b:2,\\n//a:1\\nc:3\", m(\"b\", 2.0, \"c\", 3.0))\n}\n\nfunc TestCommentMultiLine(t *testing.T) {\n\texpectParseNil(t, \"/*a:1*/\")\n\texpectParse(t, \"/*a:1*/\\nb:2\", m(\"b\", 2.0))\n\texpectParse(t, \"/*a:1\\n*/b:2\", m(\"b\", 2.0))\n\texpectParse(t, \"b:2\\n/*a:1*/\", m(\"b\", 2.0))\n\texpectParse(t, \"b:2,\\n/*\\na:1,\\n*/\\nc:3\", m(\"b\", 2.0, \"c\", 3.0))\n\n\t// Unterminated block comments should panic\n\texpectParseError(t, \"/*\")\n\texpectParseError(t, \"\\n/*\")\n\texpectParseError(t, \"a/*\")\n\texpectParseError(t, \"\\na/*\")\n}\n\n// --- Number tests (from feature.test.js) ---\n\nfunc TestNumberParsing(t *testing.T) {\n\t// Basic integers\n\texpectParse(t, \"1\", 1.0)\n\texpectParse(t, \"-1\", -1.0)\n\texpectParse(t, \"+1\", 1.0)\n\texpectParse(t, \"0\", 0.0)\n\n\t// Trailing dot\n\texpectParse(t, \"1.\", 1.0)\n\texpectParse(t, \"-1.\", -1.0)\n\texpectParse(t, \"+1.\", 1.0)\n\texpectParse(t, \"0.\", 0.0)\n\n\t// Leading dot\n\texpectParse(t, \".1\", 0.1)\n\texpectParse(t, \"-.1\", -0.1)\n\texpectParse(t, \"+.1\", 0.1)\n\texpectParse(t, \".0\", 0.0)\n\n\t// Decimals\n\texpectParse(t, \"0.9\", 0.9)\n\texpectParse(t, \"-0.9\", -0.9)\n\n\t// Floats and scientific notation\n\texpectParse(t, \"1.2\", 1.2)\n\texpectParse(t, \"1e2\", 100.0)\n\texpectParse(t, \"-1.2\", -1.2)\n\texpectParse(t, \"-1e2\", -100.0)\n\texpectParse(t, \"1e+2\", 100.0)\n\texpectParse(t, \"1e-2\", 0.01)\n\n\t// Number separators\n\texpectParse(t, \"10_0\", 100.0)\n\texpectParse(t, \"-10_0\", -100.0)\n\n\t// Hex\n\texpectParse(t, \"0xA\", 10.0)\n\texpectParse(t, \"0xa\", 10.0)\n\texpectParse(t, \"+0xA\", 10.0)\n\texpectParse(t, \"+0xa\", 10.0)\n\texpectParse(t, \"-0xA\", -10.0)\n\texpectParse(t, \"-0xa\", -10.0)\n\n\t// Octal and binary\n\texpectParse(t, \"0o12\", 10.0)\n\texpectParse(t, \"0b1010\", 10.0)\n\n\t// Hex/octal/binary with underscores\n\texpectParse(t, \"0x_A\", 10.0)\n\texpectParse(t, \"0x_a\", 10.0)\n\texpectParse(t, \"0o_12\", 10.0)\n\texpectParse(t, \"0b_1010\", 10.0)\n\n\t// Numbers as map keys use source text\n\texpectParse(t, \"1e6:a\", m(\"1e6\", \"a\"))\n\n\t// Leading zeros\n\texpectParse(t, \"01\", 1.0)\n\texpectParse(t, \"-01\", -1.0)\n\texpectParse(t, \"0099\", 99.0)\n\texpectParse(t, \"-0099\", -99.0)\n\n\t// Numbers in context\n\texpectParse(t, \"[1]\", a(1.0))\n\texpectParse(t, \"a:1\", m(\"a\", 1.0))\n\texpectParse(t, \"1:a\", m(\"1\", \"a\"))\n\texpectParse(t, \"{a:1}\", m(\"a\", 1.0))\n\texpectParse(t, \"{1:a}\", m(\"1\", \"a\"))\n\texpectParse(t, \"[1,0]\", a(1.0, 0.0))\n\texpectParse(t, \"[1,0.5]\", a(1.0, 0.5))\n\n\t// Numbers in value position\n\texpectParse(t, \"a:1\", m(\"a\", 1.0))\n\texpectParse(t, \"a:-1\", m(\"a\", -1.0))\n\texpectParse(t, \"a:+1\", m(\"a\", 1.0))\n\texpectParse(t, \"a:0\", m(\"a\", 0.0))\n\texpectParse(t, \"a:0.1\", m(\"a\", 0.1))\n\texpectParse(t, \"a:[1]\", m(\"a\", a(1.0)))\n\texpectParse(t, \"a:a:1\", m(\"a\", m(\"a\", 1.0)))\n\texpectParse(t, \"a:1:a\", m(\"a\", m(\"1\", \"a\")))\n\texpectParse(t, \"a:{a:1}\", m(\"a\", m(\"a\", 1.0)))\n\texpectParse(t, \"a:{1:a}\", m(\"a\", m(\"1\", \"a\")))\n\texpectParse(t, \"a:1.2\", m(\"a\", 1.2))\n\texpectParse(t, \"a:1e2\", m(\"a\", 100.0))\n\texpectParse(t, \"a:10_0\", m(\"a\", 100.0))\n\texpectParse(t, \"a:-1.2\", m(\"a\", -1.2))\n\texpectParse(t, \"a:-1e2\", m(\"a\", -100.0))\n\texpectParse(t, \"a:-10_0\", m(\"a\", -100.0))\n\texpectParse(t, \"a:1e+2\", m(\"a\", 100.0))\n\texpectParse(t, \"a:1e-2\", m(\"a\", 0.01))\n\texpectParse(t, \"a:0xA\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0xa\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0o12\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0b1010\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0x_A\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0x_a\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0o_12\", m(\"a\", 10.0))\n\texpectParse(t, \"a:0b_1010\", m(\"a\", 10.0))\n\texpectParse(t, \"a:1e6:a\", m(\"a\", m(\"1e6\", \"a\")))\n\n\t// text as +- not value enders\n\texpectParse(t, \"1+\", \"1+\")\n\texpectParse(t, \"1-\", \"1-\")\n\texpectParse(t, \"1-+\", \"1-+\")\n\n\t// partial numbers become text\n\texpectParse(t, \"-\", \"-\")\n\texpectParse(t, \"+\", \"+\")\n\texpectParse(t, \"1a\", \"1a\")\n}\n\n// --- Value standard tests (from feature.test.js) ---\n\nfunc TestValueStandard(t *testing.T) {\n\t// Empty input\n\texpectParseNil(t, \"\")\n\n\t// Boolean and null\n\texpectParse(t, \"true\", true)\n\texpectParse(t, \"false\", false)\n\texpectParseNil(t, \"null\")\n\n\t// With trailing newline\n\texpectParse(t, \"true\\n\", true)\n\texpectParse(t, \"false\\n\", false)\n\texpectParseNil(t, \"null\\n\")\n\n\t// With trailing hash comment\n\texpectParse(t, \"true#\", true)\n\texpectParse(t, \"false#\", false)\n\texpectParseNil(t, \"null#\")\n\n\t// With trailing // comment\n\texpectParse(t, \"true//\", true)\n\texpectParse(t, \"false//\", false)\n\texpectParseNil(t, \"null//\")\n\n\t// In maps\n\texpectParse(t, \"{a:true}\", m(\"a\", true))\n\texpectParse(t, \"{a:false}\", m(\"a\", false))\n\texpectParse(t, \"{a:null}\", m(\"a\", nil))\n\n\t// Booleans/null as keys\n\texpectParse(t, \"{true:1}\", m(\"true\", 1.0))\n\texpectParse(t, \"{false:1}\", m(\"false\", 1.0))\n\texpectParse(t, \"{null:1}\", m(\"null\", 1.0))\n\n\t// Implicit maps\n\texpectParse(t, \"a:true\", m(\"a\", true))\n\texpectParse(t, \"a:false\", m(\"a\", false))\n\texpectParse(t, \"a:null\", m(\"a\", nil))\n\texpectParse(t, \"a:\", m(\"a\", nil))\n\n\t// Trailing comma creates implicit list\n\texpectParse(t, \"true,\", a(true))\n\texpectParse(t, \"false,\", a(false))\n\n\t// Complex value\n\texpectParse(t,\n\t\t\"a:true,b:false,c:null,d:{e:true,f:false,g:null},h:[true,false,null]\",\n\t\tm(\"a\", true, \"b\", false, \"c\", nil,\n\t\t\t\"d\", m(\"e\", true, \"f\", false, \"g\", nil),\n\t\t\t\"h\", a(true, false, nil)))\n}\n\nfunc TestValueStandardNullInMap(t *testing.T) {\n\texpectParse(t, \"a:null\", m(\"a\", nil))\n\texpectParse(t, \"null,\", a(nil))\n}\n\n// --- Null-or-undefined tests (from feature.test.js) ---\n\nfunc TestNullOrUndefined(t *testing.T) {\n\t// All ignored → nil (undefined)\n\texpectParseNil(t, \"\")\n\texpectParseNil(t, \" \")\n\texpectParseNil(t, \"\\n\")\n\texpectParseNil(t, \"#\")\n\texpectParseNil(t, \"//\")\n\texpectParseNil(t, \"/**/\")\n\n\t// JSON null\n\texpectParseNil(t, \"null\")\n\texpectParse(t, \"a:null\", m(\"a\", nil))\n\n\texpectParse(t, \"[{a:null}]\", a(m(\"a\", nil)))\n\n\texpectParse(t, \"a:null,b:null\", m(\"a\", nil, \"b\", nil))\n\texpectParse(t, \"{a:null,b:null}\", m(\"a\", nil, \"b\", nil))\n\n\texpectParse(t, \"a:\", m(\"a\", nil))\n\texpectParse(t, \"a:,b:\", m(\"a\", nil, \"b\", nil))\n\texpectParse(t, \"a:,b:c:\", m(\"a\", nil, \"b\", m(\"c\", nil)))\n\n\texpectParse(t, \"{a:}\", m(\"a\", nil))\n\texpectParse(t, \"{a:,b:}\", m(\"a\", nil, \"b\", nil))\n\texpectParse(t, \"{a:,b:c:}\", m(\"a\", nil, \"b\", m(\"c\", nil)))\n}\n\n// --- Text value tests (from feature.test.js) ---\n\nfunc TestValueText(t *testing.T) {\n\texpectParse(t, \"a\", \"a\")\n\texpectParse(t, \"1a\", \"1a\") // not a number!\n\texpectParse(t, \"a/b\", \"a/b\")\n\texpectParse(t, \"a#b\", \"a\") // comment cuts text\n\n\texpectParse(t, \"a//b\", \"a\")     // comment cuts text\n\texpectParse(t, \"a/*b*/\", \"a\")   // comment cuts text\n\texpectParse(t, `a\\n`, `a\\n`)    // literal backslash-n in text\n\texpectParse(t, `\\s+`, `\\s+`)    // literal regex-like text\n\n\texpectParse(t, \"x:a\", m(\"x\", \"a\"))\n\texpectParse(t, \"x:a/b\", m(\"x\", \"a/b\"))\n\texpectParse(t, \"x:a#b\", m(\"x\", \"a\"))\n\texpectParse(t, \"x:a//b\", m(\"x\", \"a\"))\n\texpectParse(t, \"x:a/*b*/\", m(\"x\", \"a\"))\n\texpectParse(t, `x:a\\n`, m(\"x\", `a\\n`))\n\texpectParse(t, `x:\\s+`, m(\"x\", `\\s+`))\n\n\texpectParse(t, \"[a]\", a(\"a\"))\n\texpectParse(t, \"[a/b]\", a(\"a/b\"))\n\texpectParse(t, \"[a#b]\", a(\"a\"))\n\texpectParse(t, \"[a//b]\", a(\"a\"))\n\texpectParse(t, \"[a/*b*/]\", a(\"a\"))\n\texpectParse(t, `[a\\n]`, a(`a\\n`))\n\texpectParse(t, `[\\s+]`, a(`\\s+`))\n}\n\n// --- String value tests (from feature.test.js) ---\n\nfunc TestValueString(t *testing.T) {\n\t// Empty strings\n\texpectParse(t, \"''\", \"\")\n\texpectParse(t, `\"\"`, \"\")\n\texpectParse(t, \"``\", \"\")\n\n\t// Simple strings\n\texpectParse(t, \"'a'\", \"a\")\n\texpectParse(t, `\"a\"`, \"a\")\n\texpectParse(t, \"`a`\", \"a\")\n\n\t// Strings with spaces\n\texpectParse(t, \"'a b'\", \"a b\")\n\texpectParse(t, `\"a b\"`, \"a b\")\n\texpectParse(t, \"`a b`\", \"a b\")\n\n\t// Tab escape\n\texpectParse(t, `'a\\tb'`, \"a\\tb\")\n\texpectParse(t, `\"a\\tb\"`, \"a\\tb\")\n\texpectParse(t, \"`a\\\\tb`\", \"a\\tb\")\n\n\t// Unknown escape → remove backslash\n\texpectParse(t, \"`a\\\\qb`\", \"aqb\")\n\n\t// Escaped quotes within strings\n\texpectParse(t, `'a\\'b\"`+\"`c'\", \"a'b\\\"`c\")\n\texpectParse(t, `\"a\\\"b`+\"`'c\\\"\", \"a\\\"b`'c\")\n\texpectParse(t, \"`a\\\\`b\\\"'c`\", \"a`b\\\"'c\")\n\n\t// Unicode escapes\n\texpectParse(t, `\"\\u0061\"`, \"a\")\n\texpectParse(t, `\"\\x61\"`, \"a\")\n\n\t// Standard escape sequences\n\texpectParse(t, `\"\\n\"`, \"\\n\")\n\texpectParse(t, `\"\\t\"`, \"\\t\")\n\texpectParse(t, `\"\\f\"`, \"\\f\")\n\texpectParse(t, `\"\\b\"`, \"\\b\")\n\texpectParse(t, `\"\\v\"`, \"\\v\")\n\texpectParse(t, `\"\\\"\"`, \"\\\"\")\n\texpectParse(t, `\"\\'\"`, \"'\")\n\texpectParse(t, \"\\\"\\\\`\\\"\", \"`\")\n\n\t// Unknown escape → char itself\n\texpectParse(t, `\"\\w\"`, \"w\")\n\texpectParse(t, `\"\\0\"`, \"0\")\n\n\t// Unterminated strings should panic\n\texpectParseError(t, `\"x`)\n\texpectParseError(t, ` \"x`)\n\texpectParseError(t, `  \"x`)\n\texpectParseError(t, `a:\"x`)\n\n\texpectParseError(t, `'x`)\n\texpectParseError(t, ` 'x`)\n\texpectParseError(t, `  'x`)\n\texpectParseError(t, `a:'x`)\n\n\texpectParseError(t, \"`x\")\n\texpectParseError(t, \" `x\")\n\texpectParseError(t, \"  `x\")\n\texpectParseError(t, \"a:`x\")\n}\n\n// --- Multiline string tests (from feature.test.js) ---\n\nfunc TestMultilineString(t *testing.T) {\n\texpectParse(t, \"`a`\", \"a\")\n\texpectParse(t, \"`\\na`\", \"\\na\")\n\texpectParse(t, \"`\\na\\n`\", \"\\na\\n\")\n\texpectParse(t, \"`a\\nb`\", \"a\\nb\")\n\texpectParse(t, \"`a\\n\\nb`\", \"a\\n\\nb\")\n\texpectParse(t, \"`a\\nc\\nb`\", \"a\\nc\\nb\")\n\texpectParse(t, \"`a\\r\\n\\r\\nb`\", \"a\\r\\n\\r\\nb\")\n\n\t// Unterminated multiline strings\n\texpectParseError(t, \"`\\n\")\n\texpectParseError(t, \" `\\n\")\n}\n\n// --- Single-char tests (from feature.test.js) ---\n\nfunc TestSingleChar(t *testing.T) {\n\texpectParseNil(t, \"\")\n\texpectParse(t, \"a\", \"a\")\n\texpectParse(t, \"{\", m())        // auto-close empty map\n\texpectParse(t, \"[\", a())        // auto-close empty list\n\texpectParse(t, \",\", a(nil))     // implicit list, null element\n\texpectParseNil(t, \"#\")          // comment\n\texpectParseNil(t, \" \")          // space\n\texpectParseNil(t, \"\\t\")         // tab\n\texpectParseNil(t, \"\\n\")         // newline\n\texpectParseNil(t, \"\\r\")         // carriage return\n\n\t// Error cases\n\texpectParseError(t, `\"`)       // unterminated string\n\texpectParseError(t, \"'\")       // unterminated string\n\texpectParseError(t, \":\")       // unexpected\n\texpectParseError(t, \"]\")       // unexpected\n\texpectParseError(t, \"`\")       // unterminated string\n\texpectParseError(t, \"}\")       // unexpected\n}\n\n// --- Implicit list tests (from feature.test.js) ---\n\nfunc TestImplicitList(t *testing.T) {\n\t// Comma-prefixed implicit list creates null element\n\texpectParse(t, \",\", a(nil))\n\texpectParse(t, \",a\", a(nil, \"a\"))\n\texpectParse(t, `,\"a\"`, a(nil, \"a\"))\n\texpectParse(t, \",1\", a(nil, 1.0))\n\texpectParse(t, \",true\", a(nil, true))\n\texpectParse(t, \",[]\", a(nil, a()))\n\texpectParse(t, \",{}\", a(nil, m()))\n\texpectParse(t, \",[1]\", a(nil, a(1.0)))\n\texpectParse(t, \",{a:1}\", a(nil, m(\"a\", 1.0)))\n\n\t// Trailing comma creates list; ignore trailing comma\n\texpectParse(t, \"a,\", a(\"a\"))\n\texpectParse(t, `\"a\",`, a(\"a\"))\n\texpectParse(t, \"1,\", a(1.0))\n\texpectParse(t, \"1,,\", a(1.0, nil))\n\texpectParse(t, \"1,,,\", a(1.0, nil, nil))\n\texpectParse(t, \"1,null\", a(1.0, nil))\n\texpectParse(t, \"1,null,\", a(1.0, nil))\n\texpectParse(t, \"1,null,null\", a(1.0, nil, nil))\n\texpectParse(t, \"1,null,null,\", a(1.0, nil, nil))\n\texpectParse(t, \"true,\", a(true))\n\texpectParse(t, \"[],\", a(a()))\n\texpectParse(t, \"{},\", a(m()))\n\texpectParse(t, \"[1],\", a(a(1.0)))\n\texpectParse(t, \"{a:1},\", a(m(\"a\", 1.0)))\n\n\t// Map pair with trailing comma stays a map\n\texpectParse(t, \"a:1,\", m(\"a\", 1.0))\n\n\t// Comma-separated values\n\texpectParse(t, \"a,1\", a(\"a\", 1.0))\n\texpectParse(t, `\"a\",1`, a(\"a\", 1.0))\n\texpectParse(t, \"true,1\", a(true, 1.0))\n\texpectParse(t, \"1,1\", a(1.0, 1.0))\n\n\texpectParse(t, \"a,b\", a(\"a\", \"b\"))\n\texpectParse(t, \"a,b,c\", a(\"a\", \"b\", \"c\"))\n\texpectParse(t, \"a,b,c,d\", a(\"a\", \"b\", \"c\", \"d\"))\n\n\t// Space-separated values (implicit list)\n\texpectParse(t, \"a b\", a(\"a\", \"b\"))\n\texpectParse(t, \"a b c\", a(\"a\", \"b\", \"c\"))\n\texpectParse(t, \"a b c d\", a(\"a\", \"b\", \"c\", \"d\"))\n\n\t// Arrays as list elements\n\texpectParse(t, \"[a],[b]\", a(a(\"a\"), a(\"b\")))\n\texpectParse(t, \"[a],[b],[c]\", a(a(\"a\"), a(\"b\"), a(\"c\")))\n\texpectParse(t, \"[a],[b],[c],[d]\", a(a(\"a\"), a(\"b\"), a(\"c\"), a(\"d\")))\n\n\t// Space-separated arrays\n\texpectParse(t, \"[a] [b]\", a(a(\"a\"), a(\"b\")))\n\texpectParse(t, \"[a] [b] [c]\", a(a(\"a\"), a(\"b\"), a(\"c\")))\n\texpectParse(t, \"[a] [b] [c] [d]\", a(a(\"a\"), a(\"b\"), a(\"c\"), a(\"d\")))\n\n\t// Space-separated maps (useful for JSON log parsing)\n\texpectParse(t, \"{a:1} {b:1}\", a(m(\"a\", 1.0), m(\"b\", 1.0)))\n\texpectParse(t, \"{a:1} {b:1} {c:1}\", a(m(\"a\", 1.0), m(\"b\", 1.0), m(\"c\", 1.0)))\n\texpectParse(t, \"{a:1} {b:1} {c:1} {d:1}\",\n\t\ta(m(\"a\", 1.0), m(\"b\", 1.0), m(\"c\", 1.0), m(\"d\", 1.0)))\n\texpectParse(t, \"\\n{a:1}\\n{b:1}\\r\\n{c:1}\\n{d:1}\\r\\n\",\n\t\ta(m(\"a\", 1.0), m(\"b\", 1.0), m(\"c\", 1.0), m(\"d\", 1.0)))\n\n\t// Object/list trailing comma\n\texpectParse(t, \"{a:1},\", a(m(\"a\", 1.0)))\n\texpectParse(t, \"[1],\", a(a(1.0)))\n}\n\n// --- Extension (deep merge) tests (from feature.test.js) ---\n\nfunc TestExtension(t *testing.T) {\n\texpectParse(t, \"a:{b:1,c:2},a:{c:3,e:4}\", m(\"a\", m(\"b\", 1.0, \"c\", 3.0, \"e\", 4.0)))\n\n\texpectParse(t, \"a:{b:1,x:1},a:{b:2,y:2},a:{b:3,z:3}\",\n\t\tm(\"a\", m(\"b\", 3.0, \"x\", 1.0, \"y\", 2.0, \"z\", 3.0)))\n\n\texpectParse(t, \"a:[{b:1,x:1}],a:[{b:2,y:2}],a:[{b:3,z:3}]\",\n\t\tm(\"a\", a(m(\"b\", 3.0, \"x\", 1.0, \"y\", 2.0, \"z\", 3.0))))\n\n\texpectParse(t, \"a:[{b:1},{x:1}],a:[{b:2},{y:2}],a:[{b:3},{z:3}]\",\n\t\tm(\"a\", a(m(\"b\", 3.0), m(\"x\", 1.0, \"y\", 2.0, \"z\", 3.0))))\n}\n\n// --- Finish (auto-close) tests (from feature.test.js) ---\n\nfunc TestFinishAutoClose(t *testing.T) {\n\t// Unclosed structures are auto-closed with default config\n\texpectParse(t, \"a:{b:\", m(\"a\", m(\"b\", nil)))\n\texpectParse(t, \"{a:{b:{c:1}\", m(\"a\", m(\"b\", m(\"c\", 1.0))))\n\texpectParse(t, \"[[1\", a(a(1.0)))\n}\n\n// --- Property-dive tests (from feature.test.js) ---\n\nfunc TestPropertyDive(t *testing.T) {\n\t// Standard maps\n\texpectParse(t, \"{a:1,b:2}\", m(\"a\", 1.0, \"b\", 2.0))\n\texpectParse(t, \"{a:1,b:{c:2}}\", m(\"a\", 1.0, \"b\", m(\"c\", 2.0)))\n\texpectParse(t, \"{a:1,b:{c:2},d:3}\", m(\"a\", 1.0, \"b\", m(\"c\", 2.0), \"d\", 3.0))\n\texpectParse(t, \"{b:{c:2,e:4},d:3}\", m(\"b\", m(\"c\", 2.0, \"e\", 4.0), \"d\", 3.0))\n\texpectParse(t, \"{a:{b:{c:1,d:2},e:3},f:4}\",\n\t\tm(\"a\", m(\"b\", m(\"c\", 1.0, \"d\", 2.0), \"e\", 3.0), \"f\", 4.0))\n\n\t// Path dive\n\texpectParse(t, \"a:b:c\", m(\"a\", m(\"b\", \"c\")))\n\texpectParse(t, \"a:b:c, d:e:f\", m(\"a\", m(\"b\", \"c\"), \"d\", m(\"e\", \"f\")))\n\texpectParse(t, \"a:b:c\\nd:e:f\", m(\"a\", m(\"b\", \"c\"), \"d\", m(\"e\", \"f\")))\n\n\texpectParse(t, \"a:b:c,d:e\", m(\"a\", m(\"b\", \"c\"), \"d\", \"e\"))\n\texpectParse(t, \"a:b:c:1,d:e\", m(\"a\", m(\"b\", m(\"c\", 1.0)), \"d\", \"e\"))\n\texpectParse(t, \"a:b:c:f:{g:1},d:e\",\n\t\tm(\"a\", m(\"b\", m(\"c\", m(\"f\", m(\"g\", 1.0)))), \"d\", \"e\"))\n\texpectParse(t, \"c:f:{g:1,h:2},d:e\",\n\t\tm(\"c\", m(\"f\", m(\"g\", 1.0, \"h\", 2.0)), \"d\", \"e\"))\n\texpectParse(t, \"c:f:[{g:1,h:2}],d:e\",\n\t\tm(\"c\", m(\"f\", a(m(\"g\", 1.0, \"h\", 2.0))), \"d\", \"e\"))\n\n\texpectParse(t, \"a:b:c:1\\nd:e\", m(\"a\", m(\"b\", m(\"c\", 1.0)), \"d\", \"e\"))\n\n\t// Path dive in arrays\n\texpectParse(t, \"[{a:1,b:2}]\", a(m(\"a\", 1.0, \"b\", 2.0)))\n\texpectParse(t, \"[{a:1,b:{c:2}}]\", a(m(\"a\", 1.0, \"b\", m(\"c\", 2.0))))\n\texpectParse(t, \"[{a:1,b:{c:2},d:3}]\", a(m(\"a\", 1.0, \"b\", m(\"c\", 2.0), \"d\", 3.0)))\n\texpectParse(t, \"[{b:{c:2,e:4},d:3}]\", a(m(\"b\", m(\"c\", 2.0, \"e\", 4.0), \"d\", 3.0)))\n\texpectParse(t, \"[{a:{b:{c:1,d:2},e:3},f:4}]\",\n\t\ta(m(\"a\", m(\"b\", m(\"c\", 1.0, \"d\", 2.0), \"e\", 3.0), \"f\", 4.0)))\n\n\t// Path dive with deep merge\n\texpectParse(t, \"a:b:{x:1},a:b:{y:2}\", m(\"a\", m(\"b\", m(\"x\", 1.0, \"y\", 2.0))))\n\texpectParse(t, \"a:b:{x:1},a:b:{y:2},a:b:{z:3}\",\n\t\tm(\"a\", m(\"b\", m(\"x\", 1.0, \"y\", 2.0, \"z\", 3.0))))\n\n\texpectParse(t, \"a:b:c:{x:1},a:b:c:{y:2}\",\n\t\tm(\"a\", m(\"b\", m(\"c\", m(\"x\", 1.0, \"y\", 2.0)))))\n\texpectParse(t, \"a:b:c:{x:1},a:b:c:{y:2},a:b:c:{z:3}\",\n\t\tm(\"a\", m(\"b\", m(\"c\", m(\"x\", 1.0, \"y\", 2.0, \"z\", 3.0)))))\n}\n\n// --- Syntax error tests (from jsonic.test.js) ---\n\nfunc TestSyntaxErrors(t *testing.T) {\n\t// Bad close\n\texpectParseError(t, \"}\")\n\texpectParseError(t, \"]\")\n\n\t// Top level already is a map\n\texpectParseError(t, \"a:1,2\")\n\n\t// Values not valid inside map\n\texpectParseError(t, \"x:{1,2}\")\n}\n\n// --- Process-comment tests (from jsonic.test.js) ---\n\nfunc TestProcessComment(t *testing.T) {\n\texpectParse(t, \"a:q\\nb:w #X\\nc:r \\n\\nd:t\\n\\n#\",\n\t\tm(\"a\", \"q\", \"b\", \"w\", \"c\", \"r\", \"d\", \"t\"))\n}\n\n// --- NaN handling ---\n// NaN is now parsed as text \"NaN\" to match TypeScript behavior.\n// Use Value.Def to enable NaN as a value keyword if needed.\n\nfunc TestNaN(t *testing.T) {\n\tgot, err := Parse(\"NaN\")\n\tif err != nil {\n\t\tt.Fatalf(\"Parse(\\\"NaN\\\") error: %v\", err)\n\t}\n\ts, ok := got.(string)\n\tif !ok || s != \"NaN\" {\n\t\tt.Errorf(\"Parse(\\\"NaN\\\") expected string \\\"NaN\\\", got %v (%T)\", got, got)\n\t}\n}\n\n// --- Platform mismatch tests ---\n// These document behavior differences between Go and TypeScript.\n\nfunc TestPlatformMismatch_ArrayProperties(t *testing.T) {\n\t// PLATFORM MISMATCH: Array named properties\n\t//\n\t// In TypeScript/JavaScript: [a:1] creates an array with a named property.\n\t//   JSON.stringify([a:1]) → \"[]\"\n\t//   ({...[a:1]}) → {a:1}\n\t//\n\t// In Go: []any cannot have named properties.\n\t// Our parser creates the array but the pair-in-list behavior\n\t// sets properties on the array node which is a Go map operation\n\t// that doesn't work on slices. The element is effectively lost.\n\t//\n\t// This is a fundamental language platform mismatch.\n\t// In TS, arrays are objects and can have arbitrary named properties.\n\t// In Go, slices are strictly ordered collections.\n\n\tgot, err := Parse(\"[a:1]\")\n\tif err != nil {\n\t\tt.Fatalf(\"Parse(\\\"[a:1]\\\") error: %v\", err)\n\t}\n\t// In Go we get an empty array (the named property is lost)\n\tif arr, ok := got.([]any); ok {\n\t\tif len(arr) != 0 {\n\t\t\tt.Logf(\"MISMATCH NOTE: [a:1] produces %s (TS produces [] with .a=1 property)\",\n\t\t\t\tformatValue(got))\n\t\t}\n\t}\n}\n\nfunc TestPlatformMismatch_UndefinedVsNull(t *testing.T) {\n\t// PLATFORM MISMATCH: undefined vs null\n\t//\n\t// In TypeScript: Parse(\"\") returns undefined, Parse(\"null\") returns null.\n\t// These are distinct values.\n\t//\n\t// In Go: Both return nil. There is no Go equivalent of JavaScript's\n\t// undefined. Internally we use an Undefined sentinel during parsing,\n\t// but the public API returns nil for both cases.\n\t//\n\t// This means consumers cannot distinguish \"no value\" from \"null value\"\n\t// at the API level. For most practical uses this is acceptable.\n\n\temptyResult, err := Parse(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"Parse(\\\"\\\") error: %v\", err)\n\t}\n\tnullResult, err := Parse(\"null\")\n\tif err != nil {\n\t\tt.Fatalf(\"Parse(\\\"null\\\") error: %v\", err)\n\t}\n\tif emptyResult != nil {\n\t\tt.Errorf(\"Parse(\\\"\\\") should be nil, got %v\", emptyResult)\n\t}\n\tif nullResult != nil {\n\t\tt.Errorf(\"Parse(\\\"null\\\") should be nil, got %v\", nullResult)\n\t}\n\t// Both are nil in Go (TS would distinguish undefined from null)\n\tt.Logf(\"MISMATCH NOTE: Parse(\\\"\\\")=%v and Parse(\\\"null\\\")=%v are both nil in Go \"+\n\t\t\"(TS distinguishes undefined from null)\", emptyResult, nullResult)\n}\n\nfunc TestPlatformMismatch_NonStringInput(t *testing.T) {\n\t// PLATFORM MISMATCH: Non-string input\n\t//\n\t// In TypeScript: Jsonic({}) returns {}, Jsonic([]) returns [], etc.\n\t// Non-string inputs are passed through.\n\t//\n\t// In Go: Parse() only accepts strings. Non-string inputs require\n\t// a different API pattern. This is a deliberate design choice\n\t// since Go is statically typed and the function signature is\n\t// Parse(string) any.\n\n\tt.Logf(\"MISMATCH NOTE: Go Parse() only accepts strings. \" +\n\t\t\"TS Jsonic() passes through non-string inputs ({}, [], true, etc.)\")\n}\n\nfunc TestErrorFormat(t *testing.T) {\n\t// Verify error format matches TypeScript JsonicError output.\n\n\tt.Run(\"unterminated_string\", func(t *testing.T) {\n\t\t_, err := Parse(`\"unterminated`)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error\")\n\t\t}\n\t\tje := err.(*JsonicError)\n\t\tif je.Code != \"unterminated_string\" {\n\t\t\tt.Errorf(\"Code: got %q, want %q\", je.Code, \"unterminated_string\")\n\t\t}\n\t\tif je.Row != 1 || je.Col != 1 {\n\t\t\tt.Errorf(\"Position: got %d:%d, want 1:1\", je.Row, je.Col)\n\t\t}\n\t\t// Detail should match TS format: \"unterminated string: <src>\"\n\t\tif !strings.Contains(je.Detail, \"unterminated string:\") {\n\t\t\tt.Errorf(\"Detail should contain 'unterminated string:', got %q\", je.Detail)\n\t\t}\n\t\t// Error() should contain [jsonic/<code>] header\n\t\tmsg := je.Error()\n\t\tif !strings.Contains(msg, \"[jsonic/unterminated_string]:\") {\n\t\t\tt.Errorf(\"Error() should contain '[jsonic/unterminated_string]:', got:\\n%s\", msg)\n\t\t}\n\t\t// Error() should contain --> row:col\n\t\tif !strings.Contains(msg, \"--> 1:1\") {\n\t\t\tt.Errorf(\"Error() should contain '--> 1:1', got:\\n%s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"unterminated_comment\", func(t *testing.T) {\n\t\t_, err := Parse(\"/*\")\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error\")\n\t\t}\n\t\tje := err.(*JsonicError)\n\t\tif je.Code != \"unterminated_comment\" {\n\t\t\tt.Errorf(\"Code: got %q, want %q\", je.Code, \"unterminated_comment\")\n\t\t}\n\t\tif !strings.Contains(je.Detail, \"unterminated comment:\") {\n\t\t\tt.Errorf(\"Detail should contain 'unterminated comment:', got %q\", je.Detail)\n\t\t}\n\t\tmsg := je.Error()\n\t\tif !strings.Contains(msg, \"[jsonic/unterminated_comment]:\") {\n\t\t\tt.Errorf(\"Error() missing code header, got:\\n%s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"unexpected_close\", func(t *testing.T) {\n\t\t_, err := Parse(\"}\")\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error\")\n\t\t}\n\t\tje := err.(*JsonicError)\n\t\tif je.Code != \"unexpected\" {\n\t\t\tt.Errorf(\"Code: got %q, want %q\", je.Code, \"unexpected\")\n\t\t}\n\t\tif !strings.Contains(je.Detail, \"unexpected character(s):\") {\n\t\t\tt.Errorf(\"Detail should contain 'unexpected character(s):', got %q\", je.Detail)\n\t\t}\n\t\tmsg := je.Error()\n\t\tif !strings.Contains(msg, \"[jsonic/unexpected]:\") {\n\t\t\tt.Errorf(\"Error() missing code header, got:\\n%s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"multiline_source_extract\", func(t *testing.T) {\n\t\t// Match the TS test: error on line 11 with context lines\n\t\tsrc := \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n   }\"\n\t\t_, err := Parse(src)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error\")\n\t\t}\n\t\tje := err.(*JsonicError)\n\t\tmsg := je.Error()\n\t\t// Should show --> row:col\n\t\tif !strings.Contains(msg, \"--> 11:4\") {\n\t\t\tt.Errorf(\"Error() should show '--> 11:4', got:\\n%s\", msg)\n\t\t}\n\t\t// Should contain line numbers in the source extract\n\t\tif !strings.Contains(msg, \"11 |\") {\n\t\t\tt.Errorf(\"Error() should contain '11 |' line marker, got:\\n%s\", msg)\n\t\t}\n\t\t// Should contain caret marker\n\t\tif !strings.Contains(msg, \"^\") {\n\t\t\tt.Errorf(\"Error() should contain '^' caret marker, got:\\n%s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"multiline_with_context\", func(t *testing.T) {\n\t\t// Error in middle of source - verify context lines before and after\n\t\tsrc := \"a:1\\nb:2\\nc:3\\nd:\\\"unterminated\\ne:5\"\n\t\t_, err := Parse(src)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error\")\n\t\t}\n\t\tje := err.(*JsonicError)\n\t\tmsg := je.Error()\n\t\t// Should show context lines\n\t\tif !strings.Contains(msg, \"|\") {\n\t\t\tt.Errorf(\"Error() should contain '|' line markers, got:\\n%s\", msg)\n\t\t}\n\t\t// Should contain caret marker\n\t\tif !strings.Contains(msg, \"^\") {\n\t\t\tt.Errorf(\"Error() should contain '^' caret, got:\\n%s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"error_fields_match_ts\", func(t *testing.T) {\n\t\t// Verify all structured fields are present (matching TS JsonicError)\n\t\t_, err := Parse(`\"abc`)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error\")\n\t\t}\n\t\tje := err.(*JsonicError)\n\t\t// Code matches TS error code\n\t\tif je.Code == \"\" {\n\t\t\tt.Error(\"Code should not be empty\")\n\t\t}\n\t\t// Detail matches TS message format\n\t\tif je.Detail == \"\" {\n\t\t\tt.Error(\"Detail should not be empty\")\n\t\t}\n\t\t// Row is 1-based (matches TS rI / lineNumber)\n\t\tif je.Row < 1 {\n\t\t\tt.Errorf(\"Row should be >= 1, got %d\", je.Row)\n\t\t}\n\t\t// Col is 1-based (matches TS cI / columnNumber)\n\t\tif je.Col < 1 {\n\t\t\tt.Errorf(\"Col should be >= 1, got %d\", je.Col)\n\t\t}\n\t\t// Src contains the token text (matches TS token.src)\n\t\tif je.Src == \"\" {\n\t\t\tt.Error(\"Src should not be empty\")\n\t\t}\n\t})\n}\n\n// --- Lex-flag tests: disable individual lexers via Make() options ---\n// Ported from TS test/lex.test.js \"lex-flags\" section.\n\nfunc TestLexFlagCommentOff(t *testing.T) {\n\tj := Make(Options{Comment: &CommentOptions{Lex: boolPtr(false)}})\n\n\t// With comments disabled, # and // become part of text values.\n\tgot, err := j.Parse(\"a:1#b\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", \"1#b\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"comment off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a,1#b\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(\"a\", \"1#b\")\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"comment off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\nfunc TestLexFlagNumberOff(t *testing.T) {\n\tj := Make(Options{Number: &NumberOptions{Lex: boolPtr(false)}})\n\n\t// With numbers disabled, digits are parsed as text strings.\n\tgot, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", \"1\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"number off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a,1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(\"a\", \"1\")\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"number off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\nfunc TestLexFlagStringOff(t *testing.T) {\n\tj := Make(Options{String: &StringOptions{Lex: boolPtr(false)}})\n\n\t// With strings disabled, quote characters become part of text.\n\tgot, err := j.Parse(`a:\"a\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", `\"a\"`)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"string off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(`\"a\",1`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(`\"a\"`, 1.0)\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"string off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\nfunc TestLexFlagValueOff(t *testing.T) {\n\tj := Make(Options{Value: &ValueOptions{Lex: boolPtr(false)}})\n\n\t// With value matching disabled, true/false/null are parsed as text strings.\n\tgot, err := j.Parse(\"a:true\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", \"true\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"value off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a,null\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(\"a\", \"null\")\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"value off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\nfunc TestLexFlagSpaceOff(t *testing.T) {\n\tj := Make(Options{Space: &SpaceOptions{Lex: boolPtr(false)}})\n\n\t// With space lexing disabled, spaces become part of text values.\n\tgot, err := j.Parse(\"a :1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a \", 1.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"space off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a ,1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(\"a \", 1.0)\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"space off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\nfunc TestLexFlagLineOff(t *testing.T) {\n\tj := Make(Options{Line: &LineOptions{Lex: boolPtr(false)}})\n\n\t// With line lexing disabled, newlines become part of text values.\n\tgot, err := j.Parse(\"a:\\n1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", \"\\n1\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"line off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a,\\n1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedArr := a(\"a\", \"\\n1\")\n\tif !valuesEqual(stripRefs(got), expectedArr) {\n\t\tt.Errorf(\"line off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expectedArr))\n\t}\n}\n\nfunc TestLexFlagCommentDefOff(t *testing.T) {\n\t// Disable comments via def (setting each type to null/disabled).\n\t// Matches TS: { comment: { def: { hash: null, slash: false, multi: { lex: false } } } }\n\tj := Make(Options{Comment: &CommentOptions{\n\t\tDef: map[string]*CommentDef{\n\t\t\t\"hash\":  nil,\n\t\t\t\"slash\": {Line: true, Start: \"//\", Lex: boolPtr(false)},\n\t\t\t\"multi\": {Line: false, Start: \"/*\", End: \"*/\", Lex: boolPtr(false)},\n\t\t},\n\t}})\n\n\tgot, err := j.Parse(\"a: #b\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"a\", \"#b\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"comment def off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a: //b\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected = m(\"a\", \"//b\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"comment def off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n\n\tgot, err = j.Parse(\"a: /*b*/\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected = m(\"a\", \"/*b*/\")\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"comment def off: got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\nfunc TestLexFlagNumberHexOff(t *testing.T) {\n\tj := Make(Options{\n\t\tComment: &CommentOptions{Lex: boolPtr(false)},\n\t\tNumber:  &NumberOptions{Hex: boolPtr(false)},\n\t\tValue: &ValueOptions{\n\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\"true\":  {Val: true},\n\t\t\t\t\"false\": {Val: false},\n\t\t\t\t\"null\":  {Val: nil},\n\t\t\t\t\"yes\":   {Val: true},\n\t\t\t\t\"no\":    {Val: false},\n\t\t\t},\n\t\t},\n\t})\n\n\t// Custom value keywords work.\n\tgot, err := j.Parse(\"yes\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != true {\n\t\tt.Errorf(\"custom value: Parse(\\\"yes\\\") got %v, want true\", got)\n\t}\n\n\t// Hex disabled: 0xFF is text, not a number.\n\tgot, err = j.Parse(\"0xFF\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, isFloat := got.(float64); isFloat {\n\t\tt.Errorf(\"hex off: Parse(\\\"0xFF\\\") should not parse as number, got %v\", got)\n\t}\n}\n\n// --- Rule stack depth regression tests ---\n// Verify rule stack grows dynamically without panic under deep nesting.\n\nfunc TestDeepNesting(t *testing.T) {\n\t// Build deeply nested maps: {a:{a:{a:...{a:1}...}}}\n\tfor _, depth := range []int{50, 200, 500} {\n\t\tinput := strings.Repeat(\"{a:\", depth) + \"1\" + strings.Repeat(\"}\", depth)\n\t\t_, err := Parse(input)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"depth %d: Parse(deeply nested map) unexpected error: %v\", depth, err)\n\t\t}\n\t}\n\n\t// Build deeply nested arrays: [[[[...[1]...]]]\n\tfor _, depth := range []int{50, 200, 500} {\n\t\tinput := strings.Repeat(\"[\", depth) + \"1\" + strings.Repeat(\"]\", depth)\n\t\t_, err := Parse(input)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"depth %d: Parse(deeply nested array) unexpected error: %v\", depth, err)\n\t\t}\n\t}\n\n\t// Mixed deep nesting: {a:[{a:[...1...]}]}\n\tfor _, depth := range []int{50, 200} {\n\t\tinput := strings.Repeat(\"{a:[\", depth) + \"1\" + strings.Repeat(\"]}\", depth)\n\t\t_, err := Parse(input)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"depth %d: Parse(mixed nested) unexpected error: %v\", depth, err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "go/jsonic_test.go",
    "content": "package jsonic\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// tsvRow holds a row from a TSV file.\ntype tsvRow struct {\n\tcols   []string\n\tlineNo int\n}\n\n// loadTSV reads a TSV file and returns its rows (excluding the header).\nfunc loadTSV(path string) ([]tsvRow, error) {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\n\tvar rows []tsvRow\n\tscanner := bufio.NewScanner(f)\n\tlineNo := 0\n\tfor scanner.Scan() {\n\t\tlineNo++\n\t\tif lineNo == 1 {\n\t\t\tcontinue // skip header\n\t\t}\n\t\tline := scanner.Text()\n\t\tif line == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tcols := strings.Split(line, \"\\t\")\n\t\trows = append(rows, tsvRow{cols: cols, lineNo: lineNo})\n\t}\n\treturn rows, scanner.Err()\n}\n\n// parseExpected parses the expected JSON string into a Go value.\nfunc parseExpected(s string) (any, error) {\n\tif s == \"\" {\n\t\treturn nil, nil\n\t}\n\tvar val any\n\terr := json.Unmarshal([]byte(s), &val)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn val, nil\n}\n\n// normalizeValue normalizes parsed results for comparison:\n// float64 whole numbers should compare correctly with JSON unmarshaled values.\nfunc normalizeValue(v any) any {\n\tswitch val := v.(type) {\n\tcase map[string]any:\n\t\tresult := make(map[string]any)\n\t\tfor k, v := range val {\n\t\t\tresult[k] = normalizeValue(v)\n\t\t}\n\t\treturn result\n\tcase []any:\n\t\tresult := make([]any, len(val))\n\t\tfor i, v := range val {\n\t\t\tresult[i] = normalizeValue(v)\n\t\t}\n\t\treturn result\n\tcase float64:\n\t\t// Normalize -0 to 0\n\t\tif val == 0 {\n\t\t\treturn float64(0)\n\t\t}\n\t\treturn val\n\tdefault:\n\t\treturn v\n\t}\n}\n\n// valuesEqual compares two values deeply, handling float64 precision.\nfunc valuesEqual(got, expected any) bool {\n\tgot = normalizeValue(got)\n\texpected = normalizeValue(expected)\n\treturn deepCompare(got, expected)\n}\n\nfunc deepCompare(a, b any) bool {\n\tif a == nil && b == nil {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\n\tswitch av := a.(type) {\n\tcase map[string]any:\n\t\tbv, ok := b.(map[string]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av {\n\t\t\tbVal, ok := bv[k]\n\t\t\tif !ok {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif !deepCompare(v, bVal) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase []any:\n\t\tbv, ok := b.([]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av {\n\t\t\tif !deepCompare(av[i], bv[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase float64:\n\t\tbv, ok := b.(float64)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tif math.IsNaN(av) && math.IsNaN(bv) {\n\t\t\treturn true\n\t\t}\n\t\treturn av == bv\n\tcase string:\n\t\tbv, ok := b.(string)\n\t\treturn ok && av == bv\n\tcase bool:\n\t\tbv, ok := b.(bool)\n\t\treturn ok && av == bv\n\tdefault:\n\t\treturn reflect.DeepEqual(a, b)\n\t}\n}\n\n// formatValue formats a value for display in test output.\nfunc formatValue(v any) string {\n\tif v == nil {\n\t\treturn \"nil\"\n\t}\n\tb, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"%v\", v)\n\t}\n\treturn string(b)\n}\n\n// specDir returns the path to the spec directory.\nfunc specDir() string {\n\treturn filepath.Join(\"..\", \"test\", \"spec\")\n}\n\n// parserTSVFiles lists all parser-related TSV files (excluding utility-* files).\nvar parserTSVFiles = []string{\n\t\"jsonic-basic-json.tsv\",\n\t\"jsonic-basic-array-tree.tsv\",\n\t\"jsonic-basic-mixed-tree.tsv\",\n\t\"jsonic-basic-object-tree.tsv\",\n\t\"jsonic-funky-keys.tsv\",\n\t\"jsonic-process-array.tsv\",\n\t\"jsonic-process-implicit-object.tsv\",\n\t\"jsonic-process-mixed-nodes.tsv\",\n\t\"jsonic-process-object-tree.tsv\",\n\t\"jsonic-process-scalars.tsv\",\n\t\"jsonic-process-text.tsv\",\n\t\"jsonic-process-whitespace.tsv\",\n\t\"comma-implicit-comma.tsv\",\n\t\"comma-optional-comma.tsv\",\n\t\"feature-debug-cases.tsv\",\n\t\"feature-implicit-map.tsv\",\n\t\"feature-implicit-object.tsv\",\n\t\"feature-nested-space-pairs.tsv\",\n\t\"fv-arrays.tsv\",\n\t\"fv-comma.tsv\",\n\t\"fv-deep.tsv\",\n\t\"fv-drop-outs.tsv\",\n\t\"fv-numbers.tsv\",\n\t\"fv-subobj.tsv\",\n\t\"fv-types.tsv\",\n\t\"fv-works.tsv\",\n\t\"happy.tsv\",\n}\n\nfunc TestParserTSVFiles(t *testing.T) {\n\tfor _, file := range parserTSVFiles {\n\t\tt.Run(file, func(t *testing.T) {\n\t\t\tpath := filepath.Join(specDir(), file)\n\t\t\trows, err := loadTSV(path)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to load %s: %v\", file, err)\n\t\t\t}\n\n\t\t\tfor _, row := range rows {\n\t\t\t\tif len(row.cols) < 2 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tinput := row.cols[0]\n\t\t\t\texpectedStr := row.cols[1]\n\n\t\t\t\texpected, err := parseExpected(expectedStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"line %d: failed to parse expected %q: %v\", row.lineNo, expectedStr, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tgot, err := Parse(preprocessEscapes(input))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"line %d: Parse(%q) error: %v\", row.lineNo, input, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif !valuesEqual(got, expected) {\n\t\t\t\t\tt.Errorf(\"line %d: Parse(%q)\\n  got:      %s\\n  expected: %s\",\n\t\t\t\t\t\trow.lineNo, input, formatValue(got), formatValue(expected))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- Utility tests ---\n\nfunc TestUtilityDeep(t *testing.T) {\n\tpath := filepath.Join(specDir(), \"utility-deep.tsv\")\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load utility-deep.tsv: %v\", err)\n\t}\n\n\tfor _, row := range rows {\n\t\t// Columns: arg1, arg2, arg3, arg4, expected\n\t\t// Empty columns mean the argument is not provided\n\t\tif len(row.cols) < 5 {\n\t\t\tcontinue\n\t\t}\n\n\t\targs := make([]any, 0)\n\t\tfor i := 0; i < 4; i++ {\n\t\t\tcol := row.cols[i]\n\t\t\tif col == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tval, err := parseExpected(col)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"line %d: failed to parse arg%d %q: %v\", row.lineNo, i+1, col, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\targs = append(args, val)\n\t\t}\n\n\t\texpectedStr := row.cols[4]\n\t\texpected, err := parseExpected(expectedStr)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"line %d: failed to parse expected %q: %v\", row.lineNo, expectedStr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(args) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := Deep(args[0], args[1:]...)\n\n\t\tif !valuesEqual(got, expected) {\n\t\t\tt.Errorf(\"line %d: Deep(%s)\\n  got:      %s\\n  expected: %s\",\n\t\t\t\trow.lineNo, formatArgs(args), formatValue(got), formatValue(expected))\n\t\t}\n\t}\n}\n\nfunc TestUtilityStr(t *testing.T) {\n\tpath := filepath.Join(specDir(), \"utility-str.tsv\")\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load utility-str.tsv: %v\", err)\n\t}\n\n\tfor _, row := range rows {\n\t\t// Columns: input, maxlen, expected\n\t\tif len(row.cols) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tinputStr := row.cols[0]\n\t\tmaxlenStr := row.cols[1]\n\t\texpectedStr := row.cols[2]\n\n\t\t// Parse input (it's a JSON value)\n\t\tvar input any\n\t\tif inputStr != \"\" {\n\t\t\tif err := json.Unmarshal([]byte(inputStr), &input); err != nil {\n\t\t\t\tt.Errorf(\"line %d: failed to parse input %q: %v\", row.lineNo, inputStr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Parse maxlen\n\t\tmaxlen := 44 // default\n\t\tif maxlenStr != \"\" {\n\t\t\tml, err := strconv.Atoi(maxlenStr)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"line %d: failed to parse maxlen %q: %v\", row.lineNo, maxlenStr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmaxlen = ml\n\t\t}\n\n\t\tgot := Str(input, maxlen)\n\n\t\tif got != expectedStr {\n\t\t\tt.Errorf(\"line %d: Str(%s, %d)\\n  got:      %q\\n  expected: %q\",\n\t\t\t\trow.lineNo, inputStr, maxlen, got, expectedStr)\n\t\t}\n\t}\n}\n\nfunc TestUtilityStrInject(t *testing.T) {\n\tpath := filepath.Join(specDir(), \"utility-strinject.tsv\")\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load utility-strinject.tsv: %v\", err)\n\t}\n\n\tfor _, row := range rows {\n\t\t// Columns: template, values, expected\n\t\tif len(row.cols) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\ttemplate := row.cols[0]\n\t\tvaluesStr := row.cols[1]\n\t\texpectedStr := row.cols[2]\n\n\t\t// Parse values\n\t\tvar vals any\n\t\tif valuesStr != \"\" {\n\t\t\tif err := json.Unmarshal([]byte(valuesStr), &vals); err != nil {\n\t\t\t\tt.Errorf(\"line %d: failed to parse values %q: %v\", row.lineNo, valuesStr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tgot := StrInject(template, vals)\n\n\t\tif got != expectedStr {\n\t\t\tt.Errorf(\"line %d: StrInject(%q, %s)\\n  got:      %q\\n  expected: %q\",\n\t\t\t\trow.lineNo, template, valuesStr, got, expectedStr)\n\t\t}\n\t}\n}\n\nfunc TestUtilityModList(t *testing.T) {\n\tpath := filepath.Join(specDir(), \"utility-modlist.tsv\")\n\trows, err := loadTSV(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load utility-modlist.tsv: %v\", err)\n\t}\n\n\tfor _, row := range rows {\n\t\t// Columns: input, opts, expected\n\t\tif len(row.cols) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tinputStr := row.cols[0]\n\t\toptsStr := row.cols[1]\n\t\texpectedStr := row.cols[2]\n\n\t\t// Parse input array\n\t\tvar input []any\n\t\tif err := json.Unmarshal([]byte(inputStr), &input); err != nil {\n\t\t\tt.Errorf(\"line %d: failed to parse input %q: %v\", row.lineNo, inputStr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Parse opts\n\t\tvar opts *ModListOpts\n\t\tif optsStr != \"\" {\n\t\t\tvar rawOpts map[string]any\n\t\t\tif err := json.Unmarshal([]byte(optsStr), &rawOpts); err != nil {\n\t\t\t\tt.Errorf(\"line %d: failed to parse opts %q: %v\", row.lineNo, optsStr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\topts = &ModListOpts{}\n\t\t\tif del, ok := rawOpts[\"delete\"]; ok {\n\t\t\t\tif delArr, ok := del.([]any); ok {\n\t\t\t\t\tfor _, d := range delArr {\n\t\t\t\t\t\tif df, ok := d.(float64); ok {\n\t\t\t\t\t\t\topts.Delete = append(opts.Delete, int(df))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif mv, ok := rawOpts[\"move\"]; ok {\n\t\t\t\tif mvArr, ok := mv.([]any); ok {\n\t\t\t\t\tfor _, m := range mvArr {\n\t\t\t\t\t\tif mf, ok := m.(float64); ok {\n\t\t\t\t\t\t\topts.Move = append(opts.Move, int(mf))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Parse expected\n\t\tvar expected []any\n\t\tif err := json.Unmarshal([]byte(expectedStr), &expected); err != nil {\n\t\t\tt.Errorf(\"line %d: failed to parse expected %q: %v\", row.lineNo, expectedStr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tgot := ModList(input, opts)\n\n\t\tif !valuesEqual(got, expected) {\n\t\t\tt.Errorf(\"line %d: ModList(%s, %s)\\n  got:      %s\\n  expected: %s\",\n\t\t\t\trow.lineNo, inputStr, optsStr, formatValue(got), formatValue(expected))\n\t\t}\n\t}\n}\n\nfunc formatArgs(args []any) string {\n\tparts := make([]string, len(args))\n\tfor i, a := range args {\n\t\tparts[i] = formatValue(a)\n\t}\n\treturn strings.Join(parts, \", \")\n}\n"
  },
  {
    "path": "go/lexer.go",
    "content": "package jsonic\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// MatchValueEntry holds a resolved match.value matcher entry.\n// Name carries the user-supplied name so the slice can be sorted\n// deterministically at configure time (independent of the map iteration\n// order that built it).\ntype MatchValueEntry struct {\n\tName  string\n\tMatch *regexp.Regexp\n\tVal   func([]string) any\n}\n\n// ValueDefEntry is a name-tagged wrapper around *ValueDef used to sort\n// regex-based value definitions at configure time so iteration during\n// lexing is deterministic (mirrors TS cfg.value.defre being a sorted array).\ntype ValueDefEntry struct {\n\tName string\n\tDef  *ValueDef\n}\n\n// MatchTokenEntry pairs a Tin with its match regexp, pre-sorted by Tin at\n// configure time so MatchTokens iteration during lexing is deterministic.\ntype MatchTokenEntry struct {\n\tTin   Tin\n\tMatch *regexp.Regexp\n}\n\n// Lex is the lexer that produces tokens from source text.\ntype Lex struct {\n\tSrc    string\n\tCtx    *Context  // Parse context (includes Ctx.Rule for context-sensitive lexing)\n\tpnt    Point\n\tend    *Token    // End-of-source token (cached)\n\ttokens []*Token  // Lookahead token queue\n\tConfig *LexConfig\n\tErr    error     // First error encountered during lexing\n}\n\n// LexConfig holds lexer configuration.\ntype LexConfig struct {\n\t// Lex enable/disable flags (matching TS options.*.lex)\n\tFixedLex   bool // Enable fixed token recognition. Default: true.\n\tSpaceLex   bool // Enable space lexing. Default: true.\n\tLineLex    bool // Enable line lexing. Default: true.\n\tTextLex    bool // Enable text matching. Default: true.\n\tNumberLex  bool // Enable number matching. Default: true.\n\tCommentLex bool // Enable comment matching. Default: true.\n\tStringLex  bool // Enable string matching. Default: true.\n\tValueLex   bool // Enable value keyword matching. Default: true.\n\n\tStringChars  map[rune]bool // Quote characters\n\tMultiChars   map[rune]bool // Multiline quote characters\n\tEscapeChar   rune\n\tEscapeMap    map[string]string // Custom escape mappings, e.g. {\"n\": \"\\n\"}.\n\tSpaceChars   map[rune]bool\n\tLineChars    map[rune]bool\n\tRowChars     map[rune]bool\n\tCommentLine  []string // Line comment starters: \"#\", \"//\"\n\tCommentBlock [][2]string // Block comment: [start, end] pairs\n\tNumberHex    bool\n\tNumberOct    bool\n\tNumberBin    bool\n\tNumberSep    rune // Separator char (underscore)\n\tAllowUnknownEscape bool\n\tStringAbandon      bool            // On string error, return nil instead of bad token.\n\tStringReplace      map[rune]string // Character replacements during string scanning.\n\n\t// Value definitions: keyword → value (e.g. \"true\" → true)\n\t// If nil, uses built-in defaults (true, false, null).\n\tValueDef map[string]any\n\n\t// ValueDefRe holds regex-processed value definitions (TS: cfg.value.defre).\n\t// Sorted by name at configure time for deterministic iteration.\n\tValueDefRe []*ValueDefEntry\n\n\t// Match options (TS: cfg.match)\n\tMatchLex          bool                          // Enable custom matching. Default: false.\n\tMatchTokens       map[Tin]*regexp.Regexp         // Custom token tin → regexp (storage).\n\tMatchTokensSorted []*MatchTokenEntry             // Sorted-by-tin view for deterministic iteration.\n\tMatchValues       []*MatchValueEntry             // Custom value matchers, sorted by name.\n\n\t// Number options\n\tNumberExclude func(string) bool // Exclude certain number-like strings.\n\n\t// Line options\n\tLineSingle bool // Generate separate tokens per newline.\n\n\t// Text options\n\tTextModify []ValModifier // Pipeline of text value modifiers.\n\n\t// Comment options (per-def eatline stored on CommentDef)\n\tCommentLineEatLine  map[string]bool // Line comment starter → eatline flag.\n\tCommentBlockEatLine map[string]bool // Block comment start → eatline flag.\n\n\t// Comment suffix terminators — optional, non-consuming.\n\t// Per start-marker, a list of string prefixes that terminate the\n\t// comment body when encountered. The suffix remains in the input.\n\tCommentLineSuffixes  map[string][]string\n\tCommentBlockSuffixes map[string][]string\n\n\t// LexMatcher-form suffix terminators. Invoked at each candidate\n\t// position inside the comment body; a non-nil returned token signals\n\t// termination at that offset. The returned token is discarded.\n\tCommentLineSuffixFuncs  map[string]LexMatcher\n\tCommentBlockSuffixFuncs map[string]LexMatcher\n\n\t// Color carries the resolved ANSI escape codes for error formatting.\n\t// Populated from Options.Color by buildConfig.\n\tColor ColorConfig\n\n\t// Map/List options\n\tMapExtend    bool         // Deep-merge duplicate keys. Default: true.\n\tMapMerge     MapMergeFunc // Custom merge function for duplicate keys.\n\tMapChild     bool         // Parse bare colon in maps as child$ key. Default: false.\n\tListProperty bool         // Allow named properties in arrays. Default: true.\n\tListPair     bool         // Push pairs as object elements in arrays. Default: false.\n\n\t// Safe options\n\tSafeKey bool // Prevent __proto__ keys. Default: true.\n\n\t// Rule options\n\tFinishRule bool // Auto-close unclosed structures at EOF\n\tRuleStart  string // Starting rule name. Default: \"val\".\n\n\t// EnderChars lists additional characters that end text and number tokens.\n\tEnderChars map[rune]bool\n\n\t// Per-instance fixed token map (cloned from global FixedTokens).\n\t// Plugins can add custom fixed tokens here. Supports multi-char keys.\n\tFixedTokens map[string]Tin\n\n\t// FixedSorted is the list of fixed token strings sorted by length (longest first).\n\t// Rebuilt by SortFixedTokens() after adding custom tokens.\n\tFixedSorted []string\n\n\t// Custom token names: Tin → name for plugin-defined tokens.\n\tTinNames map[Tin]string\n\n\t// Custom lexer matchers added by plugins, sorted by priority.\n\tCustomMatchers []*MatcherEntry\n\n\t// TextInfo wraps string/text output values in Text structs.\n\tTextInfo bool\n\n\t// ListRef wraps list output values in ListRef structs.\n\tListRef bool\n\n\t// ListChild enables bare colon (:value) syntax in lists to set a child value.\n\tListChild bool\n\n\t// MapRef wraps map output values in MapRef structs.\n\tMapRef bool\n\n\t// InfoMarker is the key name to protect from user data when info options\n\t// are enabled. Keys matching this value are dropped during parsing.\n\t// Matches TS cfg.info.marker. Empty string means no protection.\n\tInfoMarker string\n\n\t// IgnoreSet is the per-instance set of token Tins to skip during lexing.\n\t// Matches TS cfg.tokenSetTins.IGNORE. Plugins can customize this per-instance.\n\tIgnoreSet map[Tin]bool\n\n\t// ValSet is the per-instance VAL token set (text, number, string, value).\n\t// Matches TS cfg.tokenSet.VAL. Plugins can customize this per-instance.\n\tValSet []Tin\n\n\t// KeySet is the per-instance KEY token set (text, number, string, value).\n\t// Matches TS cfg.tokenSet.KEY. Plugins can customize this per-instance.\n\tKeySet []Tin\n\n\t// ParsePrepare hooks called before parsing begins.\n\tParsePrepare []func(ctx *Context)\n\n\t// ResultFail is a list of values that are treated as parse failures.\n\tResultFail []any\n\n\t// LexCheck callbacks allow plugins to intercept and override matchers.\n\t// Each returns nil to continue normal matching, or a LexCheckResult to short-circuit.\n\tFixedCheck   LexCheck\n\tSpaceCheck   LexCheck\n\tLineCheck    LexCheck\n\tStringCheck  LexCheck\n\tCommentCheck LexCheck\n\tNumberCheck  LexCheck\n\tTextCheck    LexCheck\n\tMatchCheck   LexCheck\n}\n\n// ColorConfig is the resolved ANSI-escape palette used by the error\n// formatter. Active false means all codes are emitted as empty strings.\ntype ColorConfig struct {\n\tActive bool\n\tReset  string\n\tHi     string\n\tLo     string\n\tLine   string\n}\n\n// Codes returns the four escape sequences the formatter needs as plain\n// strings. When Active is false they are all empty, so the formatter\n// can append them unconditionally.\nfunc (c ColorConfig) Codes() (hi, lo, line, reset string) {\n\tif !c.Active {\n\t\treturn \"\", \"\", \"\", \"\"\n\t}\n\treturn c.Hi, c.Lo, c.Line, c.Reset\n}\n\n// LexCheck is a function that can intercept a matcher before it runs.\n// Return nil to continue normal matching, or a LexCheckResult to override.\ntype LexCheck func(lex *Lex) *LexCheckResult\n\n// LexCheckResult controls matcher behavior from a LexCheck callback.\ntype LexCheckResult struct {\n\tDone  bool   // If true, use Token as the match result (even if nil).\n\tToken *Token // The token to return (nil means \"no match\").\n}\n\n// DefaultLexConfig returns the default lexer configuration matching jsonic defaults.\nfunc DefaultLexConfig() *LexConfig {\n\treturn &LexConfig{\n\t\tFixedLex:     true,\n\t\tSpaceLex:     true,\n\t\tLineLex:      true,\n\t\tTextLex:      true,\n\t\tNumberLex:    true,\n\t\tCommentLex:   true,\n\t\tStringLex:    true,\n\t\tValueLex:     true,\n\n\t\tStringChars:        map[rune]bool{'\\'': true, '\"': true, '`': true},\n\t\tMultiChars:         map[rune]bool{'`': true},\n\t\tEscapeChar:         '\\\\',\n\t\tSpaceChars:         map[rune]bool{' ': true, '\\t': true},\n\t\tLineChars:          map[rune]bool{'\\r': true, '\\n': true},\n\t\tRowChars:           map[rune]bool{'\\n': true},\n\t\tCommentLine:        []string{\"#\", \"//\"},\n\t\tCommentBlock:       [][2]string{{\"/*\", \"*/\"}},\n\t\tNumberHex:          true,\n\t\tNumberOct:          true,\n\t\tNumberBin:          true,\n\t\tNumberSep:          '_',\n\t\tAllowUnknownEscape: true,\n\n\t\tMapExtend:    true,\n\t\tListProperty: true,\n\t\tSafeKey:      true,\n\n\t\tFinishRule: true,\n\t\tRuleStart:  \"val\",\n\n\t\tIgnoreSet: map[Tin]bool{TinSP: true, TinLN: true, TinCM: true},\n\t\tValSet:    []Tin{TinTX, TinNR, TinST, TinVL},\n\t\tKeySet:    []Tin{TinTX, TinNR, TinST, TinVL},\n\n\t\tFixedTokens: map[string]Tin{\n\t\t\t\"{\": TinOB, \"}\": TinCB,\n\t\t\t\"[\": TinOS, \"]\": TinCS,\n\t\t\t\":\": TinCL, \",\": TinCA,\n\t\t},\n\t\tFixedSorted: []string{\"{\", \"}\", \"[\", \"]\", \":\", \",\"},\n\t}\n}\n\n// SortFixedTokens rebuilds FixedSorted from FixedTokens, sorted by length descending.\n// Call this after adding multi-char fixed tokens to ensure longest-match-first behavior.\nfunc (cfg *LexConfig) SortFixedTokens() {\n\tsorted := make([]string, 0, len(cfg.FixedTokens))\n\tfor k := range cfg.FixedTokens {\n\t\tsorted = append(sorted, k)\n\t}\n\tsort.Slice(sorted, func(i, j int) bool {\n\t\tif len(sorted[i]) != len(sorted[j]) {\n\t\t\treturn len(sorted[i]) > len(sorted[j]) // longer first\n\t\t}\n\t\treturn sorted[i] < sorted[j] // stable tie-break\n\t})\n\tcfg.FixedSorted = sorted\n}\n\n// RebuildMatchTokensSorted projects MatchTokens (map[Tin]*regexp.Regexp) into\n// MatchTokensSorted ([]*MatchTokenEntry) in ascending Tin order. Call this\n// after any mutation of MatchTokens so the lexer can iterate deterministically\n// without sorting during parse.\nfunc (cfg *LexConfig) RebuildMatchTokensSorted() {\n\tsorted := make([]*MatchTokenEntry, 0, len(cfg.MatchTokens))\n\tfor tin, re := range cfg.MatchTokens {\n\t\tsorted = append(sorted, &MatchTokenEntry{Tin: tin, Match: re})\n\t}\n\tsort.Slice(sorted, func(i, j int) bool {\n\t\treturn sorted[i].Tin < sorted[j].Tin\n\t})\n\tcfg.MatchTokensSorted = sorted\n}\n\n// NewLex creates a new lexer for the given source.\nfunc NewLex(src string, cfg *LexConfig) *Lex {\n\treturn &Lex{\n\t\tSrc:    src,\n\t\tpnt:    Point{Len: len(src), SI: 0, RI: 1, CI: 1},\n\t\tConfig: cfg,\n\t}\n}\n\n// Cursor returns a pointer to the lexer's current position.\n// Custom matchers use this to read and advance the position.\nfunc (l *Lex) Cursor() *Point {\n\treturn &l.pnt\n}\n\n// Token creates a new token at the current point.\nfunc (l *Lex) Token(name string, tin Tin, val any, src string) *Token {\n\treturn MakeToken(name, tin, val, src, l.pnt)\n}\n\n// Fwd returns a forward-looking substring from the current position.\n// maxlen limits the length of the returned string.\n// Matches TS lex.fwd.\nfunc (l *Lex) Fwd(maxlen int) string {\n\tsi := l.pnt.SI\n\tend := si + maxlen\n\tif end > l.pnt.Len {\n\t\tend = l.pnt.Len\n\t}\n\tif si >= end {\n\t\treturn \"\"\n\t}\n\treturn l.Src[si:end]\n}\n\n// Bad creates an error token at the current position.\n// Matches TS lex.bad(why, pstart, pend).\nfunc (l *Lex) Bad(why string) *Token {\n\ttkn := MakeToken(\"#BD\", TinBD, nil, \"\", l.pnt)\n\ttkn.Why = why\n\ttkn.Err = why\n\treturn tkn\n}\n\n// Next returns the next non-IGNORE token, passing the current parsing rule\n// to custom matchers for context-sensitive lexing.\n// On error (unterminated string, unterminated comment, unexpected character),\n// the error is stored in l.Err and a ZZ (end) token is returned to allow\n// the parser to wind down gracefully.\nfunc (l *Lex) Next(rule ...*Rule) *Token {\n\tvar r *Rule\n\tif len(rule) > 0 {\n\t\tr = rule[0]\n\t}\n\tif l.Ctx != nil && r != nil {\n\t\tl.Ctx.Rule = r\n\t}\n\tfor {\n\t\t// If an error has already occurred, return end-of-source to stop parsing\n\t\tif l.Err != nil {\n\t\t\treturn &Token{Name: \"#ZZ\", Tin: TinZZ, Val: Undefined, SI: l.pnt.SI, RI: l.pnt.RI, CI: l.pnt.CI}\n\t\t}\n\n\t\ttkn := l.nextRaw(r)\n\t\tif tkn == nil {\n\t\t\tsrc := \"\"\n\t\t\tif l.pnt.SI < len(l.Src) {\n\t\t\t\tsrc = string(l.Src[l.pnt.SI])\n\t\t\t}\n\t\t\tje := makeJsonicError(\"unexpected\", src, l.Src, l.pnt.SI, l.pnt.RI, l.pnt.CI)\n\t\t\tif l.Config != nil {\n\t\t\t\tje.color = l.Config.Color\n\t\t\t}\n\t\t\tl.Err = je\n\t\t\treturn &Token{Name: \"#ZZ\", Tin: TinZZ, Val: Undefined, SI: l.pnt.SI, RI: l.pnt.RI, CI: l.pnt.CI}\n\t\t}\n\t\t// Bad token → store error and return end-of-source\n\t\tif tkn.Tin == TinBD {\n\t\t\tje := makeJsonicError(tkn.Why, tkn.Src, l.Src, tkn.SI, tkn.RI, tkn.CI)\n\t\t\tif l.Config != nil {\n\t\t\t\tje.color = l.Config.Color\n\t\t\t}\n\t\t\tl.Err = je\n\t\t\treturn &Token{Name: \"#ZZ\", Tin: TinZZ, Val: Undefined, SI: tkn.SI, RI: tkn.RI, CI: tkn.CI}\n\t\t}\n\t\t// Skip IGNORE tokens (per-instance set, matching TS cfg.tokenSetTins.IGNORE)\n\t\tif l.Config.IgnoreSet[tkn.Tin] {\n\t\t\tcontinue\n\t\t}\n\t\treturn tkn\n\t}\n}\n\n// nextRaw returns the next raw token (including IGNORE tokens).\n// The rule parameter is passed to custom matchers for context-sensitive lexing.\nfunc (l *Lex) nextRaw(rule *Rule) *Token {\n\t// Return cached end token\n\tif l.end != nil {\n\t\treturn l.end\n\t}\n\n\t// Return queued lookahead tokens\n\tif len(l.tokens) > 0 {\n\t\ttkn := l.tokens[0]\n\t\tl.tokens = l.tokens[1:]\n\t\treturn tkn\n\t}\n\n\t// End of source\n\tif l.pnt.SI >= l.pnt.Len {\n\t\tl.end = l.Token(\"#ZZ\", TinZZ, Undefined, \"\")\n\t\treturn l.end\n\t}\n\n\t// Try matchers in order (matching TS lex.match ordering):\n\t// match(1e6), fixed(2e6), space(3e6), line(4e6), string(5e6), comment(6e6), number(7e6), text(8e6)\n\t// Custom matchers are interleaved by priority (already sorted).\n\n\tcI := 0 // index into CustomMatchers\n\n\t// Run custom matchers with priority before match (< 1e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 1000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\t// Match matcher (priority 1e6) — regexp-based token and value matching.\n\tif l.Config.MatchLex {\n\t\tif l.Config.MatchCheck != nil {\n\t\t\tif cr := l.Config.MatchCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil {\n\t\t\t\t\treturn cr.Token\n\t\t\t\t}\n\t\t\t} else if tkn := l.matchMatch(rule); tkn != nil {\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t} else if tkn := l.matchMatch(rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t}\n\n\t// Run custom matchers with priority before fixed (< 2e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 2000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.FixedLex {\n\t\tif l.Config.FixedCheck != nil {\n\t\t\tif cr := l.Config.FixedCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchFixed(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchFixed(); tkn != nil { return tkn }\n\t}\n\n\t// Run custom matchers with priority before space (< 3e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 3000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.SpaceLex {\n\t\tif l.Config.SpaceCheck != nil {\n\t\t\tif cr := l.Config.SpaceCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchSpace(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchSpace(); tkn != nil { return tkn }\n\t}\n\n\t// Run custom matchers with priority before line (< 4e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 4000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.LineLex {\n\t\tif l.Config.LineCheck != nil {\n\t\t\tif cr := l.Config.LineCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchLine(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchLine(); tkn != nil { return tkn }\n\t}\n\n\t// Run custom matchers with priority before string (< 5e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 5000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.StringLex {\n\t\tif l.Config.StringCheck != nil {\n\t\t\tif cr := l.Config.StringCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchString(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchString(); tkn != nil { return tkn }\n\t}\n\n\t// Run custom matchers with priority before comment (< 6e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 6000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.CommentLex {\n\t\tif l.Config.CommentCheck != nil {\n\t\t\tif cr := l.Config.CommentCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchComment(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchComment(); tkn != nil { return tkn }\n\t}\n\n\t// Run custom matchers with priority before number (< 7e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 7000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.NumberLex {\n\t\tif l.Config.NumberCheck != nil {\n\t\t\tif cr := l.Config.NumberCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchNumber(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchNumber(); tkn != nil { return tkn }\n\t}\n\n\t// Run custom matchers with priority before text (< 8e6).\n\tfor cI < len(l.Config.CustomMatchers) && l.Config.CustomMatchers[cI].Priority < 8000000 {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\tif l.Config.TextLex || l.Config.ValueLex {\n\t\tif l.Config.TextCheck != nil {\n\t\t\tif cr := l.Config.TextCheck(l); cr != nil && cr.Done {\n\t\t\t\tif cr.Token != nil { return cr.Token }\n\t\t\t} else if tkn := l.matchText(); tkn != nil { return tkn }\n\t\t} else if tkn := l.matchText(); tkn != nil { return tkn }\n\t}\n\n\t// Run remaining custom matchers (priority >= 8e6).\n\tfor cI < len(l.Config.CustomMatchers) {\n\t\tif tkn := l.Config.CustomMatchers[cI].Match(l, rule); tkn != nil {\n\t\t\treturn tkn\n\t\t}\n\t\tcI++\n\t}\n\n\t// No matcher matched\n\treturn nil\n}\n\n// matchMatch implements the match matcher (TS: makeMatchMatcher at priority 1e6).\n// Handles both match.value (regexp → #VL) and match.token (regexp → custom token).\nfunc (l *Lex) matchMatch(rule *Rule) *Token {\n\tif l.pnt.SI >= l.pnt.Len {\n\t\treturn nil\n\t}\n\n\tfwd := l.Src[l.pnt.SI:]\n\n\t// Match values first (TS: valueMatchers loop).\n\tfor _, vm := range l.Config.MatchValues {\n\t\tif vm.Match != nil {\n\t\t\tres := vm.Match.FindStringSubmatch(fwd)\n\t\t\tif res != nil && len(res[0]) > 0 {\n\t\t\t\tmsrc := res[0]\n\t\t\t\tmlen := len(msrc)\n\t\t\t\tvar val any\n\t\t\t\tif vm.Val != nil {\n\t\t\t\t\tval = vm.Val(res)\n\t\t\t\t} else {\n\t\t\t\t\tval = msrc\n\t\t\t\t}\n\t\t\t\ttkn := l.Token(\"#VL\", TinVL, val, msrc)\n\t\t\t\tl.pnt.SI += mlen\n\t\t\t\tl.pnt.CI += mlen\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t}\n\t}\n\n\t// Match tokens (TS: tokenMatchers loop).\n\t// Only match if the token type is expected in the current rule position.\n\tif rule != nil && rule.Spec != nil {\n\t\tvar alts []*AltSpec\n\t\tif rule.State == OPEN {\n\t\t\talts = rule.Spec.Open\n\t\t} else {\n\t\t\talts = rule.Spec.Close\n\t\t}\n\n\t\tfor _, mt := range l.Config.MatchTokensSorted {\n\t\t\ttin, re := mt.Tin, mt.Match\n\t\t\tif re == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Check if this Tin is expected at position 0 in any alt.\n\t\t\texpected := false\n\t\t\tfor _, alt := range alts {\n\t\t\t\tif len(alt.S) > 0 && tinMatch(tin, alt.S[0]) {\n\t\t\t\t\texpected = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !expected {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres := re.FindString(fwd)\n\t\t\tif res != \"\" {\n\t\t\t\tname := l.tinNameFor(tin)\n\t\t\t\ttkn := l.Token(name, tin, res, res)\n\t\t\t\tl.pnt.SI += len(res)\n\t\t\t\tl.pnt.CI += len(res)\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (l *Lex) bad(why string, pstart, pend int) *Token {\n\tsrc := \"\"\n\tif pstart >= 0 && pstart < len(l.Src) && pend <= len(l.Src) {\n\t\tsrc = l.Src[pstart:pend]\n\t} else if l.pnt.SI < len(l.Src) {\n\t\tsrc = string(l.Src[l.pnt.SI])\n\t}\n\ttkn := l.Token(\"#BD\", TinBD, nil, src)\n\ttkn.Why = why\n\treturn tkn\n}\n\n// matchFixed matches fixed tokens, including multi-character tokens.\n// Tokens are tried longest-first to ensure greedy matching (e.g. \"=>\" before \"=\").\nfunc (l *Lex) matchFixed() *Token {\n\tif l.pnt.SI >= l.pnt.Len {\n\t\treturn nil\n\t}\n\tremaining := l.Src[l.pnt.SI:]\n\n\t// Use sorted list for longest-match-first. Fall back to single-char lookup\n\t// if no sorted list (e.g. standalone lexer without Jsonic).\n\tif len(l.Config.FixedSorted) > 0 {\n\t\tfor _, fs := range l.Config.FixedSorted {\n\t\t\tif strings.HasPrefix(remaining, fs) {\n\t\t\t\ttin := l.Config.FixedTokens[fs]\n\t\t\t\ttkn := l.Token(l.tinNameFor(tin), tin, nil, fs)\n\t\t\t\tl.pnt.SI += len(fs)\n\t\t\t\tl.pnt.CI += len(fs)\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Fallback: single-char lookup.\n\tsrc := string(l.Src[l.pnt.SI])\n\ttin, ok := l.Config.FixedTokens[src]\n\tif !ok {\n\t\treturn nil\n\t}\n\ttkn := l.Token(l.tinNameFor(tin), tin, nil, src)\n\tl.pnt.SI++\n\tl.pnt.CI++\n\treturn tkn\n}\n\n// matchSpace matches space and tab characters.\nfunc (l *Lex) matchSpace() *Token {\n\tsI := l.pnt.SI\n\tcI := l.pnt.CI\n\tfor sI < l.pnt.Len && l.Config.SpaceChars[rune(l.Src[sI])] {\n\t\tsI++\n\t\tcI++\n\t}\n\tif sI > l.pnt.SI {\n\t\tsrc := l.Src[l.pnt.SI:sI]\n\t\ttkn := l.Token(\"#SP\", TinSP, nil, src)\n\t\tl.pnt.SI = sI\n\t\tl.pnt.CI = cI\n\t\treturn tkn\n\t}\n\treturn nil\n}\n\n// matchLine matches line ending characters (\\r, \\n).\n// When LineSingle is true, generates separate tokens for each newline sequence.\nfunc (l *Lex) matchLine() *Token {\n\tsI := l.pnt.SI\n\tif sI >= l.pnt.Len || !l.Config.LineChars[rune(l.Src[sI])] {\n\t\treturn nil\n\t}\n\n\trI := l.pnt.RI\n\n\tif l.Config.LineSingle {\n\t\t// Single mode: consume one newline sequence (\\r\\n or \\n or \\r)\n\t\tch := l.Src[sI]\n\t\tsI++\n\t\tif l.Config.RowChars[rune(ch)] {\n\t\t\trI++\n\t\t}\n\t\t// Handle \\r\\n as a single sequence\n\t\tif ch == '\\r' && sI < l.pnt.Len && l.Src[sI] == '\\n' {\n\t\t\tif l.Config.RowChars['\\n'] {\n\t\t\t\t// \\r\\n counts as one row\n\t\t\t}\n\t\t\tsI++\n\t\t}\n\t\tsrc := l.Src[l.pnt.SI:sI]\n\t\ttkn := l.Token(\"#LN\", TinLN, nil, src)\n\t\tl.pnt.SI = sI\n\t\tl.pnt.RI = rI\n\t\tl.pnt.CI = 1\n\t\treturn tkn\n\t}\n\n\t// Default: consume all consecutive line characters into one token\n\tfor sI < l.pnt.Len && l.Config.LineChars[rune(l.Src[sI])] {\n\t\tif l.Config.RowChars[rune(l.Src[sI])] {\n\t\t\trI++\n\t\t}\n\t\tsI++\n\t}\n\tsrc := l.Src[l.pnt.SI:sI]\n\ttkn := l.Token(\"#LN\", TinLN, nil, src)\n\tl.pnt.SI = sI\n\tl.pnt.RI = rI\n\tl.pnt.CI = 1\n\treturn tkn\n}\n\n// matchComment matches line comments (# //) and block comments (/* */).\nfunc (l *Lex) matchComment() *Token {\n\tfwd := l.Src[l.pnt.SI:]\n\n\t// Line comments\n\tfor _, start := range l.Config.CommentLine {\n\t\tif strings.HasPrefix(fwd, start) {\n\t\t\tsuffixes := l.Config.CommentLineSuffixes[start]\n\t\t\tsuffixFn := l.Config.CommentLineSuffixFuncs[start]\n\t\t\tfI := len(start)\n\t\t\tcI := l.pnt.CI + len(start)\n\t\t\tsuffixLen := 0\n\t\t\tfor fI < len(fwd) && !l.Config.LineChars[rune(fwd[fI])] {\n\t\t\t\tif n := commentSuffixMatch(fwd, fI, suffixes); n > 0 {\n\t\t\t\t\tsuffixLen = n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif n := commentSuffixFnMatch(l, fI, suffixFn); n > 0 {\n\t\t\t\t\tsuffixLen = n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcI++\n\t\t\t\tfI++\n\t\t\t}\n\t\t\tif suffixLen > 0 {\n\t\t\t\t// Consume the suffix as part of the comment body.\n\t\t\t\tfI += suffixLen\n\t\t\t\tcI += suffixLen\n\t\t\t\tsrc := fwd[:fI]\n\t\t\t\ttkn := l.Token(\"#CM\", TinCM, nil, src)\n\t\t\t\tl.pnt.SI += len(src)\n\t\t\t\tl.pnt.CI = cI\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t\t// EatLine: also consume trailing line characters, only when\n\t\t\t// the comment terminated at a line-char (not via suffix).\n\t\t\tatLineChar := fI < len(fwd) && l.Config.LineChars[rune(fwd[fI])]\n\t\t\tif atLineChar && l.Config.CommentLineEatLine[start] {\n\t\t\t\trI := l.pnt.RI\n\t\t\t\tfor fI < len(fwd) && l.Config.LineChars[rune(fwd[fI])] {\n\t\t\t\t\tif l.Config.RowChars[rune(fwd[fI])] {\n\t\t\t\t\t\trI++\n\t\t\t\t\t}\n\t\t\t\t\tfI++\n\t\t\t\t}\n\t\t\t\tsrc := fwd[:fI]\n\t\t\t\ttkn := l.Token(\"#CM\", TinCM, nil, src)\n\t\t\t\tl.pnt.SI += len(src)\n\t\t\t\tl.pnt.RI = rI\n\t\t\t\tl.pnt.CI = 1\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t\tsrc := fwd[:fI]\n\t\t\ttkn := l.Token(\"#CM\", TinCM, nil, src)\n\t\t\tl.pnt.SI += len(src)\n\t\t\tl.pnt.CI = cI\n\t\t\treturn tkn\n\t\t}\n\t}\n\n\t// Block comments\n\tfor _, pair := range l.Config.CommentBlock {\n\t\tstart, end := pair[0], pair[1]\n\t\tif strings.HasPrefix(fwd, start) {\n\t\t\tsuffixes := l.Config.CommentBlockSuffixes[start]\n\t\t\tsuffixFn := l.Config.CommentBlockSuffixFuncs[start]\n\t\t\trI := l.pnt.RI\n\t\t\tcI := l.pnt.CI + len(start)\n\t\t\tfI := len(start)\n\t\t\tsuffixLen := 0\n\t\t\tfor fI < len(fwd) && !strings.HasPrefix(fwd[fI:], end) {\n\t\t\t\tif n := commentSuffixMatch(fwd, fI, suffixes); n > 0 {\n\t\t\t\t\tsuffixLen = n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif n := commentSuffixFnMatch(l, fI, suffixFn); n > 0 {\n\t\t\t\t\tsuffixLen = n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif l.Config.RowChars[rune(fwd[fI])] {\n\t\t\t\t\trI++\n\t\t\t\t\tcI = 0\n\t\t\t\t}\n\t\t\t\tcI++\n\t\t\t\tfI++\n\t\t\t}\n\t\t\tif suffixLen > 0 {\n\t\t\t\t// Consume the suffix, advancing column/row like the End path.\n\t\t\t\tfor k := 0; k < suffixLen; k++ {\n\t\t\t\t\tif l.Config.RowChars[rune(fwd[fI+k])] {\n\t\t\t\t\t\trI++\n\t\t\t\t\t\tcI = 0\n\t\t\t\t\t}\n\t\t\t\t\tcI++\n\t\t\t\t}\n\t\t\t\tfI += suffixLen\n\t\t\t\tsrc := fwd[:fI]\n\t\t\t\ttkn := l.Token(\"#CM\", TinCM, nil, src)\n\t\t\t\tl.pnt.SI += len(src)\n\t\t\t\tl.pnt.RI = rI\n\t\t\t\tl.pnt.CI = cI\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t\tif strings.HasPrefix(fwd[fI:], end) {\n\t\t\t\tcI += len(end)\n\t\t\t\tfI += len(end)\n\t\t\t\t// EatLine: also consume trailing line characters\n\t\t\t\tif l.Config.CommentBlockEatLine[start] {\n\t\t\t\t\tfor fI < len(fwd) && l.Config.LineChars[rune(fwd[fI])] {\n\t\t\t\t\t\tif l.Config.RowChars[rune(fwd[fI])] {\n\t\t\t\t\t\t\trI++\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfI++\n\t\t\t\t\t}\n\t\t\t\t\tcI = 1\n\t\t\t\t}\n\t\t\t\tsrc := fwd[:fI]\n\t\t\t\ttkn := l.Token(\"#CM\", TinCM, nil, src)\n\t\t\t\tl.pnt.SI += len(src)\n\t\t\t\tl.pnt.RI = rI\n\t\t\t\tl.pnt.CI = cI\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t\t// Unterminated comment - return bad token\n\t\t\treturn l.bad(\"unterminated_comment\", l.pnt.SI, l.pnt.SI+len(start)*9)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// commentSuffixMatch returns the length of the suffix match at fwd[fI:]\n// or zero if no suffix matches. Suffixes are pre-sorted longest-first,\n// so the first match is the longest.\nfunc commentSuffixMatch(fwd string, fI int, suffixes []string) int {\n\tif len(suffixes) == 0 {\n\t\treturn 0\n\t}\n\trest := fwd[fI:]\n\tfor _, s := range suffixes {\n\t\tif strings.HasPrefix(rest, s) {\n\t\t\treturn len(s)\n\t\t}\n\t}\n\treturn 0\n}\n\n// commentSuffixFnMatch probes the LexMatcher-form suffix terminator at\n// offset fI. Returns the length of the returned token's Src (to be\n// consumed as part of the comment body) or zero if the matcher passes.\n// The lexer point is snapshotted and restored so the matcher cannot\n// advance the stream itself.\nfunc commentSuffixFnMatch(lex *Lex, fI int, fn LexMatcher) int {\n\tif fn == nil {\n\t\treturn 0\n\t}\n\tsaved := lex.pnt\n\t// Position the lexer at the candidate offset so the matcher sees\n\t// the current body-scan location, not the comment start.\n\tlex.pnt.SI = saved.SI + fI\n\ttkn := fn(lex, nil)\n\tlex.pnt = saved\n\tif tkn == nil {\n\t\treturn 0\n\t}\n\treturn len(tkn.Src)\n}\n\n// matchString matches quoted strings: \"...\", '...', `...`\nfunc (l *Lex) matchString() *Token {\n\tif l.pnt.SI >= l.pnt.Len {\n\t\treturn nil\n\t}\n\tq := rune(l.Src[l.pnt.SI])\n\tif !l.Config.StringChars[q] {\n\t\treturn nil\n\t}\n\n\tisMultiLine := l.Config.MultiChars[q]\n\tsrc := l.Src\n\tsI := l.pnt.SI + 1\n\trI := l.pnt.RI\n\tcI := l.pnt.CI + 1\n\n\tvar sb strings.Builder\n\tsrclen := len(src)\n\tfoundClose := false\n\n\tfor sI < srclen {\n\t\tcI++\n\t\tc := rune(src[sI])\n\n\t\t// End quote\n\t\tif c == q {\n\t\t\tsI++\n\t\t\tfoundClose = true\n\t\t\tbreak\n\t\t}\n\n\t\t// Escape character (all string types process escapes)\n\t\tif c == l.Config.EscapeChar {\n\t\t\tsI++\n\t\t\tcI++\n\t\t\tif sI >= srclen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tesc := src[sI]\n\n\t\t\t// Check custom escape map first.\n\t\t\tif l.Config.EscapeMap != nil {\n\t\t\t\tif rep, ok := l.Config.EscapeMap[string(esc)]; ok {\n\t\t\t\t\tsb.WriteString(rep)\n\t\t\t\t\tsI++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch esc {\n\t\t\tcase 'b':\n\t\t\t\tsb.WriteByte('\\b')\n\t\t\tcase 'f':\n\t\t\t\tsb.WriteByte('\\f')\n\t\t\tcase 'n':\n\t\t\t\tsb.WriteByte('\\n')\n\t\t\tcase 'r':\n\t\t\t\tsb.WriteByte('\\r')\n\t\t\tcase 't':\n\t\t\t\tsb.WriteByte('\\t')\n\t\t\tcase 'v':\n\t\t\t\tsb.WriteByte('\\v')\n\t\t\tcase '\"':\n\t\t\t\tsb.WriteByte('\"')\n\t\t\tcase '\\'':\n\t\t\t\tsb.WriteByte('\\'')\n\t\t\tcase '`':\n\t\t\t\tsb.WriteByte('`')\n\t\t\tcase '\\\\':\n\t\t\t\tsb.WriteByte('\\\\')\n\t\t\tcase '/':\n\t\t\t\tsb.WriteByte('/')\n\t\t\tcase 'x':\n\t\t\t\t// ASCII escape \\x**\n\t\t\t\tsI++\n\t\t\t\tif sI+2 <= srclen {\n\t\t\t\t\tcc := parseHexInt(src[sI : sI+2])\n\t\t\t\t\tif cc >= 0 {\n\t\t\t\t\t\tsb.WriteRune(rune(cc))\n\t\t\t\t\t\tsI += 1 // loop will increment\n\t\t\t\t\t\tcI += 2\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn l.bad(\"invalid_ascii\", l.pnt.SI, sI+2)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\treturn l.bad(\"invalid_ascii\", l.pnt.SI, sI)\n\t\t\t\t}\n\t\t\tcase 'u':\n\t\t\t\t// Unicode escape \\u**** or \\u{*****}\n\t\t\t\tsI++\n\t\t\t\tif sI < srclen && src[sI] == '{' {\n\t\t\t\t\tsI++\n\t\t\t\t\tendI := strings.IndexByte(src[sI:], '}')\n\t\t\t\t\tif endI >= 0 {\n\t\t\t\t\t\tcc := parseHexInt(src[sI : sI+endI])\n\t\t\t\t\t\tif cc >= 0 {\n\t\t\t\t\t\t\tsb.WriteRune(rune(cc))\n\t\t\t\t\t\t\tsI += endI // skip past digits, loop handles +1\n\t\t\t\t\t\t\tcI += endI + 2\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn l.bad(\"invalid_unicode\", l.pnt.SI, sI+endI+1)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn l.bad(\"invalid_unicode\", l.pnt.SI, sI)\n\t\t\t\t\t}\n\t\t\t\t} else if sI+4 <= srclen {\n\t\t\t\t\tcc := parseHexInt(src[sI : sI+4])\n\t\t\t\t\tif cc >= 0 {\n\t\t\t\t\t\tsb.WriteRune(rune(cc))\n\t\t\t\t\t\tsI += 3\n\t\t\t\t\t\tcI += 4\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn l.bad(\"invalid_unicode\", l.pnt.SI, sI+4)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\treturn l.bad(\"invalid_unicode\", l.pnt.SI, sI)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif l.Config.AllowUnknownEscape {\n\t\t\t\t\tsb.WriteByte(esc)\n\t\t\t\t} else {\n\t\t\t\t\tif l.Config.StringAbandon {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\treturn l.bad(\"unexpected\", l.pnt.SI, sI+1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tsI++\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check for unprintable / multiline\n\t\tif c < 32 {\n\t\t\tif isMultiLine && l.Config.LineChars[c] {\n\t\t\t\tif l.Config.RowChars[c] {\n\t\t\t\t\trI++\n\t\t\t\t}\n\t\t\t\tcI = 1\n\t\t\t\tsb.WriteByte(src[sI])\n\t\t\t\tsI++\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Non-multiline unprintable - bad\n\t\t\tif l.Config.StringAbandon {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\t// Normal body - fast scan\n\t\tbI := sI\n\t\tif len(l.Config.StringReplace) > 0 {\n\t\t\t// Char-by-char with replacement support\n\t\t\tfor sI < srclen {\n\t\t\t\tcc := rune(src[sI])\n\t\t\t\tif cc < 32 || cc == q || cc == rune(l.Config.EscapeChar) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif rep, ok := l.Config.StringReplace[cc]; ok {\n\t\t\t\t\t// Flush pending plain chars\n\t\t\t\t\tif sI > bI {\n\t\t\t\t\t\tsb.WriteString(src[bI:sI])\n\t\t\t\t\t}\n\t\t\t\t\tsb.WriteString(rep)\n\t\t\t\t\tsI++\n\t\t\t\t\tcI++\n\t\t\t\t\tbI = sI\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsI++\n\t\t\t\tcI++\n\t\t\t}\n\t\t\tif sI > bI {\n\t\t\t\tsb.WriteString(src[bI:sI])\n\t\t\t}\n\t\t} else {\n\t\t\tfor sI < srclen {\n\t\t\t\tcc := rune(src[sI])\n\t\t\t\tif cc < 32 || cc == q || cc == rune(l.Config.EscapeChar) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tsI++\n\t\t\t\tcI++\n\t\t\t}\n\t\t\tsb.WriteString(src[bI:sI])\n\t\t}\n\t\tcI-- // loop will re-increment\n\t\tcontinue\n\t}\n\n\t// Check for unterminated string\n\tif !foundClose {\n\t\tif l.Config.StringAbandon {\n\t\t\treturn nil\n\t\t}\n\t\treturn l.bad(\"unterminated_string\", l.pnt.SI, sI)\n\t}\n\n\tval := sb.String()\n\tssrc := src[l.pnt.SI:sI]\n\ttkn := l.Token(\"#ST\", TinST, val, ssrc)\n\tl.pnt.SI = sI\n\tl.pnt.RI = rI\n\tl.pnt.CI = cI\n\treturn tkn\n}\n\n// matchNumber matches numeric literals: decimal, hex (0x), octal (0o), binary (0b).\n// Returns nil if the text at current position is not a valid number (lets text matcher try).\nfunc (l *Lex) matchNumber() *Token {\n\tif l.pnt.SI >= l.pnt.Len {\n\t\treturn nil\n\t}\n\n\tsrc := l.Src\n\tsI := l.pnt.SI\n\tch := src[sI]\n\n\t// Must start with digit, +, -, or .\n\tif !isDigit(ch) && ch != '-' && ch != '+' && ch != '.' {\n\t\treturn nil\n\t}\n\n\t// Save start position for backtracking\n\tstart := sI\n\n\t// Handle sign\n\thasSign := false\n\tif ch == '-' || ch == '+' {\n\t\thasSign = true\n\t\tsI++\n\t\tif sI >= len(src) {\n\t\t\treturn nil\n\t\t}\n\t\tch = src[sI]\n\t}\n\n\t// Hex: 0x...\n\tif ch == '0' && sI+1 < len(src) && (src[sI+1] == 'x' || src[sI+1] == 'X') && l.Config.NumberHex {\n\t\tsI += 2\n\t\thexStart := sI\n\t\tfor sI < len(src) && (isHexDigitByte(src[sI]) || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\tsI++\n\t\t}\n\t\tif sI == hexStart {\n\t\t\t// \"0x\" with no hex digits → let text matcher handle\n\t\t\treturn nil\n\t\t}\n\t\t// Check trailing text\n\t\tif l.isFollowingText(sI) {\n\t\t\treturn nil\n\t\t}\n\t\tmsrc := src[start:sI]\n\t\tnstr := msrc\n\t\tif l.Config.NumberSep != 0 {\n\t\t\tnstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n\t\t}\n\t\tnum := parseNumericString(nstr)\n\t\tif num != num { // NaN check\n\t\t\treturn nil\n\t\t}\n\t\ttkn := l.Token(\"#NR\", TinNR, num, msrc)\n\t\tl.pnt.SI = sI\n\t\tl.pnt.CI += sI - start\n\t\treturn tkn\n\t}\n\n\t// Octal: 0o...\n\tif ch == '0' && sI+1 < len(src) && (src[sI+1] == 'o' || src[sI+1] == 'O') && l.Config.NumberOct {\n\t\tsI += 2\n\t\toctStart := sI\n\t\tfor sI < len(src) && ((src[sI] >= '0' && src[sI] <= '7') || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\tsI++\n\t\t}\n\t\tif sI == octStart {\n\t\t\treturn nil\n\t\t}\n\t\tif l.isFollowingText(sI) {\n\t\t\treturn nil\n\t\t}\n\t\tmsrc := src[start:sI]\n\t\tnstr := msrc\n\t\tif l.Config.NumberSep != 0 {\n\t\t\tnstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n\t\t}\n\t\tnum := parseNumericString(nstr)\n\t\tif num != num {\n\t\t\treturn nil\n\t\t}\n\t\ttkn := l.Token(\"#NR\", TinNR, num, msrc)\n\t\tl.pnt.SI = sI\n\t\tl.pnt.CI += sI - start\n\t\treturn tkn\n\t}\n\n\t// Binary: 0b...\n\tif ch == '0' && sI+1 < len(src) && (src[sI+1] == 'b' || src[sI+1] == 'B') && l.Config.NumberBin {\n\t\tsI += 2\n\t\tbinStart := sI\n\t\tfor sI < len(src) && ((src[sI] == '0' || src[sI] == '1') || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\tsI++\n\t\t}\n\t\tif sI == binStart {\n\t\t\treturn nil\n\t\t}\n\t\tif l.isFollowingText(sI) {\n\t\t\treturn nil\n\t\t}\n\t\tmsrc := src[start:sI]\n\t\tnstr := msrc\n\t\tif l.Config.NumberSep != 0 {\n\t\t\tnstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n\t\t}\n\t\tnum := parseNumericString(nstr)\n\t\tif num != num {\n\t\t\treturn nil\n\t\t}\n\t\ttkn := l.Token(\"#NR\", TinNR, num, msrc)\n\t\tl.pnt.SI = sI\n\t\tl.pnt.CI += sI - start\n\t\treturn tkn\n\t}\n\n\t// Decimal number: optional leading dot, digits, decimal, exponent\n\t// Pattern: \\.?[0-9]+([0-9_]*[0-9])? (\\.[0-9]?([0-9_]*[0-9])?)? ([eE][-+]?[0-9]+([0-9_]*[0-9])?)?\n\thasDigits := false\n\n\t// Leading dot\n\tif ch == '.' {\n\t\tif sI+1 >= len(src) || !isDigit(src[sI+1]) {\n\t\t\treturn nil // Just a dot, not a number\n\t\t}\n\t\tsI++ // consume dot\n\t\tfor sI < len(src) && (isDigit(src[sI]) || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\tsI++\n\t\t\thasDigits = true\n\t\t}\n\t} else {\n\t\t// Integer part\n\t\tfor sI < len(src) && (isDigit(src[sI]) || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\thasDigits = true\n\t\t\tsI++\n\t\t}\n\t}\n\n\tif !hasDigits {\n\t\treturn nil\n\t}\n\n\t// Decimal point\n\tif sI < len(src) && src[sI] == '.' {\n\t\t// Check what follows the dot\n\t\tif sI+1 < len(src) && isDigit(src[sI+1]) {\n\t\t\tsI++ // consume dot\n\t\t\tfor sI < len(src) && (isDigit(src[sI]) || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\t\tsI++\n\t\t\t}\n\t\t} else if sI+1 < len(src) && l.isFollowingText(sI+1) && src[sI+1] != '.' {\n\t\t\t// \"0.a\" → not a number, let text handle it\n\t\t\treturn nil\n\t\t} else {\n\t\t\t// Trailing dot: \"0.\" at end or before delimiter\n\t\t\tsI++ // consume dot\n\t\t}\n\t}\n\n\t// Exponent\n\tif sI < len(src) && (src[sI] == 'e' || src[sI] == 'E') {\n\t\teSI := sI\n\t\tsI++ // consume e\n\t\tif sI < len(src) && (src[sI] == '+' || src[sI] == '-') {\n\t\t\tsI++\n\t\t}\n\t\texpStart := sI\n\t\tfor sI < len(src) && (isDigit(src[sI]) || (l.Config.NumberSep != 0 && rune(src[sI]) == l.Config.NumberSep)) {\n\t\t\tsI++\n\t\t}\n\t\tif sI == expStart {\n\t\t\t// No exponent digits - check if trailing makes it text\n\t\t\tif l.isFollowingText(sI) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tsI = eSI // backtrack, 'e' is not part of number\n\t\t}\n\t\t// Check for trailing text after exponent\n\t\tif l.isFollowingText(sI) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Check for trailing alpha/text that would make this text\n\tif l.isFollowingText(sI) {\n\t\treturn nil\n\t}\n\n\tmsrc := src[start:sI]\n\tif len(msrc) == 0 || (hasSign && len(msrc) == 1) {\n\t\treturn nil\n\t}\n\n\t// Check number.exclude\n\tif l.Config.NumberExclude != nil && l.Config.NumberExclude(msrc) {\n\t\treturn nil\n\t}\n\n\tnstr := msrc\n\tif l.Config.NumberSep != 0 {\n\t\tnstr = strings.ReplaceAll(nstr, string(l.Config.NumberSep), \"\")\n\t}\n\n\tnum := parseNumericString(nstr)\n\tif num != num { // NaN\n\t\treturn nil\n\t}\n\n\ttkn := l.Token(\"#NR\", TinNR, num, msrc)\n\tl.pnt.SI = sI\n\tl.pnt.CI += sI - start\n\n\t// subMatchFixed: push trailing fixed token as lookahead (matching TS)\n\tif l.pnt.SI < l.pnt.Len {\n\t\tremaining := src[l.pnt.SI:]\n\t\tfor _, fs := range l.Config.FixedSorted {\n\t\t\tif strings.HasPrefix(remaining, fs) {\n\t\t\t\ttin := l.Config.FixedTokens[fs]\n\t\t\t\tfixTkn := l.Token(l.tinNameFor(tin), tin, nil, fs)\n\t\t\t\tl.pnt.SI += len(fs)\n\t\t\t\tl.pnt.CI += len(fs)\n\t\t\t\tl.tokens = append(l.tokens, fixTkn)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tkn\n}\n\n// matchText matches unquoted text and checks for value keywords (true, false, null).\n// Text is terminated by fixed tokens, whitespace, quotes, and comment starters.\nfunc (l *Lex) matchText() *Token {\n\tif l.pnt.SI >= l.pnt.Len {\n\t\treturn nil\n\t}\n\n\tsrc := l.Src\n\tsI := l.pnt.SI\n\tstart := sI\n\n\tfor sI < len(src) {\n\t\tch := rune(src[sI])\n\t\t// Stop at characters whose lexers are enabled, plus ender chars.\n\t\t// When a lexer is disabled, its characters are consumed as text\n\t\t// (matching TS behavior where enderRE is built conditionally).\n\t\tif (l.Config.SpaceLex && l.Config.SpaceChars[ch]) ||\n\t\t\t(l.Config.LineLex && l.Config.LineChars[ch]) ||\n\t\t\t(l.Config.StringLex && l.Config.StringChars[ch]) ||\n\t\t\tl.Config.EnderChars[ch] {\n\t\t\tbreak\n\t\t}\n\t\t// Stop at fixed tokens (check multi-char first, then single-char)\n\t\trest := src[sI:]\n\t\tisFixed := false\n\t\tfor _, fs := range l.Config.FixedSorted {\n\t\t\tif strings.HasPrefix(rest, fs) {\n\t\t\t\tisFixed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !isFixed && len(l.Config.FixedSorted) == 0 {\n\t\t\t// Fallback for standalone lexer without sorted list\n\t\t\tif ch == '{' || ch == '}' || ch == '[' || ch == ']' ||\n\t\t\t\tch == ':' || ch == ',' {\n\t\t\t\tisFixed = true\n\t\t\t}\n\t\t}\n\t\tif isFixed {\n\t\t\tbreak\n\t\t}\n\t\t// Comment starters (only stop text if comment lexing is enabled)\n\t\tif l.Config.CommentLex {\n\t\t\tisComment := false\n\t\t\tfor _, cs := range l.Config.CommentLine {\n\t\t\t\tif strings.HasPrefix(rest, cs) {\n\t\t\t\t\tisComment = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isComment {\n\t\t\t\tfor _, cb := range l.Config.CommentBlock {\n\t\t\t\t\tif strings.HasPrefix(rest, cb[0]) {\n\t\t\t\t\t\tisComment = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif isComment {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tsI++\n\t}\n\n\tif sI == start {\n\t\treturn nil\n\t}\n\n\tmsrc := src[start:sI]\n\tmlen := len(msrc)\n\n\t// Check for value keywords\n\tif l.Config.ValueLex {\n\t\tif l.Config.ValueDef != nil {\n\t\t\t// Custom value definitions (exact match)\n\t\t\tif val, ok := l.Config.ValueDef[msrc]; ok {\n\t\t\t\ttkn := l.Token(\"#VL\", TinVL, val, msrc)\n\t\t\t\tl.pnt.SI += mlen\n\t\t\t\tl.pnt.CI += mlen\n\t\t\t\treturn tkn\n\t\t\t}\n\n\t\t\t// Regex-processed value definitions (TS: cfg.value.defre).\n\t\t\t// Iterated in configure-time name-sorted order for determinism.\n\t\t\tif len(l.Config.ValueDefRe) > 0 {\n\t\t\t\tfor _, entry := range l.Config.ValueDefRe {\n\t\t\t\t\tvspec := entry.Def\n\t\t\t\t\tif vspec.Match != nil {\n\t\t\t\t\t\tvar matchSrc string\n\t\t\t\t\t\tif vspec.Consume {\n\t\t\t\t\t\t\tmatchSrc = src[start:]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmatchSrc = msrc\n\t\t\t\t\t\t}\n\t\t\t\t\t\tres := vspec.Match.FindStringSubmatch(matchSrc)\n\t\t\t\t\t\tif res != nil && (vspec.Consume || len(res[0]) == len(msrc)) {\n\t\t\t\t\t\t\tremsrc := res[0]\n\t\t\t\t\t\t\tvar val any\n\t\t\t\t\t\t\tif vspec.ValFunc != nil {\n\t\t\t\t\t\t\t\tval = vspec.ValFunc(res)\n\t\t\t\t\t\t\t} else if vspec.Val != nil {\n\t\t\t\t\t\t\t\tval = vspec.Val\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tval = remsrc\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttkn := l.Token(\"#VL\", TinVL, val, remsrc)\n\t\t\t\t\t\t\tl.pnt.SI = start + len(remsrc)\n\t\t\t\t\t\t\tl.pnt.CI += len(remsrc)\n\t\t\t\t\t\t\treturn tkn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Default value keywords (matching TS: true, false, null only)\n\t\t\tswitch msrc {\n\t\t\tcase \"true\":\n\t\t\t\ttkn := l.Token(\"#VL\", TinVL, true, msrc)\n\t\t\t\tl.pnt.SI += mlen\n\t\t\t\tl.pnt.CI += mlen\n\t\t\t\treturn tkn\n\t\t\tcase \"false\":\n\t\t\t\ttkn := l.Token(\"#VL\", TinVL, false, msrc)\n\t\t\t\tl.pnt.SI += mlen\n\t\t\t\tl.pnt.CI += mlen\n\t\t\t\treturn tkn\n\t\t\tcase \"null\":\n\t\t\t\ttkn := l.Token(\"#VL\", TinVL, nil, msrc)\n\t\t\t\tl.pnt.SI += mlen\n\t\t\t\tl.pnt.CI += mlen\n\t\t\t\treturn tkn\n\t\t\t}\n\t\t}\n\t}\n\n\t// Plain text — only emit #TX when text lexing is enabled.\n\t// When text.lex is false but value.lex is true, we still reach here\n\t// to check value keywords (above), but unmatched text is not emitted.\n\t// This matches TS behavior (lexer.ts line 506: `if (null == out && mcfg.lex)`).\n\tif !l.Config.TextLex {\n\t\treturn nil\n\t}\n\n\tvar textVal any = msrc\n\t// Run text.modify pipeline\n\tif len(l.Config.TextModify) > 0 {\n\t\tfor _, mod := range l.Config.TextModify {\n\t\t\ttextVal = mod(textVal)\n\t\t}\n\t}\n\ttkn := l.Token(\"#TX\", TinTX, textVal, msrc)\n\tl.pnt.SI += mlen\n\tl.pnt.CI += mlen\n\n\t// Check if next chars are a fixed token - push as lookahead (subMatchFixed)\n\tif l.pnt.SI < l.pnt.Len {\n\t\tremaining := src[l.pnt.SI:]\n\t\tmatched := false\n\t\tfor _, fs := range l.Config.FixedSorted {\n\t\t\tif strings.HasPrefix(remaining, fs) {\n\t\t\t\ttin := l.Config.FixedTokens[fs]\n\t\t\t\tfixTkn := l.Token(l.tinNameFor(tin), tin, nil, fs)\n\t\t\t\tl.pnt.SI += len(fs)\n\t\t\t\tl.pnt.CI += len(fs)\n\t\t\t\tl.tokens = append(l.tokens, fixTkn)\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched && len(l.Config.FixedSorted) == 0 {\n\t\t\t// Fallback for standalone lexer\n\t\t\tnextCh := string(src[l.pnt.SI])\n\t\t\tif tin, ok := l.Config.FixedTokens[nextCh]; ok {\n\t\t\t\tfixTkn := l.Token(l.tinNameFor(tin), tin, nil, nextCh)\n\t\t\t\tl.pnt.SI++\n\t\t\t\tl.pnt.CI++\n\t\t\t\tl.tokens = append(l.tokens, fixTkn)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tkn\n}\n\n// Helper functions\n\n// tinNameFor returns the name for a Tin, checking custom names first.\nfunc (l *Lex) tinNameFor(tin Tin) string {\n\tif l.Config.TinNames != nil {\n\t\tif name, ok := l.Config.TinNames[tin]; ok {\n\t\t\treturn name\n\t\t}\n\t}\n\treturn tinName(tin)\n}\n\nfunc tinName(tin Tin) string {\n\tswitch tin {\n\tcase TinOB:\n\t\treturn \"#OB\"\n\tcase TinCB:\n\t\treturn \"#CB\"\n\tcase TinOS:\n\t\treturn \"#OS\"\n\tcase TinCS:\n\t\treturn \"#CS\"\n\tcase TinCL:\n\t\treturn \"#CL\"\n\tcase TinCA:\n\t\treturn \"#CA\"\n\tdefault:\n\t\treturn \"#UK\"\n\t}\n}\n\nfunc isDigit(ch byte) bool {\n\treturn ch >= '0' && ch <= '9'\n}\n\nfunc isHexDigitByte(ch byte) bool {\n\treturn (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')\n}\n\n// isTextChar returns true if the character can continue a text token,\n// checking against the config's fixed tokens, ender chars, and string chars.\nfunc (l *Lex) isTextChar(pos int) bool {\n\tif pos >= len(l.Src) {\n\t\treturn false\n\t}\n\tch := l.Src[pos]\n\tr := rune(ch)\n\t// Only treat whitespace as non-text if the corresponding lexer is enabled.\n\tif l.Config.SpaceLex && l.Config.SpaceChars[r] {\n\t\treturn false\n\t}\n\tif l.Config.LineLex && l.Config.LineChars[r] {\n\t\treturn false\n\t}\n\t// Check string chars (only when string lexing is enabled)\n\tif l.Config.StringLex && l.Config.StringChars[r] {\n\t\treturn false\n\t}\n\t// Check ender chars\n\tif l.Config.EnderChars[r] {\n\t\treturn false\n\t}\n\t// Check fixed tokens (multi-char: check if any fixed token starts here)\n\trest := l.Src[pos:]\n\tfor _, fs := range l.Config.FixedSorted {\n\t\tif strings.HasPrefix(rest, fs) {\n\t\t\treturn false\n\t\t}\n\t}\n\t// Fallback for standalone lexer without sorted list\n\tif len(l.Config.FixedSorted) == 0 {\n\t\tif ch == '{' || ch == '}' || ch == '[' || ch == ']' ||\n\t\t\tch == ':' || ch == ',' {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// isFollowingText returns true if the character at pos would continue a text token,\n// taking into account fixed tokens, ender chars, and comment starters.\nfunc (l *Lex) isFollowingText(pos int) bool {\n\tif !l.isTextChar(pos) {\n\t\treturn false\n\t}\n\t// Comment starters are not text continuation (only when comment lexing is enabled)\n\tif l.Config.CommentLex {\n\t\trest := l.Src[pos:]\n\t\tfor _, cs := range l.Config.CommentLine {\n\t\t\tif strings.HasPrefix(rest, cs) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tfor _, cb := range l.Config.CommentBlock {\n\t\t\tif strings.HasPrefix(rest, cb[0]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\nfunc parseHexInt(s string) int {\n\tval := 0\n\tfor _, ch := range s {\n\t\tval <<= 4\n\t\tswitch {\n\t\tcase ch >= '0' && ch <= '9':\n\t\t\tval |= int(ch - '0')\n\t\tcase ch >= 'a' && ch <= 'f':\n\t\t\tval |= int(ch-'a') + 10\n\t\tcase ch >= 'A' && ch <= 'F':\n\t\t\tval |= int(ch-'A') + 10\n\t\tdefault:\n\t\t\treturn -1\n\t\t}\n\t}\n\treturn val\n}\n"
  },
  {
    "path": "go/listchild_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// makeChildParser creates a parser with List.Child enabled.\n// ListRef is automatically enabled by list.child.\nfunc makeChildParser() *Jsonic {\n\treturn Make(Options{List: &ListOptions{Child: boolPtr(true)}})\n}\n\n// expectChild parses input with list.child enabled and checks Val and Child.\nfunc expectChild(t *testing.T, input string, expectedVal []any, expectedChild any) {\n\tt.Helper()\n\tj := makeChildParser()\n\tgot, err := j.Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Errorf(\"Parse(%q) expected ListRef, got %T: %#v\", input, got, got)\n\t\treturn\n\t}\n\tif !bothRefEqual(lr.Val, expectedVal) {\n\t\tt.Errorf(\"Parse(%q) Val\\n  got:      %#v\\n  expected: %#v\", input, lr.Val, expectedVal)\n\t}\n\tif !bothRefEqual(lr.Child, expectedChild) {\n\t\tt.Errorf(\"Parse(%q) Child\\n  got:      %#v\\n  expected: %#v\", input, lr.Child, expectedChild)\n\t}\n}\n\n// --- Basic child values ---\n\nfunc TestListChildNumber(t *testing.T) {\n\t// [:1] → empty list with child=1\n\texpectChild(t, \"[:1]\", []any{}, 1.0)\n}\n\nfunc TestListChildString(t *testing.T) {\n\t// [:a] → empty list with child=\"a\"\n\texpectChild(t, \"[:a]\", []any{}, \"a\")\n}\n\nfunc TestListChildQuotedString(t *testing.T) {\n\t// [:\"hello\"] → empty list with child=\"hello\"\n\texpectChild(t, `[:\"hello\"]`, []any{}, \"hello\")\n}\n\nfunc TestListChildTrue(t *testing.T) {\n\texpectChild(t, \"[:true]\", []any{}, true)\n}\n\nfunc TestListChildFalse(t *testing.T) {\n\texpectChild(t, \"[:false]\", []any{}, false)\n}\n\nfunc TestListChildNull(t *testing.T) {\n\texpectChild(t, \"[:null]\", []any{}, nil)\n}\n\nfunc TestListChildBareColon(t *testing.T) {\n\t// [:] → empty list with child=null (bare colon, no value)\n\texpectChild(t, \"[:]\", []any{}, nil)\n}\n\nfunc TestListChildMap(t *testing.T) {\n\t// [:{a:1}] → empty list with child={a:1}\n\texpectChild(t, \"[:{a:1}]\", []any{}, map[string]any{\"a\": 1.0})\n}\n\nfunc TestListChildMapMultiKey(t *testing.T) {\n\t// [:{a:1,b:2}] → empty list with child={a:1,b:2}\n\texpectChild(t, \"[:{a:1,b:2}]\", []any{}, map[string]any{\"a\": 1.0, \"b\": 2.0})\n}\n\nfunc TestListChildEmptyMap(t *testing.T) {\n\texpectChild(t, \"[:{}]\", []any{}, map[string]any{})\n}\n\nfunc TestListChildNestedMap(t *testing.T) {\n\texpectChild(t, \"[:{a:{b:1}}]\", []any{}, map[string]any{\"a\": map[string]any{\"b\": 1.0}})\n}\n\nfunc TestListChildList(t *testing.T) {\n\t// [:[1,2]] → empty list with child=ListRef([1,2])\n\t// ListRef is auto-enabled, so inner lists are also wrapped.\n\texpectChild(t, \"[:[1,2]]\", []any{}, ListRef{Val: []any{1.0, 2.0}, Implicit: false})\n}\n\nfunc TestListChildEmptyList(t *testing.T) {\n\texpectChild(t, \"[:[]]\", []any{}, ListRef{Val: []any{}, Implicit: false})\n}\n\nfunc TestListChildNestedList(t *testing.T) {\n\texpectChild(t, \"[:[[1],[2]]]\", []any{},\n\t\tListRef{Val: []any{\n\t\t\tListRef{Val: []any{1.0}, Implicit: false},\n\t\t\tListRef{Val: []any{2.0}, Implicit: false},\n\t\t}, Implicit: false})\n}\n\n// --- Mixed child and regular elements ---\n\nfunc TestListChildAfterElement(t *testing.T) {\n\t// [1,:2] → [1] with child=2\n\texpectChild(t, \"[1,:2]\", []any{1.0}, 2.0)\n}\n\nfunc TestListChildBeforeElement(t *testing.T) {\n\t// [:1,2] → [2] with child=1\n\texpectChild(t, \"[:1,2]\", []any{2.0}, 1.0)\n}\n\nfunc TestListChildMiddle(t *testing.T) {\n\t// [1,:2,3] → [1,3] with child=2\n\texpectChild(t, \"[1,:2,3]\", []any{1.0, 3.0}, 2.0)\n}\n\nfunc TestListChildAtEnd(t *testing.T) {\n\t// [1,2,:3] → [1,2] with child=3\n\texpectChild(t, \"[1,2,:3]\", []any{1.0, 2.0}, 3.0)\n}\n\nfunc TestListChildAtStartMultiple(t *testing.T) {\n\t// [:1,2,3] → [2,3] with child=1\n\texpectChild(t, \"[:1,2,3]\", []any{2.0, 3.0}, 1.0)\n}\n\n// --- Multiple child values (last wins for scalars, deep merge for maps) ---\n\nfunc TestListChildMultipleScalars(t *testing.T) {\n\t// [:1,:2] → [] with child=2 (last scalar wins)\n\texpectChild(t, \"[:1,:2]\", []any{}, 2.0)\n}\n\nfunc TestListChildTripleScalars(t *testing.T) {\n\t// [:1,:2,:3] → [] with child=3\n\texpectChild(t, \"[:1,:2,:3]\", []any{}, 3.0)\n}\n\nfunc TestListChildMergeMaps(t *testing.T) {\n\t// [:{a:1},:{b:2}] → [] with child={a:1,b:2} (deep merge)\n\texpectChild(t, \"[:{a:1},:{b:2}]\", []any{}, map[string]any{\"a\": 1.0, \"b\": 2.0})\n}\n\nfunc TestListChildMergeThreeMaps(t *testing.T) {\n\t// [:{a:1},:{b:2},:{c:3}] → [] with child={a:1,b:2,c:3}\n\texpectChild(t, \"[:{a:1},:{b:2},:{c:3}]\", []any{}, map[string]any{\"a\": 1.0, \"b\": 2.0, \"c\": 3.0})\n}\n\nfunc TestListChildDeepMergeMaps(t *testing.T) {\n\t// [:{a:{x:1}},:{a:{y:2}}] → [] with child={a:{x:1,y:2}}\n\texpectChild(t, \"[:{a:{x:1}},:{a:{y:2}}]\", []any{}, map[string]any{\n\t\t\"a\": map[string]any{\"x\": 1.0, \"y\": 2.0},\n\t})\n}\n\nfunc TestListChildMergeDupKey(t *testing.T) {\n\t// [:{a:1},:{a:2}] → [] with child={a:2} (dup key, over wins)\n\texpectChild(t, \"[:{a:1},:{a:2}]\", []any{}, map[string]any{\"a\": 2.0})\n}\n\n// --- Pair in list with child ---\n\nfunc TestListChildWithPairProperty(t *testing.T) {\n\t// [a:1,:2] → list with property a=1 and child=2\n\tj := makeChildParser()\n\tgot, err := j.Parse(\"[a:1,:2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tif !bothRefEqual(lr.Child, 2.0) {\n\t\tt.Errorf(\"Child: got %#v, expected 2.0\", lr.Child)\n\t}\n}\n\n// --- Child path dive ---\n\nfunc TestListChildPathDive(t *testing.T) {\n\t// [:a:b] → [] with child={a:\"b\"} (path dive from child)\n\texpectChild(t, \"[:a:b]\", []any{}, map[string]any{\"a\": \"b\"})\n}\n\nfunc TestListChildDeepPathDive(t *testing.T) {\n\t// [:a:b:1] → [] with child={a:{b:1}}\n\texpectChild(t, \"[:a:b:1]\", []any{}, map[string]any{\"a\": map[string]any{\"b\": 1.0}})\n}\n\n// --- Trailing comma ---\n\nfunc TestListChildTrailingComma(t *testing.T) {\n\t// [:1,] → [] with child=1\n\texpectChild(t, \"[:1,]\", []any{}, 1.0)\n}\n\nfunc TestListChildElementThenChildTrailing(t *testing.T) {\n\t// [1,:2,] → [1] with child=2\n\texpectChild(t, \"[1,:2,]\", []any{1.0}, 2.0)\n}\n\nfunc TestListChildMultipleTrailing(t *testing.T) {\n\t// [:1,:2,] → [] with child=2\n\texpectChild(t, \"[:1,:2,]\", []any{}, 2.0)\n}\n\n// --- Leading comma (null element) ---\n\nfunc TestListChildLeadingCommaNull(t *testing.T) {\n\t// [,:1] → [null] with child=1\n\texpectChild(t, \"[,:1]\", []any{nil}, 1.0)\n}\n\nfunc TestListChildDoubleLeadingComma(t *testing.T) {\n\t// [,,:1] → [null,null] with child=1\n\texpectChild(t, \"[,,:1]\", []any{nil, nil}, 1.0)\n}\n\n// --- No child (regular lists unchanged) ---\n\nfunc TestListChildNone(t *testing.T) {\n\t// [1,2,3] → [1,2,3] with child=nil (no child set)\n\texpectChild(t, \"[1,2,3]\", []any{1.0, 2.0, 3.0}, nil)\n}\n\nfunc TestListChildEmptyBrackets(t *testing.T) {\n\t// [] → [] with child=nil\n\texpectChild(t, \"[]\", []any{}, nil)\n}\n\n// --- Mixed maps and child ---\n\nfunc TestListChildMapElementThenChild(t *testing.T) {\n\t// [{a:1},:2] → [{a:1}] with child=2\n\texpectChild(t, \"[{a:1},:2]\", []any{map[string]any{\"a\": 1.0}}, 2.0)\n}\n\nfunc TestListChildBeforeMapElement(t *testing.T) {\n\t// [:1,{a:2}] → [{a:2}] with child=1\n\texpectChild(t, \"[:1,{a:2}]\", []any{map[string]any{\"a\": 2.0}}, 1.0)\n}\n\nfunc TestListChildListElement(t *testing.T) {\n\t// [[1,2],:3] → [ListRef([1,2])] with child=3\n\texpectChild(t, \"[[1,2],:3]\", []any{ListRef{Val: []any{1.0, 2.0}, Implicit: false}}, 3.0)\n}\n\nfunc TestListChildBeforeListElement(t *testing.T) {\n\t// [:1,[2,3]] → [ListRef([2,3])] with child=1\n\texpectChild(t, \"[:1,[2,3]]\", []any{ListRef{Val: []any{2.0, 3.0}, Implicit: false}}, 1.0)\n}\n\nfunc TestListChildMapAroundChild(t *testing.T) {\n\t// [{a:1},:2,{b:3}] → [{a:1},{b:3}] with child=2\n\texpectChild(t, \"[{a:1},:2,{b:3}]\", []any{map[string]any{\"a\": 1.0}, map[string]any{\"b\": 3.0}}, 2.0)\n}\n\n// --- Bare colon creates null child ---\n\nfunc TestListChildBareColonThenElement(t *testing.T) {\n\t// [:,1] → [1] with child=null\n\texpectChild(t, \"[:,1]\", []any{1.0}, nil)\n}\n\nfunc TestListChildElementThenBareColon(t *testing.T) {\n\t// [1,:] → [1] with child=null\n\texpectChild(t, \"[1,:]\", []any{1.0}, nil)\n}\n\n// --- Multiple children interleaved with elements ---\n\nfunc TestListChildInterleaved(t *testing.T) {\n\t// [:1,:2,3,:4] → [3] with child=4\n\texpectChild(t, \"[:1,:2,3,:4]\", []any{3.0}, 4.0)\n}\n\nfunc TestListChildMultiInterleaved(t *testing.T) {\n\t// [1,:2,3,:4,5] → [1,3,5] with child=4\n\texpectChild(t, \"[1,:2,3,:4,5]\", []any{1.0, 3.0, 5.0}, 4.0)\n}\n\n// --- list.child auto-enables ListRef ---\n\nfunc TestListChildAutoEnablesListRef(t *testing.T) {\n\t// Enabling list.child should auto-enable ListRef wrapping.\n\tj := Make(Options{List: &ListOptions{Child: boolPtr(true)}})\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.(ListRef); !ok {\n\t\tt.Errorf(\"expected ListRef (auto-enabled by list.child), got %T: %#v\", got, got)\n\t}\n}\n\n// --- list.child disabled (default) ---\n\nfunc TestListChildDisabledDefault(t *testing.T) {\n\t// Default: list.child disabled, bare colon is not special.\n\tj := Make()\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.([]any); !ok {\n\t\tt.Errorf(\"expected []any, got %T: %#v\", got, got)\n\t}\n}\n\n// --- list.child with MapRef ---\n\nfunc TestListChildWithMapRef(t *testing.T) {\n\t// list.child + MapRef: child map should be MapRef\n\tj := Make(Options{\n\t\tList:     &ListOptions{Child: boolPtr(true)},\n\t\tInfo: &InfoOptions{Map: boolPtr(true)},\n\t})\n\tgot, err := j.Parse(\"[:{a:1}]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tchild, ok := lr.Child.(MapRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected child to be MapRef, got %T: %#v\", lr.Child, lr.Child)\n\t}\n\tif child.Val[\"a\"] != 1.0 {\n\t\tt.Errorf(\"expected child.Val[a]=1, got %#v\", child.Val)\n\t}\n}\n\n// --- list.child with TextInfo ---\n\nfunc TestListChildWithTextInfo(t *testing.T) {\n\t// list.child + TextInfo: child text should be Text struct\n\tj := Make(Options{\n\t\tList:     &ListOptions{Child: boolPtr(true)},\n\t\tInfo: &InfoOptions{Text: boolPtr(true)},\n\t})\n\tgot, err := j.Parse(`[:\"hello\"]`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tchild, ok := lr.Child.(Text)\n\tif !ok {\n\t\tt.Fatalf(\"expected child to be Text, got %T: %#v\", lr.Child, lr.Child)\n\t}\n\tif child.Str != \"hello\" || child.Quote != `\"` {\n\t\tt.Errorf(\"expected Text{Str:hello, Quote:\\\"}, got %#v\", child)\n\t}\n}\n\n// --- Implicit flag ---\n\nfunc TestListChildExplicitBrackets(t *testing.T) {\n\tj := makeChildParser()\n\tgot, err := j.Parse(\"[:1]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr := got.(ListRef)\n\tif lr.Implicit {\n\t\tt.Error(\"expected Implicit=false for bracketed list\")\n\t}\n}\n\n// --- Deep merge disabled ---\n\nfunc TestListChildNoExtend(t *testing.T) {\n\t// With map.extend=false, multiple child values → last wins (no deep merge)\n\tj := Make(Options{\n\t\tList: &ListOptions{Child: boolPtr(true)},\n\t\tMap:  &MapOptions{Extend: boolPtr(false)},\n\t})\n\tgot, err := j.Parse(\"[:{a:1},:{b:2}]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T\", got)\n\t}\n\t// Without extend, last child value wins entirely\n\tchild, ok := lr.Child.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected child to be map, got %T: %#v\", lr.Child, lr.Child)\n\t}\n\tif _, hasA := child[\"a\"]; hasA {\n\t\tt.Errorf(\"expected child to only have key 'b' (last wins), got %#v\", child)\n\t}\n\tif child[\"b\"] != 2.0 {\n\t\tt.Errorf(\"expected child[b]=2, got %#v\", child)\n\t}\n}\n"
  },
  {
    "path": "go/listref_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// expectListRef parses input with ListRef enabled and checks the result.\nfunc expectListRef(t *testing.T, input string, expected any) {\n\tt.Helper()\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(true)}})\n\tgot, err := j.Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tif !listRefEqual(got, expected) {\n\t\tt.Errorf(\"Parse(%q)\\n  got:      %#v\\n  expected: %#v\",\n\t\t\tinput, got, expected)\n\t}\n}\n\n// listRefEqual compares values including ListRef structs.\nfunc listRefEqual(a, b any) bool {\n\tif a == nil && b == nil {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\n\tswitch av := a.(type) {\n\tcase ListRef:\n\t\tbv, ok := b.(ListRef)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tif av.Implicit != bv.Implicit {\n\t\t\treturn false\n\t\t}\n\t\tif len(av.Val) != len(bv.Val) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av.Val {\n\t\t\tif !listRefEqual(av.Val[i], bv.Val[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\t// Meta: treat nil and empty map as equal\n\t\tif len(av.Meta) != 0 || len(bv.Meta) != 0 {\n\t\t\tif len(av.Meta) != len(bv.Meta) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfor k, v := range av.Meta {\n\t\t\t\tbval, exists := bv.Meta[k]\n\t\t\t\tif !exists || !listRefEqual(v, bval) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase map[string]any:\n\t\tbv, ok := b.(map[string]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av {\n\t\t\tbval, exists := bv[k]\n\t\t\tif !exists || !listRefEqual(v, bval) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase []any:\n\t\tbv, ok := b.([]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av {\n\t\t\tif !listRefEqual(av[i], bv[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase float64:\n\t\tbv, ok := b.(float64)\n\t\treturn ok && av == bv\n\tcase bool:\n\t\tbv, ok := b.(bool)\n\t\treturn ok && av == bv\n\tcase string:\n\t\tbv, ok := b.(string)\n\t\treturn ok && av == bv\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// lr is shorthand to create a ListRef value.\nfunc lr(implicit bool, vals ...any) ListRef {\n\tif vals == nil {\n\t\tvals = []any{}\n\t}\n\treturn ListRef{Val: vals, Implicit: implicit}\n}\n\nfunc TestListRefOff(t *testing.T) {\n\t// Default (ListRef off) - plain []any in output.\n\tj := Make()\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.([]any); !ok {\n\t\tt.Errorf(\"expected []any, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestListRefExplicitOff(t *testing.T) {\n\t// Explicitly setting ListRef to false.\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(false)}})\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.([]any); !ok {\n\t\tt.Errorf(\"expected []any, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestListRefExplicitList(t *testing.T) {\n\t// Explicit list with brackets: not implicit.\n\texpectListRef(t, \"[1,2,3]\", lr(false, 1.0, 2.0, 3.0))\n}\n\nfunc TestListRefExplicitEmpty(t *testing.T) {\n\t// Empty explicit list.\n\texpectListRef(t, \"[]\", lr(false))\n}\n\nfunc TestListRefImplicitComma(t *testing.T) {\n\t// Implicit list via trailing comma: a,b\n\texpectListRef(t, \"a,b\", lr(true, \"a\", \"b\"))\n}\n\nfunc TestListRefImplicitTrailingComma(t *testing.T) {\n\t// Trailing comma creates implicit list.\n\texpectListRef(t, \"a,\", lr(true, \"a\"))\n}\n\nfunc TestListRefImplicitSpace(t *testing.T) {\n\t// Implicit list via space separation: a b c\n\texpectListRef(t, \"a b c\", lr(true, \"a\", \"b\", \"c\"))\n}\n\nfunc TestListRefImplicitLeadingComma(t *testing.T) {\n\t// Leading comma creates implicit list with null first element.\n\texpectListRef(t, \",a\", lr(true, nil, \"a\"))\n}\n\nfunc TestListRefImplicitCommaOnly(t *testing.T) {\n\t// Single comma creates implicit list with null element.\n\texpectListRef(t, \",\", lr(true, nil))\n}\n\nfunc TestListRefNestedExplicit(t *testing.T) {\n\t// Nested explicit lists.\n\texpectListRef(t, \"[[1],[2]]\", lr(false,\n\t\tlr(false, 1.0),\n\t\tlr(false, 2.0),\n\t))\n}\n\nfunc TestListRefExplicitInMap(t *testing.T) {\n\t// Explicit list as map value.\n\texpectListRef(t, \"a:[1,2]\", map[string]any{\n\t\t\"a\": lr(false, 1.0, 2.0),\n\t})\n}\n\nfunc TestListRefMixedImplicitExplicit(t *testing.T) {\n\t// Implicit list of explicit lists.\n\texpectListRef(t, \"[a],[b]\", lr(true,\n\t\tlr(false, \"a\"),\n\t\tlr(false, \"b\"),\n\t))\n}\n\nfunc TestListRefSpaceSeparatedLists(t *testing.T) {\n\t// Space-separated explicit lists.\n\texpectListRef(t, \"[a] [b]\", lr(true,\n\t\tlr(false, \"a\"),\n\t\tlr(false, \"b\"),\n\t))\n}\n\nfunc TestListRefMapsUnaffected(t *testing.T) {\n\t// Maps should not be wrapped in ListRef.\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(true)}})\n\tgot, err := j.Parse(\"a:1\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.(map[string]any); !ok {\n\t\tt.Errorf(\"expected map[string]any, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestListRefScalarsUnaffected(t *testing.T) {\n\t// Scalars should not be affected.\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(true)}})\n\n\tgot, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif f, ok := got.(float64); !ok || f != 42.0 {\n\t\tt.Errorf(\"expected 42.0, got %#v\", got)\n\t}\n\n\tgot, err = j.Parse(\"true\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif b, ok := got.(bool); !ok || b != true {\n\t\tt.Errorf(\"expected true, got %#v\", got)\n\t}\n}\n\nfunc TestListRefDeepMerge(t *testing.T) {\n\t// Extension (deep merge) with ListRef enabled.\n\t// a:[{b:1}],a:[{b:2}] merges the arrays.\n\texpectListRef(t, \"a:[{b:1}],a:[{b:2}]\", map[string]any{\n\t\t\"a\": lr(false, map[string]any{\"b\": 2.0}),\n\t})\n}\n\nfunc TestListRefSpaceSeparatedMaps(t *testing.T) {\n\t// Space-separated maps create implicit list.\n\texpectListRef(t, \"{a:1} {b:2}\", lr(true,\n\t\tmap[string]any{\"a\": 1.0},\n\t\tmap[string]any{\"b\": 2.0},\n\t))\n}\n\nfunc TestListRefWithNumbers(t *testing.T) {\n\texpectListRef(t, \"[1,2,3]\", lr(false, 1.0, 2.0, 3.0))\n\texpectListRef(t, \"1,2,3\", lr(true, 1.0, 2.0, 3.0))\n}\n\nfunc TestListRefImplicitNullCommas(t *testing.T) {\n\t// Double comma creates null element.\n\texpectListRef(t, \"1,,\", lr(true, 1.0, nil))\n\texpectListRef(t, \"1,,,\", lr(true, 1.0, nil, nil))\n}\n\nfunc TestListRefSingleElement(t *testing.T) {\n\t// Single element in brackets.\n\texpectListRef(t, \"[a]\", lr(false, \"a\"))\n}\n\nfunc TestListRefCombinedWithTextInfo(t *testing.T) {\n\t// Both ListRef and TextInfo enabled.\n\tj := Make(Options{Info: &InfoOptions{List: boolPtr(true), Text: boolPtr(true)}})\n\tgot, err := j.Parse(`[\"a\",'b',c]`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlr, ok := got.(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef, got %T: %#v\", got, got)\n\t}\n\tif lr.Implicit {\n\t\tt.Errorf(\"expected Implicit=false for bracketed list\")\n\t}\n\tif len(lr.Val) != 3 {\n\t\tt.Fatalf(\"expected 3 elements, got %d\", len(lr.Val))\n\t}\n\t// Check elements are Text structs.\n\tfor i, expected := range []Text{\n\t\t{Quote: `\"`, Str: \"a\"},\n\t\t{Quote: \"'\", Str: \"b\"},\n\t\t{Quote: \"\", Str: \"c\"},\n\t} {\n\t\tif txt, ok := lr.Val[i].(Text); !ok || txt != expected {\n\t\t\tt.Errorf(\"element %d: expected %#v, got %#v\", i, expected, lr.Val[i])\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "go/mapref_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// expectMapRef parses input with MapRef enabled and checks the result.\nfunc expectMapRef(t *testing.T, input string, expected any) {\n\tt.Helper()\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\tgot, err := j.Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tif !mapRefEqual(got, expected) {\n\t\tt.Errorf(\"Parse(%q)\\n  got:      %#v\\n  expected: %#v\",\n\t\t\tinput, got, expected)\n\t}\n}\n\n// mapRefEqual compares values including MapRef structs.\nfunc mapRefEqual(a, b any) bool {\n\tif a == nil && b == nil {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\n\tswitch av := a.(type) {\n\tcase MapRef:\n\t\tbv, ok := b.(MapRef)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tif av.Implicit != bv.Implicit {\n\t\t\treturn false\n\t\t}\n\t\tif len(av.Val) != len(bv.Val) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av.Val {\n\t\t\tbval, exists := bv.Val[k]\n\t\t\tif !exists || !mapRefEqual(v, bval) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\t// Meta: treat nil and empty map as equal\n\t\tif len(av.Meta) != 0 || len(bv.Meta) != 0 {\n\t\t\tif len(av.Meta) != len(bv.Meta) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfor k, v := range av.Meta {\n\t\t\t\tbval, exists := bv.Meta[k]\n\t\t\t\tif !exists || !mapRefEqual(v, bval) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase map[string]any:\n\t\tbv, ok := b.(map[string]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av {\n\t\t\tbval, exists := bv[k]\n\t\t\tif !exists || !mapRefEqual(v, bval) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase []any:\n\t\tbv, ok := b.([]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av {\n\t\t\tif !mapRefEqual(av[i], bv[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase float64:\n\t\tbv, ok := b.(float64)\n\t\treturn ok && av == bv\n\tcase bool:\n\t\tbv, ok := b.(bool)\n\t\treturn ok && av == bv\n\tcase string:\n\t\tbv, ok := b.(string)\n\t\treturn ok && av == bv\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// mr is shorthand to create a MapRef value.\nfunc mr(implicit bool, pairs ...any) MapRef {\n\tm := make(map[string]any)\n\tfor i := 0; i+1 < len(pairs); i += 2 {\n\t\tk, _ := pairs[i].(string)\n\t\tm[k] = pairs[i+1]\n\t}\n\treturn MapRef{Val: m, Implicit: implicit}\n}\n\nfunc TestMapRefOff(t *testing.T) {\n\t// Default (MapRef off) - plain map[string]any in output.\n\tj := Make()\n\tgot, err := j.Parse(\"{a:1}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.(map[string]any); !ok {\n\t\tt.Errorf(\"expected map[string]any, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestMapRefExplicitOff(t *testing.T) {\n\t// Explicitly setting MapRef to false.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(false)}})\n\tgot, err := j.Parse(\"{a:1}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.(map[string]any); !ok {\n\t\tt.Errorf(\"expected map[string]any, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestMapRefExplicitMap(t *testing.T) {\n\t// Explicit map with braces: not implicit.\n\texpectMapRef(t, \"{a:1,b:2}\", mr(false, \"a\", 1.0, \"b\", 2.0))\n}\n\nfunc TestMapRefExplicitEmpty(t *testing.T) {\n\t// Empty explicit map.\n\texpectMapRef(t, \"{}\", mr(false))\n}\n\nfunc TestMapRefImplicitMap(t *testing.T) {\n\t// Implicit map via key:value without braces.\n\texpectMapRef(t, \"a:1\", mr(true, \"a\", 1.0))\n}\n\nfunc TestMapRefImplicitMultipleKeys(t *testing.T) {\n\t// Implicit map with multiple keys.\n\texpectMapRef(t, \"a:1,b:2\", mr(true, \"a\", 1.0, \"b\", 2.0))\n}\n\nfunc TestMapRefImplicitSpaceSeparated(t *testing.T) {\n\t// Implicit map with space-separated pairs.\n\texpectMapRef(t, \"a:1 b:2\", mr(true, \"a\", 1.0, \"b\", 2.0))\n}\n\nfunc TestMapRefNestedExplicit(t *testing.T) {\n\t// Nested explicit maps.\n\texpectMapRef(t, \"{a:{b:1}}\", mr(false, \"a\", mr(false, \"b\", 1.0)))\n}\n\nfunc TestMapRefNestedImplicitInExplicit(t *testing.T) {\n\t// Implicit map nested inside explicit map value (via pair dive).\n\texpectMapRef(t, \"{a:b:1}\", mr(false, \"a\", mr(true, \"b\", 1.0)))\n}\n\nfunc TestMapRefListsUnaffected(t *testing.T) {\n\t// Lists should not be wrapped in MapRef.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\tgot, err := j.Parse(\"[1,2]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := got.([]any); !ok {\n\t\tt.Errorf(\"expected []any, got %T: %#v\", got, got)\n\t}\n}\n\nfunc TestMapRefScalarsUnaffected(t *testing.T) {\n\t// Scalars should not be affected.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\n\tgot, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif f, ok := got.(float64); !ok || f != 42.0 {\n\t\tt.Errorf(\"expected 42.0, got %#v\", got)\n\t}\n\n\tgot, err = j.Parse(\"true\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif b, ok := got.(bool); !ok || b != true {\n\t\tt.Errorf(\"expected true, got %#v\", got)\n\t}\n}\n\nfunc TestMapRefDeepMerge(t *testing.T) {\n\t// Extension (deep merge) with MapRef enabled.\n\texpectMapRef(t, \"a:{b:1},a:{c:2}\", mr(true, \"a\", mr(false, \"b\", 1.0, \"c\", 2.0)))\n}\n\nfunc TestMapRefMapInList(t *testing.T) {\n\t// MapRef inside a list.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true)}})\n\tgot, err := j.Parse(\"[{a:1},{b:2}]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tarr, ok := got.([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected []any, got %T: %#v\", got, got)\n\t}\n\tif len(arr) != 2 {\n\t\tt.Fatalf(\"expected 2 elements, got %d\", len(arr))\n\t}\n\tm0, ok := arr[0].(MapRef)\n\tif !ok {\n\t\tt.Errorf(\"expected MapRef for element 0, got %T: %#v\", arr[0], arr[0])\n\t} else if m0.Implicit || len(m0.Val) != 1 || m0.Val[\"a\"] != 1.0 {\n\t\tt.Errorf(\"element 0: expected {a:1} explicit, got %#v\", m0)\n\t}\n\tm1, ok := arr[1].(MapRef)\n\tif !ok {\n\t\tt.Errorf(\"expected MapRef for element 1, got %T: %#v\", arr[1], arr[1])\n\t} else if m1.Implicit || len(m1.Val) != 1 || m1.Val[\"b\"] != 2.0 {\n\t\tt.Errorf(\"element 1: expected {b:2} explicit, got %#v\", m1)\n\t}\n}\n\nfunc TestMapRefWithStringValues(t *testing.T) {\n\texpectMapRef(t, `{a:\"hello\",b:\"world\"}`, mr(false, \"a\", \"hello\", \"b\", \"world\"))\n}\n\nfunc TestMapRefCombinedWithListRef(t *testing.T) {\n\t// Both MapRef and ListRef enabled.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), List: boolPtr(true)}})\n\tgot, err := j.Parse(\"{a:[1,2]}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := got.(MapRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected MapRef, got %T: %#v\", got, got)\n\t}\n\tif m.Implicit {\n\t\tt.Errorf(\"expected Implicit=false for braced map\")\n\t}\n\tlistVal, ok := m.Val[\"a\"].(ListRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected ListRef for key 'a', got %T: %#v\", m.Val[\"a\"], m.Val[\"a\"])\n\t}\n\tif listVal.Implicit {\n\t\tt.Errorf(\"expected Implicit=false for bracketed list\")\n\t}\n\tif len(listVal.Val) != 2 {\n\t\tt.Fatalf(\"expected 2 elements, got %d\", len(listVal.Val))\n\t}\n}\n\nfunc TestMapRefCombinedWithTextInfo(t *testing.T) {\n\t// Both MapRef and TextInfo enabled.\n\tj := Make(Options{Info: &InfoOptions{Map: boolPtr(true), Text: boolPtr(true)}})\n\tgot, err := j.Parse(`{\"a\":1}`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := got.(MapRef)\n\tif !ok {\n\t\tt.Fatalf(\"expected MapRef, got %T: %#v\", got, got)\n\t}\n\tif m.Implicit {\n\t\tt.Errorf(\"expected Implicit=false for braced map\")\n\t}\n}\n\nfunc TestMapRefSingleKey(t *testing.T) {\n\t// Single key explicit map.\n\texpectMapRef(t, \"{a:1}\", mr(false, \"a\", 1.0))\n}\n"
  },
  {
    "path": "go/nlookahead_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// N-token lookahead tests. Previously Go's ParseAlts only inspected\n// alt.S[0] and alt.S[1]; anything at alt.S[2] and beyond was silently\n// ignored. These tests exercise the generalized N-position match loop.\n\n// TestNLookaheadThreeTokens verifies that a three-position alt.S\n// matches all three consecutive tokens and populates both the new\n// rule.O slice and the legacy O0/O1 aliases.\nfunc TestNLookaheadThreeTokens(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"A\")\n\tTB := j.Token(\"#TB\", \"B\")\n\tTC := j.Token(\"#TC\", \"C\")\n\n\tvar gotO []string\n\tvar gotLegacy string\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\tS: [][]Tin{{TA}, {TB}, {TC}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tfor _, tkn := range r.O {\n\t\t\t\t\tgotO = append(gotO, string(tkn.Src))\n\t\t\t\t}\n\t\t\t\tgotLegacy = string(r.O0.Src) + string(r.O1.Src)\n\t\t\t\tr.Node = \"ABC\"\n\t\t\t},\n\t\t}}, rs.Open...)\n\t})\n\n\tresult, err := j.Parse(\"ABC\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"ABC\" {\n\t\tt.Errorf(\"expected ABC, got %v\", result)\n\t}\n\tif len(gotO) != 3 || gotO[0] != \"A\" || gotO[1] != \"B\" || gotO[2] != \"C\" {\n\t\tt.Errorf(\"expected r.O to be [A B C], got %v\", gotO)\n\t}\n\tif gotLegacy != \"AB\" {\n\t\tt.Errorf(\"expected legacy O0+O1 = AB, got %s\", gotLegacy)\n\t}\n}\n\n// TestNLookaheadFiveTokensNoCap confirms there is no fixed cap -\n// five-token lookahead matches as expected.\nfunc TestNLookaheadFiveTokensNoCap(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"A\")\n\tTB := j.Token(\"#TB\", \"B\")\n\tTC := j.Token(\"#TC\", \"C\")\n\tTD := j.Token(\"#TD\", \"D\")\n\tTE := j.Token(\"#TE\", \"E\")\n\n\tvar matchedN int\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\tS: [][]Tin{{TA}, {TB}, {TC}, {TD}, {TE}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tmatchedN = r.ON\n\t\t\t\tr.Node = \"five\"\n\t\t\t},\n\t\t}}, rs.Open...)\n\t})\n\n\tresult, err := j.Parse(\"ABCDE\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"five\" {\n\t\tt.Errorf(\"expected 'five', got %v\", result)\n\t}\n\tif matchedN != 5 {\n\t\tt.Errorf(\"expected ON=5, got %d\", matchedN)\n\t}\n}\n\n// TestNLookaheadFirstMatchWins verifies that alts with longer S\n// sequences are attempted before shorter ones, and that a deeper\n// mismatch correctly falls through to the next alt.\nfunc TestNLookaheadFirstMatchWins(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"A\")\n\tTB := j.Token(\"#TB\", \"B\")\n\tTC := j.Token(\"#TC\", \"C\")\n\tTD := j.Token(\"#TD\", \"D\")\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{\n\t\t\t{\n\t\t\t\tS: [][]Tin{{TA}, {TB}, {TC}},\n\t\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"abc\" },\n\t\t\t},\n\t\t\t{\n\t\t\t\tS: [][]Tin{{TA}, {TB}, {TD}},\n\t\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"abd\" },\n\t\t\t},\n\t\t\t{\n\t\t\t\tS: [][]Tin{{TA}, {TB}},\n\t\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"ab\" },\n\t\t\t},\n\t\t\t{\n\t\t\t\tS: [][]Tin{{TA}},\n\t\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"a\" },\n\t\t\t},\n\t\t}, rs.Open...)\n\t})\n\n\tcases := []struct {\n\t\tin   string\n\t\twant string\n\t}{\n\t\t{\"ABC\", \"abc\"},\n\t\t{\"ABD\", \"abd\"},\n\t\t{\"AB\", \"ab\"},\n\t\t{\"A\", \"a\"},\n\t}\n\tfor _, tc := range cases {\n\t\tgot, err := j.Parse(tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%q: unexpected error: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"%q: want %s, got %v\", tc.in, tc.want, got)\n\t\t}\n\t}\n}\n\n// TestNLookaheadCtxTSlice verifies that ctx.T is populated with every\n// fetched lookahead slot and stays in sync with the legacy T0 / T1.\nfunc TestNLookaheadCtxTSlice(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"A\")\n\tTB := j.Token(\"#TB\", \"B\")\n\tTC := j.Token(\"#TC\", \"C\")\n\n\tvar seenT0, seenT1, seenT2 string\n\tvar seenLegacyT0, seenLegacyT1 string\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\tS: [][]Tin{{TA}, {TB}, {TC}},\n\t\t\tC: func(r *Rule, ctx *Context) bool {\n\t\t\t\t// After the lookahead fetch but before the alt is\n\t\t\t\t// committed, ctx.T[0..2] are populated and the legacy\n\t\t\t\t// aliases mirror T[0] / T[1].\n\t\t\t\tif len(ctx.T) >= 3 {\n\t\t\t\t\tseenT0 = string(ctx.T[0].Src)\n\t\t\t\t\tseenT1 = string(ctx.T[1].Src)\n\t\t\t\t\tseenT2 = string(ctx.T[2].Src)\n\t\t\t\t}\n\t\t\t\tseenLegacyT0 = string(ctx.T0.Src)\n\t\t\t\tseenLegacyT1 = string(ctx.T1.Src)\n\t\t\t\treturn true\n\t\t\t},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = \"ok\"\n\t\t\t},\n\t\t}}, rs.Open...)\n\t})\n\n\t_, err := j.Parse(\"ABC\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif seenT0 != \"A\" || seenT1 != \"B\" || seenT2 != \"C\" {\n\t\tt.Errorf(\"ctx.T[0..2] want A B C, got %s %s %s\", seenT0, seenT1, seenT2)\n\t}\n\tif seenLegacyT0 != \"A\" || seenLegacyT1 != \"B\" {\n\t\tt.Errorf(\"legacy T0/T1 want A B, got %s %s\", seenLegacyT0, seenLegacyT1)\n\t}\n}\n\n// TestNLookaheadNullMiddleSlotIsWildcard is a regression test for an\n// earlier bug where an empty alt.S[i] caused the match loop to break,\n// silently dropping checks at later required positions. An empty\n// inner slice must act as \"accept any token at position i\" while still\n// enforcing S[i+1..].\nfunc TestNLookaheadNullMiddleSlotIsWildcard(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"A\")\n\tTC := j.Token(\"#TC\", \"C\")\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\t// Middle position empty = wildcard (no Tin constraint).\n\t\t\t// Outer positions must still match.\n\t\t\tS: [][]Tin{{TA}, nil, {TC}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = \"ok\"\n\t\t\t},\n\t\t}}, rs.Open...)\n\t})\n\n\tcases := []struct {\n\t\tin     string\n\t\twant   string\n\t\terrStr string\n\t}{\n\t\t{\"AxC\", \"ok\", \"\"},\n\t\t{\"A!C\", \"ok\", \"\"},\n\t\t{\"AxD\", \"\", \"unexpected\"},\n\t\t{\"BxC\", \"\", \"unexpected\"},\n\t}\n\tfor _, tc := range cases {\n\t\tgot, err := j.Parse(tc.in)\n\t\tif tc.errStr != \"\" {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"%q: expected error, got %v\", tc.in, got)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%q: unexpected error: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"%q: want %s, got %v\", tc.in, tc.want, got)\n\t\t}\n\t}\n}\n\n// TestNLookaheadBacktrackN3 verifies that a three-token match with\n// b: 3 leaves all three tokens in ctx.T for the child / next rule to\n// consume. The consumed count becomes 0 so the buffer is not shifted.\nfunc TestNLookaheadBacktrackN3(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"A\")\n\tTB := j.Token(\"#TB\", \"B\")\n\tTC := j.Token(\"#TC\", \"C\")\n\n\tvar seen []string\n\n\t// val: match A B C with b:3, push to `collect`.\n\t// collect: consume A, then B, then C one at a time, logging each.\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\tS: [][]Tin{{TA}, {TB}, {TC}},\n\t\t\tB: 3,\n\t\t\tP: \"collect\",\n\t\t}}, rs.Open...)\n\t})\n\n\tj.Rule(\"collect\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = []*AltSpec{{\n\t\t\tS: [][]Tin{{TA}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tseen = append(seen, string(r.O[0].Src))\n\t\t\t},\n\t\t\tR: \"collectB\",\n\t\t}}\n\t\trs.Close = []*AltSpec{{}}\n\t})\n\n\tj.Rule(\"collectB\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = []*AltSpec{{\n\t\t\tS: [][]Tin{{TB}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tseen = append(seen, string(r.O[0].Src))\n\t\t\t},\n\t\t\tR: \"collectC\",\n\t\t}}\n\t\trs.Close = []*AltSpec{{}}\n\t})\n\n\tj.Rule(\"collectC\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = []*AltSpec{{\n\t\t\tS: [][]Tin{{TC}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tseen = append(seen, string(r.O[0].Src))\n\t\t\t\tr.Node = \"done\"\n\t\t\t},\n\t\t}}\n\t\trs.Close = []*AltSpec{{}}\n\t})\n\n\t_, err := j.Parse(\"ABC\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif len(seen) != 3 || seen[0] != \"A\" || seen[1] != \"B\" || seen[2] != \"C\" {\n\t\tt.Errorf(\"expected [A B C], got %v\", seen)\n\t}\n}\n"
  },
  {
    "path": "go/options.go",
    "content": "package jsonic\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n)\n\n// Options configures a Jsonic parser instance.\n// All fields use pointer types so that nil means \"use default\".\n// This matches the TypeScript pattern where unset options fall back to defaults.\ntype Options struct {\n\t// Safe controls prototype-pollution-style key safety.\n\tSafe *SafeOptions\n\n\t// Fixed controls fixed token recognition ({, }, [, ], :, ,).\n\tFixed *FixedOptions\n\n\t// Match controls custom regexp-based token and value matching.\n\t// Matches TS options.match.\n\tMatch *MatchOptions\n\n\t// TokenSet allows customizing token sets (VAL, KEY, etc.).\n\t// Matches TS options.tokenSet.\n\tTokenSet map[string][]string\n\n\t// Space controls space/tab lexing.\n\tSpace *SpaceOptions\n\n\t// Line controls line-ending lexing.\n\tLine *LineOptions\n\n\t// Text controls unquoted text lexing.\n\tText *TextOptions\n\n\t// Number controls numeric literal lexing.\n\tNumber *NumberOptions\n\n\t// Comment controls comment lexing.\n\tComment *CommentOptions\n\n\t// String controls quoted string lexing.\n\tString *StringOptions\n\n\t// Map controls object/map merging behavior.\n\tMap *MapOptions\n\n\t// List controls array/list behavior.\n\tList *ListOptions\n\n\t// Value controls keyword literal matching (true, false, null, etc.).\n\tValue *ValueOptions\n\n\t// Ender lists additional characters that end text tokens.\n\tEnder []string\n\n\t// Rule controls parser rule behavior.\n\tRule *RuleOptions\n\n\t// Lex controls global lexer behavior (empty source, etc.).\n\tLex *LexOptions\n\n\t// Parse provides hooks invoked during parsing (distinct from Parser,\n\t// which replaces the parser entrypoint). Mirrors TS `options.parse`.\n\tParse *ParseOptions\n\n\t// Parser allows custom parser overrides.\n\tParser *ParserOptions\n\n\t// Result controls parse result validation.\n\tResult *ResultOptions\n\n\t// Error provides custom error message templates keyed by error code.\n\t// e.g. {\"unexpected\": \"unexpected character(s): {src}\"}\n\tError map[string]string\n\n\t// Hint provides additional explanatory text per error code.\n\tHint map[string]string\n\n\t// ErrMsg controls error message formatting.\n\tErrMsg *ErrMsgOptions\n\n\t// Info controls metadata attachment to parsed output nodes.\n\t// Matches TS options.info.\n\tInfo *InfoOptions\n\n\t// Property holds Go-specific options not present in the TypeScript version.\n\tProperty *PropertyOptions\n\n\t// Color controls ANSI colour codes in formatted error messages.\n\t// Mirrors TS `options.color`.\n\tColor *ColorOptions\n\n\t// Tag is an instance identifier tag.\n\tTag string\n}\n\n// ColorOptions configures the ANSI escape codes used when formatting a\n// JsonicError. When Active is nil or *Active is true, the non-empty\n// codes are wrapped around the appropriate parts of the error output.\n// Setting *Active to false disables colour output entirely regardless\n// of the other fields. Mirrors TS `options.color`.\ntype ColorOptions struct {\n\t// Active toggles colour output. Default: true (matches TS).\n\tActive *bool\n\n\t// Reset clears all colour/style attributes. Default: ESC[0m.\n\tReset string\n\n\t// Hi highlights the error header (`[tag/code]:`). Default: ESC[91m.\n\tHi string\n\n\t// Lo dims trailing suffix material (internal diagnostics, links).\n\t// Default: ESC[2m.\n\tLo string\n\n\t// Line colours the source-location arrow and line-number gutter.\n\t// Default: ESC[34m.\n\tLine string\n}\n\n// ErrMsgOptions controls error message formatting.\n// Matches the TypeScript errmsg option.\ntype ErrMsgOptions struct {\n\t// Name sets the error tag in formatted messages.\n\t// Default: \"jsonic\". E.g. Name=\"bar\" → \"[bar/unexpected]: ...\"\n\tName string\n\n\t// Suffix controls the optional trailing text after an error message.\n\t// Mirrors TS `options.errmsg.suffix` which accepts bool | string |\n\t// function. In Go the permitted concrete types are:\n\t//   bool   — true (default) enables the standard suffix, false disables.\n\t//   string — literal text appended to the error message.\n\t//   func(code, src string) string — dynamic suffix computation.\n\t// Stored as any because the TS field is also polymorphic.\n\tSuffix any\n}\n\n// InfoOptions controls metadata attachment to parsed output nodes.\n// Matches TS options.info.\ntype InfoOptions struct {\n\t// Map enables returning maps as MapRef structs instead of map[string]any.\n\t// When true, map values include an Implicit flag indicating whether\n\t// the map was created implicitly (without braces). Default: false.\n\tMap *bool\n\n\t// List enables returning lists as ListRef structs instead of []any.\n\t// When true, list values include an Implicit flag indicating whether\n\t// the list was created implicitly (without brackets). Default: false.\n\tList *bool\n\n\t// Text enables extended text info in output.\n\t// When true, string and text values are wrapped in Text structs\n\t// that include the quote character used. Default: false.\n\tText *bool\n\n\t// Marker is the key under which info metadata is stored on wrapped\n\t// values. Mirrors TS `options.info.marker`. Default: \"__info__\".\n\tMarker string\n}\n\n// PropertyOptions holds Go-specific options not present in the TypeScript version.\ntype PropertyOptions struct {\n\t// ConfigModify callbacks, keyed by name.\n\t// Called after config construction to allow dynamic customization.\n\tConfigModify map[string]ConfigModifier\n}\n\n// MatchOptions controls custom regexp-based matching.\n// Matches TS options.match.\ntype MatchOptions struct {\n\tLex   *bool                     // Enable custom matching. Default: true.\n\tToken map[string]*regexp.Regexp // \"#NAME\" → regexp pattern for custom tokens.\n\tValue map[string]*MatchValueSpec // name → {Match, Val} for custom value matchers.\n\n\t// Check is a LexCheck hook invoked before the match matcher runs.\n\t// Mirrors TS `options.match.check`.\n\tCheck LexCheck\n}\n\n// MatchValueSpec defines a regexp-based value matcher.\n// Matches TS options.match.value[name].\ntype MatchValueSpec struct {\n\tMatch *regexp.Regexp       // Regexp pattern to match against.\n\tVal   func([]string) any   // Optional value transformer, receives match groups.\n}\n\n// SafeOptions controls key safety.\ntype SafeOptions struct {\n\tKey *bool // Prevent __proto__ keys. Default: true.\n}\n\n// FixedOptions controls fixed token recognition.\ntype FixedOptions struct {\n\tLex *bool // Enable fixed tokens. Default: true.\n\n\t// Token overrides the source string for named fixed tokens.\n\t// Mirrors TS `options.fixed.token` (a StrMap of name → src).\n\t// Keys are token names (e.g. \"#CA\"). Values:\n\t//   - non-nil string pointer: set that name's fixed source, removing any\n\t//     previous source for the same name (\"#CA\" → \";\" swaps the comma).\n\t//     Unknown names are allocated a new Tin (matches (*Jsonic).Token).\n\t//   - nil pointer: remove the name's fixed mapping(s) from the lexer.\n\tToken map[string]*string\n\n\t// Check is a LexCheck hook invoked before the fixed matcher runs.\n\t// Return nil to continue normal matching or a LexCheckResult to\n\t// override. Mirrors TS `options.fixed.check`.\n\tCheck LexCheck\n}\n\n// SpaceOptions controls space lexing.\ntype SpaceOptions struct {\n\tLex   *bool  // Enable space lexing. Default: true.\n\tChars string // Space characters. Default: \" \\t\".\n\n\t// Check is a LexCheck hook invoked before the space matcher runs.\n\t// Mirrors TS `options.space.check`.\n\tCheck LexCheck\n}\n\n// LineOptions controls line-ending lexing.\ntype LineOptions struct {\n\tLex      *bool  // Enable line lexing. Default: true.\n\tChars    string // Line characters. Default: \"\\r\\n\".\n\tRowChars string // Row-counting characters. Default: \"\\n\".\n\tSingle   *bool  // Generate separate tokens per newline. Default: false.\n\n\t// Check is a LexCheck hook invoked before the line matcher runs.\n\t// Mirrors TS `options.line.check`.\n\tCheck LexCheck\n}\n\n// ValModifier transforms a text token value after lexing.\ntype ValModifier func(val any) any\n\n// TextOptions controls unquoted text lexing.\ntype TextOptions struct {\n\tLex    *bool         // Enable text matching. Default: true.\n\tModify []ValModifier // Pipeline of value modifiers applied after text matching.\n\n\t// Check is a LexCheck hook invoked before the text matcher runs.\n\t// Mirrors TS `options.text.check`.\n\tCheck LexCheck\n}\n\n// NumberOptions controls numeric literal lexing.\ntype NumberOptions struct {\n\tLex     *bool             // Enable number matching. Default: true.\n\tHex     *bool             // Support 0x hex format. Default: true.\n\tOct     *bool             // Support 0o octal format. Default: true.\n\tBin     *bool             // Support 0b binary format. Default: true.\n\tSep     string            // Number separator character. Default: \"_\". Empty string disables.\n\tExclude func(string) bool // Exclude certain number-like strings from number matching.\n\n\t// Check is a LexCheck hook invoked before the number matcher runs.\n\t// Mirrors TS `options.number.check`.\n\tCheck LexCheck\n}\n\n// CommentDef defines a single comment type.\ntype CommentDef struct {\n\tLine    bool   // true = line comment, false = block comment.\n\tStart   string // Start marker, e.g. \"#\", \"//\", \"/*\".\n\tEnd     string // End marker for block comments, e.g. \"*/\".\n\tLex     *bool  // Enable this comment type. Default: true.\n\tEatLine *bool  // Also consume trailing line chars. Default: false.\n\n\t// Suffix terminates a comment body at an additional marker beyond\n\t// its natural end (line char for line comments, End for block\n\t// comments). When matched, the suffix is CONSUMED as the last part\n\t// of the comment body.\n\t//\n\t// Accepts one of:\n\t//   string                — single suffix marker.\n\t//   []string              — any of these markers terminates.\n\t//   LexMatcher            — custom terminator probe: a non-nil\n\t//                           returned token signals termination;\n\t//                           len(token.Src) characters are consumed.\n\t//\n\t// EatLine still only fires for line-char termination; it does not\n\t// stack with Suffix consumption.\n\t//\n\t// Mirrors TS `options.comment.def.<name>.suffix`.\n\tSuffix any\n}\n\n// CommentOptions controls comment lexing.\ntype CommentOptions struct {\n\tLex *bool                  // Enable all comment lexing. Default: true.\n\tDef map[string]*CommentDef // Comment type definitions.\n\n\t// Check is a LexCheck hook invoked before the comment matcher runs.\n\t// Mirrors TS `options.comment.check`.\n\tCheck LexCheck\n}\n\n// StringOptions controls quoted string lexing.\ntype StringOptions struct {\n\tLex          *bool             // Enable string matching. Default: true.\n\tChars        string            // Quote characters. Default: `'\"` + \"`\".\n\tMultiChars   string            // Multiline quote characters. Default: \"`\".\n\tEscapeChar   string            // Escape character. Default: \"\\\\\".\n\tEscape       map[string]string // Escape mappings, e.g. {\"n\": \"\\n\"}.\n\tAllowUnknown *bool             // Allow unknown escapes. Default: true.\n\tAbandon      *bool             // On string error, return nil to let next matcher try. Default: false.\n\tReplace      map[rune]string   // Character replacements applied during string scanning.\n\n\t// Check is a LexCheck hook invoked before the string matcher runs.\n\t// Mirrors TS `options.string.check`.\n\tCheck LexCheck\n}\n\n// MapMergeFunc is a custom merge function for duplicate map keys.\n// Receives the previous value, new value, rule, and context.\ntype MapMergeFunc func(prev, val any, r *Rule, ctx *Context) any\n\n// MapOptions controls object/map behavior.\ntype MapOptions struct {\n\tExtend *bool        // Deep-merge duplicate keys. Default: true.\n\tChild  *bool        // Parse bare colon as child$ key: {:1} → {\"child$\":1}. Default: false.\n\tMerge  MapMergeFunc // Custom merge function for duplicate keys. Takes precedence over Extend.\n}\n\n// ListOptions controls array/list behavior.\ntype ListOptions struct {\n\tProperty *bool // Allow named properties in arrays [a:1]. Default: true.\n\tPair     *bool // Push pairs as object elements: [a:1] → [{\"a\":1}]. Default: false.\n\tChild    *bool // Parse bare colon as child value: [:1] → ListRef with Child=1. Default: false.\n}\n\n// ValueDef defines a keyword value.\ntype ValueDef struct {\n\tVal any // Value to produce for this keyword.\n\n\t// Match is a RegExp pattern for regex-based value matching.\n\t// When set, the value keyword is matched by regex instead of exact string.\n\t// Matches TS options.value.def[name].match.\n\tMatch *regexp.Regexp\n\n\t// ValFunc is a function that produces the value from regex match groups.\n\t// Used when Match is set and the value depends on the match result.\n\t// Matches TS value.def[name].val when val is a function.\n\tValFunc func(match []string) any\n\n\t// Consume, when true, matches against the full forward source (not just\n\t// the text token). The regexp should start with ^.\n\tConsume bool\n}\n\n// ValueOptions controls keyword value matching.\ntype ValueOptions struct {\n\tLex *bool                // Enable value matching. Default: true.\n\tDef map[string]*ValueDef // Keyword definitions, e.g. {\"true\": {Val: true}}.\n}\n\n// RuleOptions controls parser rule behavior.\ntype RuleOptions struct {\n\tStart  string // Starting rule name. Default: \"val\".\n\tFinish *bool  // Auto-close unclosed structures at EOF. Default: true.\n\tMaxMul *int   // Max rule occurrence multiplier. Default: 3.\n\n\t// Include is a comma-separated list of group tags. When non-empty,\n\t// only grammar alternates whose G field contains at least one of\n\t// these tags survive; all other alts (including those with no tags)\n\t// are dropped. Applied before Exclude.\n\tInclude string\n\n\t// Exclude is a comma-separated list of group tags. Grammar\n\t// alternates whose G field contains any of these tags are removed.\n\t// Applied after Include.\n\tExclude string\n}\n\n// LexOptions controls global lex behavior.\ntype LexOptions struct {\n\tEmpty       *bool // Allow empty source. Default: true.\n\tEmptyResult any   // Result for empty source. Default: nil.\n\n\t// Match defines custom lexer matchers, keyed by name.\n\t// Matches the TypeScript pattern: jsonic.options({ lex: { match: { name: { order, make } } } })\n\tMatch map[string]*MatchSpec\n}\n\n// ParserOptions allows custom parser overrides.\ntype ParserOptions struct {\n\tStart func(src string, j *Jsonic, meta map[string]any) (any, error)\n}\n\n// ParseOptions holds parse-time hooks. Mirrors TS `options.parse`.\ntype ParseOptions struct {\n\t// Prepare is a map of named callbacks invoked once at the start of\n\t// every parse, in name order. The map form matches TS (which uses\n\t// an object so plugins can replace each others' entries by name).\n\tPrepare map[string]func(ctx *Context)\n}\n\n// ResultOptions controls parse result validation.\n// Matches the TypeScript result option.\ntype ResultOptions struct {\n\t// Fail lists values that are treated as parse failures.\n\t// If the parse result matches any of these, an \"unexpected\" error is returned.\n\t// E.g. Fail: []any{nil} would make nil results fail.\n\tFail []any\n}\n\n// ConfigModifier is a function that modifies the LexConfig after construction.\ntype ConfigModifier func(cfg *LexConfig, opts *Options)\n\n// idCounter is used to generate unique Jsonic instance IDs.\nvar idCounter int\n\n// Jsonic is a configured parser instance, equivalent to TypeScript's Jsonic.make().\ntype Jsonic struct {\n\tid           string             // Unique instance identifier (TS: jsonic.id)\n\toptions      *Options\n\tparser       *Parser\n\tplugins      []pluginEntry      // Registered plugins\n\ttinByName    map[string]Tin     // Custom token name → Tin\n\tnameByTin    map[Tin]string     // Custom Tin → token name\n\tnextTin      Tin                // Next available Tin for allocation\n\tlexSubs      []LexSub           // Lex event subscribers\n\truleSubs     []RuleSub          // Rule event subscribers\n\thints        map[string]string  // Error hints per error code\n\temptyAllow   bool               // Allow empty source\n\temptyResult  any                // Result for empty source\n\tparserStart  func(src string, j *Jsonic, meta map[string]any) (any, error)\n\tinSetOptions bool               // Re-entrancy guard for SetOptions\n\tdecorations     map[string]any     // Plugin decorations (TS: jsonic.foo = value)\n\tpluginOpts      map[string]map[string]any // Plugin options namespace (TS: options.plugin)\n\tcustomTokenSets map[string][]Tin  // Custom token sets (TS: options.tokenSet)\n}\n\n// Decorate sets a named value on this instance. This is the Go equivalent of\n// the TypeScript pattern where plugins add properties to the jsonic instance\n// (e.g. jsonic.foo = () => 'FOO'). Decorations are inherited by Derive.\nfunc (j *Jsonic) Decorate(name string, value any) *Jsonic {\n\tif j.decorations == nil {\n\t\tj.decorations = make(map[string]any)\n\t}\n\tj.decorations[name] = value\n\treturn j\n}\n\n// Decoration returns a named value previously set by Decorate.\n// Returns nil if the name has not been set.\nfunc (j *Jsonic) Decoration(name string) any {\n\tif j.decorations == nil {\n\t\treturn nil\n\t}\n\treturn j.decorations[name]\n}\n\n// Id returns the unique instance identifier for this Jsonic instance.\n// Matches TS jsonic.id.\nfunc (j *Jsonic) Id() string {\n\treturn j.id\n}\n\n// PluginOptions returns the options stored for a named plugin.\n// Matches TS `jsonic.options.plugin[name]`.\nfunc (j *Jsonic) PluginOptions(name string) map[string]any {\n\tif j.pluginOpts == nil {\n\t\treturn nil\n\t}\n\treturn j.pluginOpts[name]\n}\n\n// SetPluginOptions stores options for a named plugin.\n// Matches TS `jsonic.options({ plugin: { name: opts } })`.\nfunc (j *Jsonic) SetPluginOptions(name string, opts map[string]any) {\n\tif j.pluginOpts == nil {\n\t\tj.pluginOpts = make(map[string]map[string]any)\n\t}\n\texisting := j.pluginOpts[name]\n\tif existing == nil {\n\t\tj.pluginOpts[name] = opts\n\t} else {\n\t\tfor k, v := range opts {\n\t\t\texisting[k] = v\n\t\t}\n\t}\n}\n\n// Make creates a new Jsonic parser instance with the given options.\n// Unset option fields fall back to defaults, matching TypeScript Jsonic.make().\nfunc Make(opts ...Options) *Jsonic {\n\tvar o Options\n\tif len(opts) > 0 {\n\t\to = opts[0]\n\t}\n\n\tcfg := buildConfig(&o)\n\trsm := make(map[string]*RuleSpec)\n\tbuildGrammar(rsm, cfg)\n\n\tmaxmul := 3\n\tif o.Rule != nil && o.Rule.MaxMul != nil {\n\t\tmaxmul = *o.Rule.MaxMul\n\t}\n\n\t// Copy global FixedTokens into the config for per-instance customization.\n\tcfg.FixedTokens = make(map[string]Tin, len(FixedTokens))\n\tfor k, v := range FixedTokens {\n\t\tcfg.FixedTokens[k] = v\n\t}\n\tcfg.SortFixedTokens()\n\n\t// Copy global error messages as defaults.\n\tmsgs := make(map[string]string, len(errorMessages))\n\tfor k, v := range errorMessages {\n\t\tmsgs[k] = v\n\t}\n\n\tp := &Parser{Config: cfg, RSM: rsm, MaxMul: maxmul, ErrorMessages: msgs}\n\n\t// Initialize built-in token name mappings.\n\ttinByName := map[string]Tin{\n\t\t\"#BD\": TinBD, \"#ZZ\": TinZZ, \"#UK\": TinUK, \"#AA\": TinAA,\n\t\t\"#SP\": TinSP, \"#LN\": TinLN, \"#CM\": TinCM, \"#NR\": TinNR,\n\t\t\"#ST\": TinST, \"#TX\": TinTX, \"#VL\": TinVL, \"#OB\": TinOB,\n\t\t\"#CB\": TinCB, \"#OS\": TinOS, \"#CS\": TinCS, \"#CL\": TinCL,\n\t\t\"#CA\": TinCA,\n\t}\n\tnameByTin := make(map[Tin]string, len(tinByName))\n\tfor name, tin := range tinByName {\n\t\tnameByTin[tin] = name\n\t}\n\n\tidCounter++\n\ttag := \"\"\n\tif o.Tag != \"\" {\n\t\ttag = \"/\" + o.Tag\n\t}\n\tinstanceId := \"Jsonic/\" + strconv.Itoa(idCounter) + tag\n\n\tj := &Jsonic{\n\t\tid:          instanceId,\n\t\toptions:     &o,\n\t\tparser:      p,\n\t\ttinByName:   tinByName,\n\t\tnameByTin:   nameByTin,\n\t\tnextTin:     TinMAX,\n\t\temptyAllow:  true, // default: allow empty source\n\t}\n\n\t// Apply custom error messages.\n\tif o.Error != nil {\n\t\tfor k, v := range o.Error {\n\t\t\tj.parser.ErrorMessages[k] = v\n\t\t}\n\t}\n\n\t// Apply error hints.\n\tif o.Hint != nil {\n\t\tj.hints = make(map[string]string, len(o.Hint))\n\t\tj.parser.Hints = make(map[string]string, len(o.Hint))\n\t\tfor k, v := range o.Hint {\n\t\t\tj.hints[k] = v\n\t\t\tj.parser.Hints[k] = v\n\t\t}\n\t}\n\n\t// Apply errmsg options.\n\tif o.ErrMsg != nil && o.ErrMsg.Name != \"\" {\n\t\tj.parser.ErrTag = o.ErrMsg.Name\n\t}\n\n\t// Apply lex options (empty source handling).\n\tif o.Lex != nil {\n\t\tif o.Lex.Empty != nil {\n\t\t\tj.emptyAllow = *o.Lex.Empty\n\t\t}\n\t\tj.emptyResult = o.Lex.EmptyResult\n\t}\n\n\t// Apply custom parser start.\n\tif o.Parser != nil && o.Parser.Start != nil {\n\t\tj.parserStart = o.Parser.Start\n\t}\n\n\t// Apply rule include first, then exclude — mirrors SetOptions.\n\tif o.Rule != nil && o.Rule.Include != \"\" {\n\t\tj.include(o.Rule.Include)\n\t}\n\tif o.Rule != nil && o.Rule.Exclude != \"\" {\n\t\tj.exclude(o.Rule.Exclude)\n\t}\n\n\t// Apply lex.match specs passed at construction.\n\tj.registerMatchSpecs(&o)\n\n\t// Apply fixed.token overrides passed at construction.\n\tj.applyFixedTokens(&o)\n\n\treturn j\n}\n\n// Empty creates a Jsonic instance with no built-in grammar rules.\n// Matches TS jsonic.empty(). Useful for building a parser from scratch.\nfunc Empty(opts ...Options) *Jsonic {\n\tj := Make(opts...)\n\t// Clear all grammar rules.\n\tfor _, rs := range j.parser.RSM {\n\t\trs.Clear()\n\t}\n\treturn j\n}\n\n// MakeJSON creates a Jsonic instance configured to accept strict JSON only.\n// Mirrors TS Jsonic.make('json') (src/grammar.ts:980). Rejects jsonic\n// relaxations: unquoted keys/values, comments, hex/octal/binary numbers,\n// trailing commas, leading-zero numbers, single-quoted or backtick\n// strings, and empty input.\nfunc MakeJSON() *Jsonic {\n\tf := false\n\treturn Make(Options{\n\t\tText: &TextOptions{Lex: &f},\n\t\tNumber: &NumberOptions{\n\t\t\tHex: &f, Oct: &f, Bin: &f,\n\t\t\tSep: \"\",\n\t\t\tExclude: func(s string) bool {\n\t\t\t\treturn len(s) >= 2 && s[0] == '0' && s[1] == '0'\n\t\t\t},\n\t\t},\n\t\tString: &StringOptions{\n\t\t\tChars:        `\"`,\n\t\t\tMultiChars:   \"\",\n\t\t\tAllowUnknown: &f,\n\t\t},\n\t\tComment: &CommentOptions{Lex: &f},\n\t\tMap:     &MapOptions{Extend: &f},\n\t\tLex:     &LexOptions{Empty: &f},\n\t\tRule: &RuleOptions{\n\t\t\tFinish:  &f,\n\t\t\tInclude: \"json\",\n\t\t},\n\t})\n}\n\n// Parse parses a jsonic string using this instance's configuration.\nfunc (j *Jsonic) Parse(src string) (any, error) {\n\treturn j.parseInternal(src, nil)\n}\n\n// parseInternal handles empty source, custom parser.start, and delegation.\nfunc (j *Jsonic) parseInternal(src string, meta map[string]any) (any, error) {\n\t// Handle empty source.\n\tif src == \"\" {\n\t\tif !j.emptyAllow {\n\t\t\treturn nil, j.parser.makeError(\"unexpected\", \"\", src, 0, 1, 1)\n\t\t}\n\t\treturn j.emptyResult, nil\n\t}\n\n\t// Custom parser start.\n\tif j.parserStart != nil {\n\t\tresult, err := j.parserStart(src, j, meta)\n\t\treturn result, j.attachHint(err)\n\t}\n\n\tresult, err := j.parser.startParse(src, meta, j.lexSubs, j.ruleSubs, j)\n\treturn result, j.attachHint(err)\n}\n\n// attachHint adds hint text to a JsonicError if hints are configured.\nfunc (j *Jsonic) attachHint(err error) error {\n\tif err == nil || j.hints == nil {\n\t\treturn err\n\t}\n\tif je, ok := err.(*JsonicError); ok && je.Hint == \"\" {\n\t\tif hint, ok := j.hints[je.Code]; ok {\n\t\t\tje.Hint = hint\n\t\t}\n\t}\n\treturn err\n}\n\n// Options returns a copy of this instance's options.\nfunc (j *Jsonic) Options() Options {\n\tif j.options != nil {\n\t\treturn *j.options\n\t}\n\treturn Options{}\n}\n\n// boolPtr is a helper to create a *bool.\nfunc boolPtr(b bool) *bool {\n\treturn &b\n}\n\n// intPtr is a helper to create a *int.\nfunc intPtr(i int) *int {\n\treturn &i\n}\n\n// boolVal returns the value of a *bool, or the default if nil.\nfunc boolVal(p *bool, def bool) bool {\n\tif p != nil {\n\t\treturn *p\n\t}\n\treturn def\n}\n\n// buildConfig converts Options into a LexConfig, applying defaults for unset fields.\nfunc buildConfig(o *Options) *LexConfig {\n\tcfg := &LexConfig{}\n\n\t// Fixed tokens\n\tcfg.FixedLex = boolVal(optBool(o.Fixed, func(f *FixedOptions) *bool { return f.Lex }), true)\n\tif o.Fixed != nil {\n\t\tcfg.FixedCheck = o.Fixed.Check\n\t}\n\n\t// Match (custom regexp matchers - TS: options.match)\n\t// Always initialize maps so SetOptions can add entries later.\n\tcfg.MatchTokens = make(map[Tin]*regexp.Regexp)\n\tif o.Match != nil {\n\t\tcfg.MatchLex = boolVal(o.Match.Lex, true)\n\t\tif o.Match.Value != nil {\n\t\t\tcfg.MatchValues = make([]*MatchValueEntry, 0, len(o.Match.Value))\n\t\t\tfor name, spec := range o.Match.Value {\n\t\t\t\tif spec == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcfg.MatchValues = append(cfg.MatchValues, &MatchValueEntry{\n\t\t\t\t\tName:  name,\n\t\t\t\t\tMatch: spec.Match,\n\t\t\t\t\tVal:   spec.Val,\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Sort by name for deterministic iteration at lex time.\n\t\t\tsort.Slice(cfg.MatchValues, func(i, j int) bool {\n\t\t\t\treturn cfg.MatchValues[i].Name < cfg.MatchValues[j].Name\n\t\t\t})\n\t\t}\n\t\tcfg.MatchCheck = o.Match.Check\n\t}\n\n\t// Space\n\tcfg.SpaceLex = boolVal(optBool(o.Space, func(s *SpaceOptions) *bool { return s.Lex }), true)\n\tif o.Space != nil && o.Space.Chars != \"\" {\n\t\tcfg.SpaceChars = runeSet(o.Space.Chars)\n\t} else {\n\t\tcfg.SpaceChars = map[rune]bool{' ': true, '\\t': true}\n\t}\n\tif o.Space != nil {\n\t\tcfg.SpaceCheck = o.Space.Check\n\t}\n\n\t// Line\n\tcfg.LineLex = boolVal(optBool(o.Line, func(l *LineOptions) *bool { return l.Lex }), true)\n\tcfg.LineSingle = boolVal(optBool(o.Line, func(l *LineOptions) *bool { return l.Single }), false)\n\tif o.Line != nil {\n\t\tcfg.LineCheck = o.Line.Check\n\t}\n\tif o.Line != nil && o.Line.Chars != \"\" {\n\t\tcfg.LineChars = runeSet(o.Line.Chars)\n\t} else {\n\t\tcfg.LineChars = map[rune]bool{'\\r': true, '\\n': true}\n\t}\n\tif o.Line != nil && o.Line.RowChars != \"\" {\n\t\tcfg.RowChars = runeSet(o.Line.RowChars)\n\t} else {\n\t\tcfg.RowChars = map[rune]bool{'\\n': true}\n\t}\n\n\t// Text\n\tcfg.TextLex = boolVal(optBool(o.Text, func(t *TextOptions) *bool { return t.Lex }), true)\n\tif o.Text != nil && len(o.Text.Modify) > 0 {\n\t\tcfg.TextModify = o.Text.Modify\n\t}\n\tif o.Text != nil {\n\t\tcfg.TextCheck = o.Text.Check\n\t}\n\n\t// Number\n\tcfg.NumberLex = boolVal(optBool(o.Number, func(n *NumberOptions) *bool { return n.Lex }), true)\n\tif o.Number != nil && o.Number.Exclude != nil {\n\t\tcfg.NumberExclude = o.Number.Exclude\n\t}\n\tif o.Number != nil {\n\t\tcfg.NumberCheck = o.Number.Check\n\t}\n\tcfg.NumberHex = boolVal(optBool(o.Number, func(n *NumberOptions) *bool { return n.Hex }), true)\n\tcfg.NumberOct = boolVal(optBool(o.Number, func(n *NumberOptions) *bool { return n.Oct }), true)\n\tcfg.NumberBin = boolVal(optBool(o.Number, func(n *NumberOptions) *bool { return n.Bin }), true)\n\tif o.Number != nil && o.Number.Sep != \"\" {\n\t\tcfg.NumberSep = rune(o.Number.Sep[0])\n\t} else if o.Number != nil && o.Number.Sep == \"\" && o.Number.Lex != nil {\n\t\t// Explicitly set to empty: disable separator\n\t\tcfg.NumberSep = 0\n\t} else {\n\t\tcfg.NumberSep = '_'\n\t}\n\n\t// Comment\n\tcfg.CommentLex = boolVal(optBool(o.Comment, func(c *CommentOptions) *bool { return c.Lex }), true)\n\tif o.Comment != nil {\n\t\tcfg.CommentCheck = o.Comment.Check\n\t}\n\tif o.Comment != nil && o.Comment.Def != nil {\n\t\tcfg.CommentLine = nil\n\t\tcfg.CommentBlock = nil\n\t\tcfg.CommentLineEatLine = make(map[string]bool)\n\t\tcfg.CommentBlockEatLine = make(map[string]bool)\n\t\tcfg.CommentLineSuffixes = nil\n\t\tcfg.CommentBlockSuffixes = nil\n\t\tcfg.CommentLineSuffixFuncs = nil\n\t\tcfg.CommentBlockSuffixFuncs = nil\n\t\tfor _, def := range o.Comment.Def {\n\t\t\tif def == nil || !boolVal(def.Lex, true) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\teatLine := boolVal(def.EatLine, false)\n\t\t\tsuffixStrs, suffixFn := normalizeCommentSuffix(def.Suffix)\n\t\t\tif def.Line {\n\t\t\t\tcfg.CommentLine = append(cfg.CommentLine, def.Start)\n\t\t\t\tif eatLine {\n\t\t\t\t\tcfg.CommentLineEatLine[def.Start] = true\n\t\t\t\t}\n\t\t\t\tif len(suffixStrs) > 0 {\n\t\t\t\t\tif cfg.CommentLineSuffixes == nil {\n\t\t\t\t\t\tcfg.CommentLineSuffixes = make(map[string][]string)\n\t\t\t\t\t}\n\t\t\t\t\tcfg.CommentLineSuffixes[def.Start] = suffixStrs\n\t\t\t\t}\n\t\t\t\tif suffixFn != nil {\n\t\t\t\t\tif cfg.CommentLineSuffixFuncs == nil {\n\t\t\t\t\t\tcfg.CommentLineSuffixFuncs = make(map[string]LexMatcher)\n\t\t\t\t\t}\n\t\t\t\t\tcfg.CommentLineSuffixFuncs[def.Start] = suffixFn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcfg.CommentBlock = append(cfg.CommentBlock, [2]string{def.Start, def.End})\n\t\t\t\tif eatLine {\n\t\t\t\t\tcfg.CommentBlockEatLine[def.Start] = true\n\t\t\t\t}\n\t\t\t\tif len(suffixStrs) > 0 {\n\t\t\t\t\tif cfg.CommentBlockSuffixes == nil {\n\t\t\t\t\t\tcfg.CommentBlockSuffixes = make(map[string][]string)\n\t\t\t\t\t}\n\t\t\t\t\tcfg.CommentBlockSuffixes[def.Start] = suffixStrs\n\t\t\t\t}\n\t\t\t\tif suffixFn != nil {\n\t\t\t\t\tif cfg.CommentBlockSuffixFuncs == nil {\n\t\t\t\t\t\tcfg.CommentBlockSuffixFuncs = make(map[string]LexMatcher)\n\t\t\t\t\t}\n\t\t\t\t\tcfg.CommentBlockSuffixFuncs[def.Start] = suffixFn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Sort by marker length descending (ties by start ascending) so\n\t\t// longer markers shadow shorter ones regardless of how the caller's\n\t\t// Comment.Def map iterated. Mirrors TS makeCommentMatcher.\n\t\tsort.Slice(cfg.CommentLine, func(i, j int) bool {\n\t\t\tif len(cfg.CommentLine[i]) != len(cfg.CommentLine[j]) {\n\t\t\t\treturn len(cfg.CommentLine[i]) > len(cfg.CommentLine[j])\n\t\t\t}\n\t\t\treturn cfg.CommentLine[i] < cfg.CommentLine[j]\n\t\t})\n\t\tsort.Slice(cfg.CommentBlock, func(i, j int) bool {\n\t\t\tif len(cfg.CommentBlock[i][0]) != len(cfg.CommentBlock[j][0]) {\n\t\t\t\treturn len(cfg.CommentBlock[i][0]) > len(cfg.CommentBlock[j][0])\n\t\t\t}\n\t\t\treturn cfg.CommentBlock[i][0] < cfg.CommentBlock[j][0]\n\t\t})\n\t} else {\n\t\tcfg.CommentLine = []string{\"#\", \"//\"}\n\t\tcfg.CommentBlock = [][2]string{{\"/*\", \"*/\"}}\n\t}\n\n\t// String\n\tcfg.StringLex = boolVal(optBool(o.String, func(s *StringOptions) *bool { return s.Lex }), true)\n\tif o.String != nil {\n\t\tcfg.StringCheck = o.String.Check\n\t}\n\tif o.String != nil && o.String.Chars != \"\" {\n\t\tcfg.StringChars = runeSet(o.String.Chars)\n\t} else {\n\t\tcfg.StringChars = map[rune]bool{'\\'': true, '\"': true, '`': true}\n\t}\n\tif o.String != nil && o.String.MultiChars != \"\" {\n\t\tcfg.MultiChars = runeSet(o.String.MultiChars)\n\t} else {\n\t\tcfg.MultiChars = map[rune]bool{'`': true}\n\t}\n\tif o.String != nil && o.String.EscapeChar != \"\" {\n\t\tcfg.EscapeChar = rune(o.String.EscapeChar[0])\n\t} else {\n\t\tcfg.EscapeChar = '\\\\'\n\t}\n\tcfg.AllowUnknownEscape = boolVal(optBool(o.String, func(s *StringOptions) *bool { return s.AllowUnknown }), true)\n\tcfg.StringAbandon = boolVal(optBool(o.String, func(s *StringOptions) *bool { return s.Abandon }), false)\n\tif o.String != nil && o.String.Replace != nil {\n\t\tcfg.StringReplace = o.String.Replace\n\t}\n\tif o.String != nil && o.String.Escape != nil {\n\t\tcfg.EscapeMap = make(map[string]string, len(o.String.Escape))\n\t\tfor k, v := range o.String.Escape {\n\t\t\tcfg.EscapeMap[k] = v\n\t\t}\n\t}\n\n\t// Ender\n\tif len(o.Ender) > 0 {\n\t\tcfg.EnderChars = make(map[rune]bool)\n\t\tfor _, e := range o.Ender {\n\t\t\tfor _, r := range e {\n\t\t\t\tcfg.EnderChars[r] = true\n\t\t\t}\n\t\t}\n\t}\n\n\t// Value\n\tcfg.ValueLex = boolVal(optBool(o.Value, func(v *ValueOptions) *bool { return v.Lex }), true)\n\tif o.Value != nil && o.Value.Def != nil {\n\t\tcfg.ValueDef = make(map[string]any)\n\t\tcfg.ValueDefRe = cfg.ValueDefRe[:0]\n\t\tfor k, v := range o.Value.Def {\n\t\t\tif v == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif v.Match != nil {\n\t\t\t\t// Regex-based value def goes into ValueDefRe (TS: cfg.value.defre)\n\t\t\t\tcfg.ValueDefRe = append(cfg.ValueDefRe, &ValueDefEntry{Name: k, Def: v})\n\t\t\t} else {\n\t\t\t\tcfg.ValueDef[k] = v.Val\n\t\t\t}\n\t\t}\n\t\t// Sort by name for deterministic iteration at lex time.\n\t\tsort.Slice(cfg.ValueDefRe, func(i, j int) bool {\n\t\t\treturn cfg.ValueDefRe[i].Name < cfg.ValueDefRe[j].Name\n\t\t})\n\t}\n\n\t// Map\n\tcfg.MapExtend = boolVal(optBool(o.Map, func(m *MapOptions) *bool { return m.Extend }), true)\n\tcfg.MapChild = boolVal(optBool(o.Map, func(m *MapOptions) *bool { return m.Child }), false)\n\tif o.Map != nil && o.Map.Merge != nil {\n\t\tcfg.MapMerge = o.Map.Merge\n\t}\n\n\t// List\n\tcfg.ListProperty = boolVal(optBool(o.List, func(l *ListOptions) *bool { return l.Property }), true)\n\tcfg.ListPair = boolVal(optBool(o.List, func(l *ListOptions) *bool { return l.Pair }), false)\n\tcfg.ListChild = boolVal(optBool(o.List, func(l *ListOptions) *bool { return l.Child }), false)\n\n\t// Rule\n\tcfg.FinishRule = boolVal(optBool(o.Rule, func(r *RuleOptions) *bool { return r.Finish }), true)\n\tif o.Rule != nil && o.Rule.Start != \"\" {\n\t\tcfg.RuleStart = o.Rule.Start\n\t} else {\n\t\tcfg.RuleStart = \"val\"\n\t}\n\n\t// Safe\n\tcfg.SafeKey = boolVal(optBool(o.Safe, func(s *SafeOptions) *bool { return s.Key }), true)\n\n\t// Initialize per-instance token sets from defaults (matching TS cfg.tokenSetTins).\n\tcfg.IgnoreSet = map[Tin]bool{TinSP: true, TinLN: true, TinCM: true}\n\tcfg.ValSet = make([]Tin, len(TinSetVAL))\n\tcopy(cfg.ValSet, TinSetVAL)\n\tcfg.KeySet = make([]Tin, len(TinSetKEY))\n\tcopy(cfg.KeySet, TinSetKEY)\n\n\t// Info options (metadata on parsed output)\n\tif o.Info != nil {\n\t\tcfg.TextInfo = boolVal(o.Info.Text, false)\n\t\tcfg.ListRef = boolVal(o.Info.List, false)\n\t\tcfg.MapRef = boolVal(o.Info.Map, false)\n\t\tif cfg.MapRef || cfg.ListRef || cfg.TextInfo {\n\t\t\tcfg.InfoMarker = \"__info__\"\n\t\t\tif o.Info.Marker != \"\" {\n\t\t\t\tcfg.InfoMarker = o.Info.Marker\n\t\t\t}\n\t\t}\n\t}\n\t// list.child requires ListRef to store the child value on ListRef.Child.\n\tif cfg.ListChild {\n\t\tcfg.ListRef = true\n\t}\n\n\t// Parse-time hooks. The TS map is keyed by name so plugins can replace\n\t// each others' entries; we preserve insertion-order-free semantics by\n\t// sorting the callbacks by key before storing them.\n\tif o.Parse != nil && len(o.Parse.Prepare) > 0 {\n\t\tnames := make([]string, 0, len(o.Parse.Prepare))\n\t\tfor name := range o.Parse.Prepare {\n\t\t\tnames = append(names, name)\n\t\t}\n\t\tsort.Strings(names)\n\t\tcfg.ParsePrepare = cfg.ParsePrepare[:0]\n\t\tfor _, name := range names {\n\t\t\tif fn := o.Parse.Prepare[name]; fn != nil {\n\t\t\t\tcfg.ParsePrepare = append(cfg.ParsePrepare, fn)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Result fail values.\n\tif o.Result != nil && len(o.Result.Fail) > 0 {\n\t\tcfg.ResultFail = append(cfg.ResultFail, o.Result.Fail...)\n\t}\n\n\t// Colour palette for error formatting. Defaults mirror TS: active,\n\t// bright red header, dim suffix, blue line gutter.\n\tcfg.Color = ColorConfig{\n\t\tActive: boolVal(optBool(o.Color, func(c *ColorOptions) *bool { return c.Active }), true),\n\t\tReset:  \"\\x1b[0m\",\n\t\tHi:     \"\\x1b[91m\",\n\t\tLo:     \"\\x1b[2m\",\n\t\tLine:   \"\\x1b[34m\",\n\t}\n\tif o.Color != nil {\n\t\tif o.Color.Reset != \"\" {\n\t\t\tcfg.Color.Reset = o.Color.Reset\n\t\t}\n\t\tif o.Color.Hi != \"\" {\n\t\t\tcfg.Color.Hi = o.Color.Hi\n\t\t}\n\t\tif o.Color.Lo != \"\" {\n\t\t\tcfg.Color.Lo = o.Color.Lo\n\t\t}\n\t\tif o.Color.Line != \"\" {\n\t\t\tcfg.Color.Line = o.Color.Line\n\t\t}\n\t}\n\n\t// Apply config modifiers.\n\tif o.Property != nil && o.Property.ConfigModify != nil {\n\t\tfor _, mod := range o.Property.ConfigModify {\n\t\t\tmod(cfg, o)\n\t\t}\n\t}\n\n\treturn cfg\n}\n\n// optBool extracts a *bool from an optional sub-options struct.\nfunc optBool[T any](outer *T, getter func(*T) *bool) *bool {\n\tif outer == nil {\n\t\treturn nil\n\t}\n\treturn getter(outer)\n}\n\n// runeSet converts a string into a rune presence map.\nfunc runeSet(s string) map[rune]bool {\n\tm := make(map[rune]bool, len(s))\n\tfor _, r := range s {\n\t\tm[r] = true\n\t}\n\treturn m\n}\n\n// normalizeCommentSuffix splits the polymorphic CommentDef.Suffix value\n// into two internal forms: a list of suffix strings (sorted longest-first\n// so a longer marker shadows a shorter one) and an optional LexMatcher\n// probe. The matcher may also produce strings via non-nil returns; those\n// are handled at match time, not here.  Returns zero-length slice and\n// nil when no suffix is configured.\nfunc normalizeCommentSuffix(raw any) ([]string, LexMatcher) {\n\tif raw == nil {\n\t\treturn nil, nil\n\t}\n\n\tvar strs []string\n\tvar fn LexMatcher\n\n\tswitch v := raw.(type) {\n\tcase string:\n\t\tif v != \"\" {\n\t\t\tstrs = []string{v}\n\t\t}\n\tcase []string:\n\t\tfor _, s := range v {\n\t\t\tif s != \"\" {\n\t\t\t\tstrs = append(strs, s)\n\t\t\t}\n\t\t}\n\tcase []any:\n\t\t// MapToOptions parses JSON arrays into []any.\n\t\tfor _, el := range v {\n\t\t\tif s, ok := el.(string); ok && s != \"\" {\n\t\t\t\tstrs = append(strs, s)\n\t\t\t}\n\t\t}\n\tcase LexMatcher:\n\t\tfn = v\n\tcase func(lex *Lex, rule *Rule) *Token:\n\t\tfn = LexMatcher(v)\n\t}\n\n\tif len(strs) > 1 {\n\t\tsort.Slice(strs, func(i, j int) bool {\n\t\t\tif len(strs[i]) != len(strs[j]) {\n\t\t\t\treturn len(strs[i]) > len(strs[j])\n\t\t\t}\n\t\t\treturn strs[i] < strs[j]\n\t\t})\n\t}\n\treturn strs, fn\n}"
  },
  {
    "path": "go/options_parity_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// --- LexCheck hooks ---\n\nfunc TestFixedCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tcheck := func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}\n\tj := Make(Options{Fixed: &FixedOptions{Check: check}})\n\n\tif _, err := j.Parse(\"{a:1}\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"FixedCheck was never invoked by the lexer\")\n\t}\n}\n\nfunc TestSpaceCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tj := Make(Options{Space: &SpaceOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}}})\n\tif _, err := j.Parse(\"a: 1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"SpaceCheck was never invoked\")\n\t}\n}\n\nfunc TestLineCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tj := Make(Options{Line: &LineOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}}})\n\tif _, err := j.Parse(\"a:1\\nb:2\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"LineCheck was never invoked\")\n\t}\n}\n\nfunc TestTextCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tj := Make(Options{Text: &TextOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}}})\n\tif _, err := j.Parse(\"a:foo\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"TextCheck was never invoked\")\n\t}\n}\n\nfunc TestNumberCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tj := Make(Options{Number: &NumberOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}}})\n\tif _, err := j.Parse(\"a:42\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"NumberCheck was never invoked\")\n\t}\n}\n\nfunc TestStringCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tj := Make(Options{String: &StringOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}}})\n\tif _, err := j.Parse(`a:\"hi\"`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"StringCheck was never invoked\")\n\t}\n}\n\nfunc TestCommentCheckHookFires(t *testing.T) {\n\tcalled := 0\n\tj := Make(Options{Comment: &CommentOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\tcalled++\n\t\treturn nil\n\t}}})\n\tif _, err := j.Parse(\"# hi\\na:1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"CommentCheck was never invoked\")\n\t}\n}\n\nfunc TestLexCheckCanOverrideResult(t *testing.T) {\n\t// A Done result short-circuits the matcher.  Here FixedCheck returns a\n\t// synthetic comma token for every fixed-token lookup attempt, which\n\t// effectively disables {,},[,],: — parsing a map then fails.\n\tj := Make(Options{Fixed: &FixedOptions{Check: func(lex *Lex) *LexCheckResult {\n\t\treturn &LexCheckResult{Done: true, Token: nil}\n\t}}})\n\tif _, err := j.Parse(\"{a:1}\"); err == nil {\n\t\tt.Error(\"expected fixed matcher suppression to break parsing\")\n\t}\n}\n\n// --- info.marker ---\n\nfunc TestInfoMarkerDefault(t *testing.T) {\n\tyes := true\n\tj := Make(Options{Info: &InfoOptions{Map: &yes}})\n\tif m := j.parser.Config.InfoMarker; m != \"__info__\" {\n\t\tt.Errorf(\"expected default marker '__info__', got %q\", m)\n\t}\n}\n\nfunc TestInfoMarkerOverride(t *testing.T) {\n\tyes := true\n\tj := Make(Options{Info: &InfoOptions{Map: &yes, Marker: \"$meta\"}})\n\tif m := j.parser.Config.InfoMarker; m != \"$meta\" {\n\t\tt.Errorf(\"expected marker '$meta', got %q\", m)\n\t}\n}\n\nfunc TestInfoMarkerFromText(t *testing.T) {\n\t// info.marker via SetOptionsText round-trips.\n\tj, err := Make().SetOptionsText(`info: { map: true, marker: \"$$info\" }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif m := j.parser.Config.InfoMarker; m != \"$$info\" {\n\t\tt.Errorf(\"expected '$$info', got %q\", m)\n\t}\n}\n\n// --- parse.prepare ---\n\nfunc TestParsePrepareHooksRunInOrder(t *testing.T) {\n\tvar order []string\n\tj := Make(Options{Parse: &ParseOptions{Prepare: map[string]func(ctx *Context){\n\t\t\"b-second\": func(ctx *Context) { order = append(order, \"second\") },\n\t\t\"a-first\":  func(ctx *Context) { order = append(order, \"first\") },\n\t}}})\n\n\tif _, err := j.Parse(\"a:1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(order) != 2 || order[0] != \"first\" || order[1] != \"second\" {\n\t\tt.Errorf(\"expected [first second] by key-sorted order, got %v\", order)\n\t}\n}\n\nfunc TestParsePrepareSkipsNil(t *testing.T) {\n\t// Nil callbacks are dropped; remaining ones still run.\n\tcalled := false\n\tj := Make(Options{Parse: &ParseOptions{Prepare: map[string]func(ctx *Context){\n\t\t\"a\": nil,\n\t\t\"b\": func(ctx *Context) { called = true },\n\t}}})\n\tif _, err := j.Parse(\"a:1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !called {\n\t\tt.Error(\"non-nil prepare hook did not fire\")\n\t}\n}\n\n// --- match.check ---\n\nfunc TestMatchCheckHookFires(t *testing.T) {\n\t// Enable match lex so the MatchCheck branch is reached.\n\tyes := true\n\tcalled := 0\n\tj := Make(Options{Match: &MatchOptions{\n\t\tLex:   &yes,\n\t\tCheck: func(lex *Lex) *LexCheckResult { called++; return nil },\n\t}})\n\tif _, err := j.Parse(\"a:1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called == 0 {\n\t\tt.Error(\"MatchCheck was never invoked\")\n\t}\n}\n\n// --- errmsg.suffix field round-trip ---\n\nfunc TestErrMsgSuffixRoundTripString(t *testing.T) {\n\tj, err := Make().SetOptionsText(`errmsg: { name: \"foo\", suffix: \"bar\" }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\topts := j.Options()\n\tif opts.ErrMsg == nil || opts.ErrMsg.Suffix != \"bar\" {\n\t\tt.Errorf(\"expected suffix 'bar', got %#v\", opts.ErrMsg)\n\t}\n}\n\nfunc TestErrMsgSuffixRoundTripBool(t *testing.T) {\n\tj, err := Make().SetOptionsText(`errmsg: { suffix: false }`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\topts := j.Options()\n\tif opts.ErrMsg == nil {\n\t\tt.Fatal(\"errmsg options not populated\")\n\t}\n\tif v, ok := opts.ErrMsg.Suffix.(bool); !ok || v != false {\n\t\tt.Errorf(\"expected suffix=false bool, got %#v\", opts.ErrMsg.Suffix)\n\t}\n}\n"
  },
  {
    "path": "go/parser.go",
    "content": "package jsonic\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Context holds the parse state, matching the TypeScript Context type.\ntype Context struct {\n\tUI int // Unique rule ID counter (TS: uI)\n\n\t// Generalized lookahead buffer. T[i] is the token at position i,\n\t// or NoToken if that slot has not yet been fetched. This supersedes\n\t// the legacy T0 / T1 two-slot fields, which are kept in sync for\n\t// backward compatibility (plugins / grammars / debug.go that read\n\t// ctx.T0 and ctx.T1 directly continue to work unchanged).\n\tT []*Token\n\n\tT0 *Token // Alias of T[0] (legacy). Kept in sync with T[0].\n\tT1 *Token // Alias of T[1] (legacy). Kept in sync with T[1].\n\tV1 *Token // Previous token (TS: v1)\n\tV2 *Token // Previous previous token (TS: v2)\n\tRS       []*Rule           // Rule stack (TS: rs)\n\tRSI      int               // Rule stack index (TS: rsI)\n\tRSM      map[string]*RuleSpec // Rule spec map (TS: rsm)\n\tKI       int               // Iteration counter (TS: kI)\n\tRule     *Rule             // Current parsing rule (TS: rule)\n\tMeta     map[string]any    // Parse metadata (TS: meta)\n\tLexSubs  []LexSub          // Lex event subscribers (TS: sub.lex)\n\tRuleSubs []RuleSub         // Rule event subscribers (TS: sub.rule)\n\tParseErr *Token            // Error token, halts parse\n\n\t// Fields matching TS Context:\n\tOpts     *Options          // Jsonic instance options (TS: opts)\n\tCfg      *LexConfig        // Jsonic instance config (TS: cfg)\n\tSrc      string            // Source text being parsed (TS: src)\n\tInst     *Jsonic           // Current Jsonic instance (TS: inst)\n\tU        map[string]any    // Custom plugin data bag (TS: u)\n\tRoot     *Rule             // Root rule (TS: root)\n\tTC       int               // Token count (TS: tC)\n\tF        func(any) string  // Format value as string (TS: F)\n\tLog      func(...any)      // Debug logger (TS: log)\n\tNOTOKEN  *Token            // Sentinel no-token (TS: NOTOKEN)\n\tNORULE   *Rule             // Sentinel no-rule (TS: NORULE)\n}\n\n// Parser orchestrates the parsing process.\ntype Parser struct {\n\tConfig        *LexConfig\n\tRSM           map[string]*RuleSpec\n\tMaxMul        int               // Max rule occurrence multiplier. Default: 3.\n\tErrorMessages map[string]string  // Custom error message templates.\n\tHints         map[string]string  // Explanatory hints per error code.\n\tErrTag        string             // Custom error tag (TS: errmsg.name). Default: \"jsonic\".\n}\n\n// NewParser creates a parser with default configuration.\nfunc NewParser() *Parser {\n\tcfg := DefaultLexConfig()\n\trsm := make(map[string]*RuleSpec)\n\tbuildGrammar(rsm, cfg)\n\t// Copy global error messages as defaults.\n\tmsgs := make(map[string]string, len(errorMessages))\n\tfor k, v := range errorMessages {\n\t\tmsgs[k] = v\n\t}\n\treturn &Parser{Config: cfg, RSM: rsm, MaxMul: 3, ErrorMessages: msgs}\n}\n\n// Start parses the source string and returns the result.\n// Returns a *JsonicError if parsing fails.\nfunc (p *Parser) Start(src string) (any, error) {\n\treturn p.startParse(src, nil, nil, nil, nil)\n}\n\n// StartMeta parses the source string with metadata, subscriptions, and\n// an optional Jsonic instance reference (for Context.Inst).\nfunc (p *Parser) StartMeta(src string, meta map[string]any, lexSubs []LexSub, ruleSubs []RuleSub) (any, error) {\n\treturn p.startParse(src, meta, lexSubs, ruleSubs, nil)\n}\n\n// startParse is the internal entry point that populates the full Context.\nfunc (p *Parser) startParse(src string, meta map[string]any, lexSubs []LexSub, ruleSubs []RuleSub, inst *Jsonic) (any, error) {\n\tif src == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tlex := NewLex(src, p.Config)\n\n\tvar opts *Options\n\tif inst != nil {\n\t\topts = inst.options\n\t}\n\n\tctx := &Context{\n\t\tUI:       0,\n\t\tT:        []*Token{NoToken, NoToken},\n\t\tT0:       NoToken,\n\t\tT1:       NoToken,\n\t\tV1:       NoToken,\n\t\tV2:       NoToken,\n\t\tRS:       make([]*Rule, len(src)*4+100),\n\t\tRSI:      0,\n\t\tRSM:      p.RSM,\n\t\tMeta:     meta,\n\t\tLexSubs:  lexSubs,\n\t\tRuleSubs: ruleSubs,\n\t\tOpts:     opts,\n\t\tCfg:      p.Config,\n\t\tSrc:      src,\n\t\tInst:     inst,\n\t\tU:        make(map[string]any),\n\t\tTC:       0,\n\t\tNOTOKEN:  NoToken,\n\t\tNORULE:   NoRule,\n\t\tF:        func(v any) string { return Str(v, 44) },\n\t}\n\n\tlex.Ctx = ctx\n\n\tstartName := p.Config.RuleStart\n\tif startName == \"\" {\n\t\tstartName = \"val\"\n\t}\n\tstartSpec := p.RSM[startName]\n\tif startSpec == nil {\n\t\treturn nil, nil\n\t}\n\n\trule := MakeRule(startSpec, ctx, nil)\n\troot := rule\n\tctx.Root = root\n\n\t// Run parse.prepare hooks\n\tif len(p.Config.ParsePrepare) > 0 {\n\t\tfor _, prep := range p.Config.ParsePrepare {\n\t\t\tprep(ctx)\n\t\t}\n\t}\n\n\t// Maximum iterations: 2 * numRules * srcLen * 2 * maxmul\n\tmaxmul := p.MaxMul\n\tif maxmul <= 0 {\n\t\tmaxmul = 3\n\t}\n\tmaxr := 2 * len(p.RSM) * len(src) * 2 * maxmul\n\tif maxr < 100 {\n\t\tmaxr = 100\n\t}\n\n\tkI := 0\n\tfor rule != NoRule && kI < maxr {\n\t\tctx.KI = kI\n\t\tctx.Rule = rule\n\n\t\t// Fire rule subscribers BEFORE process (matching TS).\n\t\tif len(ctx.RuleSubs) > 0 {\n\t\t\tfor _, sub := range ctx.RuleSubs {\n\t\t\t\tsub(rule, ctx)\n\t\t\t}\n\t\t}\n\n\t\trule = rule.Process(ctx, lex)\n\n\t\t// Check for parse error from alt.E or actions.\n\t\tif ctx.ParseErr != nil {\n\t\t\t// Prefer lexer errors (e.g. unterminated_string) over generic\n\t\t\t// \"unexpected\" from alt matching, since the lex error is more\n\t\t\t// specific about what went wrong. Matches TS behavior where\n\t\t\t// lex errors propagate through #ZZ tokens.\n\t\t\tif lex.Err != nil {\n\t\t\t\treturn nil, lex.Err\n\t\t\t}\n\t\t\ttkn := ctx.ParseErr\n\t\t\treturn nil, p.makeError(\"unexpected\", tkn.Src, src, tkn.SI, tkn.RI, tkn.CI)\n\t\t}\n\n\t\tkI++\n\t}\n\n\t// Check for lexer errors (unterminated strings, comments, etc.)\n\tif lex.Err != nil {\n\t\treturn nil, lex.Err\n\t}\n\n\t// Check for unconsumed tokens (syntax error) - explicit trailing content check.\n\t// First check tokens already in the lookahead buffer.\n\tif ctx.T0 != nil && !ctx.T0.IsNoToken() && ctx.T0.Tin != TinZZ {\n\t\t// Prefer lex errors over generic unexpected for unconsumed tokens too.\n\t\tif lex.Err != nil {\n\t\t\treturn nil, lex.Err\n\t\t}\n\t\treturn nil, p.makeError(\"unexpected\", ctx.T0.Src, src, ctx.T0.SI, ctx.T0.RI, ctx.T0.CI)\n\t}\n\t// Also explicitly ask lexer for more (matching TS parser.ts:187-189).\n\tendTkn := lex.Next(rule)\n\tif endTkn.Tin != TinZZ {\n\t\tif lex.Err != nil {\n\t\t\treturn nil, lex.Err\n\t\t}\n\t\treturn nil, p.makeError(\"unexpected\", endTkn.Src, src, endTkn.SI, endTkn.RI, endTkn.CI)\n\t}\n\t// Check lexer errors from that final Next() call.\n\tif lex.Err != nil {\n\t\treturn nil, lex.Err\n\t}\n\n\t// Follow replacement chain: when val is replaced by list (implicit list),\n\t// root.Node is stale. Follow Next/Prev links to find the actual result.\n\tresult := root\n\tfor result.Next != NoRule && result.Next != nil && result.Next.Prev == result {\n\t\tresult = result.Next\n\t}\n\n\tif IsUndefined(result.Node) {\n\t\treturn nil, nil\n\t}\n\n\t// Check result.fail\n\tif len(p.Config.ResultFail) > 0 {\n\t\tfor _, fail := range p.Config.ResultFail {\n\t\t\tif result.Node == fail {\n\t\t\t\treturn nil, p.makeError(\"unexpected\", \"\", src, 0, 1, 1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result.Node, nil\n}\n\n// makeError creates a JsonicError using this parser's error messages.\nfunc (p *Parser) makeError(code, src, fullSource string, pos, row, col int) *JsonicError {\n\tmsgs := p.ErrorMessages\n\tif msgs == nil {\n\t\tmsgs = errorMessages\n\t}\n\ttmpl, ok := msgs[code]\n\tif !ok {\n\t\ttmpl = msgs[\"unknown\"]\n\t\tif tmpl == \"\" {\n\t\t\ttmpl = errorMessages[\"unknown\"]\n\t\t}\n\t}\n\tdetail := tmpl + src\n\n\thint := \"\"\n\tif p.Hints != nil {\n\t\thint = p.Hints[code]\n\t}\n\n\tje := &JsonicError{\n\t\tCode:       code,\n\t\tDetail:     detail,\n\t\tPos:        pos,\n\t\tRow:        row,\n\t\tCol:        col,\n\t\tSrc:        src,\n\t\tHint:       hint,\n\t\tfullSource: fullSource,\n\t\ttag:        p.ErrTag,\n\t}\n\tif p.Config != nil {\n\t\tje.color = p.Config.Color\n\t}\n\treturn je\n}\n\n// parseNumericString converts a numeric string to float64.\n// Handles standard decimals, hex (0x), octal (0o), binary (0b), and signs.\nfunc parseNumericString(s string) float64 {\n\tif len(s) == 0 {\n\t\treturn math.NaN()\n\t}\n\n\t// Handle sign prefix for special formats\n\tsign := 1.0\n\tns := s\n\tif ns[0] == '-' {\n\t\tsign = -1.0\n\t\tns = ns[1:]\n\t} else if ns[0] == '+' {\n\t\tsign = 1.0\n\t\tns = ns[1:]\n\t}\n\n\tif len(ns) >= 2 {\n\t\tswitch {\n\t\tcase ns[0] == '0' && (ns[1] == 'x' || ns[1] == 'X'):\n\t\t\tval, err := strconv.ParseInt(ns[2:], 16, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn math.NaN()\n\t\t\t}\n\t\t\treturn sign * float64(val)\n\t\tcase ns[0] == '0' && (ns[1] == 'o' || ns[1] == 'O'):\n\t\t\tval, err := strconv.ParseInt(ns[2:], 8, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn math.NaN()\n\t\t\t}\n\t\t\treturn sign * float64(val)\n\t\tcase ns[0] == '0' && (ns[1] == 'b' || ns[1] == 'B'):\n\t\t\tval, err := strconv.ParseInt(ns[2:], 2, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn math.NaN()\n\t\t\t}\n\t\t\treturn sign * float64(val)\n\t\t}\n\t}\n\n\t// Remove underscores if present\n\tns = strings.ReplaceAll(s, \"_\", \"\")\n\n\tval, err := strconv.ParseFloat(ns, 64)\n\tif err != nil {\n\t\treturn math.NaN()\n\t}\n\n\t// Normalize -0 to 0\n\tif val == 0 {\n\t\treturn 0\n\t}\n\n\treturn val\n}\n"
  },
  {
    "path": "go/plugin.go",
    "content": "package jsonic\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// Plugin is a function that modifies a Jsonic instance.\n// Plugins can add custom tokens, matchers, and rule modifications.\n// Returns an error if the plugin fails to initialize.\ntype Plugin func(j *Jsonic, opts map[string]any) error\n\n// LexMatcher is a custom lexer matcher function.\n// It receives the lexer and the current parsing rule, and returns a Token\n// if matched, or nil to pass. The rule parameter allows context-sensitive\n// lexing (e.g. checking lex.Ctx.Rule, rule.K, rule.U, rule.N, or rule.State).\n// The matcher can read the current position via lex.Cursor() and must\n// advance the cursor if it produces a token.\ntype LexMatcher func(lex *Lex, rule *Rule) *Token\n\n// MakeLexMatcher is a factory function that creates a LexMatcher.\n// It receives the lexer config and parser options, matching the TypeScript\n// pattern: (cfg: Config, opts: Options) => (lex: Lex) => Token | undefined.\ntype MakeLexMatcher func(cfg *LexConfig, opts *Options) LexMatcher\n\n// MatchSpec defines a custom matcher to be registered via options.\n// This matches the TypeScript pattern: { order: number, make: MakeLexMatcher }.\ntype MatchSpec struct {\n\tOrder int            // Priority order (lower runs first)\n\tMake  MakeLexMatcher // Factory function that creates the matcher\n}\n\n// MatcherEntry holds a named custom matcher with a priority for ordering.\n// Lower priority numbers run first. Built-in matchers use:\n// fixed=2e6, space=3e6, line=4e6, string=5e6, comment=6e6, number=7e6, text=8e6.\n// Custom matchers at priority < 2e6 run before all built-ins (matching TS behavior).\ntype MatcherEntry struct {\n\tName     string\n\tPriority int\n\tMatch    LexMatcher\n}\n\n// RuleDefiner is a callback that modifies a RuleSpec.\n// Plugins use this to add alternates, actions, or conditions to grammar rules.\n// The parser is passed so definers can inspect or reference other rules.\n// Mirrors TS src/types.ts:841 `(rs: RuleSpec, p: Parser) => void`.\ntype RuleDefiner func(rs *RuleSpec, p *Parser)\n\n// LexSub is a subscriber callback invoked after each token is lexed.\ntype LexSub func(tkn *Token, rule *Rule, ctx *Context)\n\n// RuleSub is a subscriber callback invoked after each rule step.\ntype RuleSub func(rule *Rule, ctx *Context)\n\n// pluginEntry stores a registered plugin and its options.\ntype pluginEntry struct {\n\tplugin Plugin\n\topts   map[string]any\n}\n\n// Use registers and invokes a plugin on this Jsonic instance.\n// The plugin function is called with the Jsonic instance and optional options.\n// Returns an error if the plugin fails to initialize.\n//\n// Example:\n//\n//\tj := jsonic.Make()\n//\terr := j.Use(myPlugin, map[string]any{\"key\": \"value\"})\nfunc (j *Jsonic) Use(plugin Plugin, opts ...map[string]any) error {\n\tvar pluginOpts map[string]any\n\tif len(opts) > 0 && opts[0] != nil {\n\t\tpluginOpts = opts[0]\n\t}\n\n\tj.plugins = append(j.plugins, pluginEntry{plugin: plugin, opts: pluginOpts})\n\treturn plugin(j, pluginOpts)\n}\n\n// UseDefaults registers and invokes a plugin, merging default options with\n// user-provided options before calling the plugin. This matches the TS pattern\n// where plugins have a .defaults property:\n//\n//\tdeep({}, plugin.defaults || {}, plugin_options || {})\n//\n// Example:\n//\n//\tj := jsonic.Make()\n//\terr := j.UseDefaults(hoover.Hoover, hoover.Defaults, map[string]any{...})\nfunc (j *Jsonic) UseDefaults(plugin Plugin, defaults map[string]any, opts ...map[string]any) error {\n\tpluginOpts := Deep(map[string]any{}, defaults).(map[string]any)\n\tif len(opts) > 0 && opts[0] != nil {\n\t\tpluginOpts = Deep(pluginOpts, opts[0]).(map[string]any)\n\t}\n\n\tj.plugins = append(j.plugins, pluginEntry{plugin: plugin, opts: pluginOpts})\n\treturn plugin(j, pluginOpts)\n}\n\n// Rule modifies or creates a grammar rule by name.\n// The definer callback receives the RuleSpec and can modify its Open/Close\n// alternates, and BO/BC/AO/AC state actions.\n//\n// If the rule does not exist, a new empty RuleSpec is created.\n// Returns the Jsonic instance for chaining.\n//\n// Example:\n//\n//\tj.Rule(\"val\", func(rs *RuleSpec, p *Parser) {\n//\t    rs.Open = append([]*AltSpec{{\n//\t        S: [][]Tin{{myToken}},\n//\t        A: func(r *Rule, ctx *Context) { r.Node = \"custom\" },\n//\t    }}, rs.Open...)\n//\t})\nfunc (j *Jsonic) Rule(name string, definer RuleDefiner) *Jsonic {\n\trs := j.parser.RSM[name]\n\tif rs == nil {\n\t\trs = &RuleSpec{Name: name}\n\t\tj.parser.RSM[name] = rs\n\t}\n\tdefiner(rs, j.parser)\n\treturn j\n}\n\n// Token registers a new token type or looks up an existing one.\n// With just a name, it returns the Tin for an existing token.\n// With a name and source character(s), it registers a new fixed token.\n//\n// Returns the Tin (token identification number) for the token.\n//\n// Example:\n//\n//\t// Register a new fixed token\n//\tTT := j.Token(\"#TL\", \"~\")\n//\n//\t// Look up existing token\n//\tOB := j.Token(\"#OB\", \"\")\nfunc (j *Jsonic) Token(name string, src ...string) Tin {\n\t// Look up existing token by name.\n\tif tin, ok := j.tinByName[name]; ok {\n\t\t// If src provided, update the fixed token mapping.\n\t\tif len(src) > 0 && src[0] != \"\" {\n\t\t\tif j.parser.Config.FixedTokens == nil {\n\t\t\t\tj.parser.Config.FixedTokens = make(map[string]Tin)\n\t\t\t}\n\t\t\tj.parser.Config.FixedTokens[src[0]] = tin\n\t\t\tj.parser.Config.SortFixedTokens()\n\t\t}\n\t\treturn tin\n\t}\n\n\t// Allocate a new Tin.\n\ttin := j.nextTin\n\tj.nextTin++\n\n\tj.tinByName[name] = tin\n\tj.nameByTin[tin] = name\n\n\t// Also store in the config's TinNames for lexer access.\n\tif j.parser.Config.TinNames == nil {\n\t\tj.parser.Config.TinNames = make(map[Tin]string)\n\t}\n\tj.parser.Config.TinNames[tin] = name\n\n\t// Register as fixed token if src provided.\n\tif len(src) > 0 && src[0] != \"\" {\n\t\tif j.parser.Config.FixedTokens == nil {\n\t\t\tj.parser.Config.FixedTokens = make(map[string]Tin)\n\t\t}\n\t\tj.parser.Config.FixedTokens[src[0]] = tin\n\t\tj.parser.Config.SortFixedTokens()\n\t}\n\n\treturn tin\n}\n\n// FixedSrc returns the Tin for a fixed token source string (e.g. \"{\" → TinOB).\n// Returns 0 if the source string is not a fixed token.\n// Matches TS `jsonic.fixed('b')`.\nfunc (j *Jsonic) FixedSrc(src string) Tin {\n\tif tin, ok := j.parser.Config.FixedTokens[src]; ok {\n\t\treturn tin\n\t}\n\treturn 0\n}\n\n// FixedTin returns the source string for a fixed token Tin (e.g. TinOB → \"{\").\n// Returns \"\" if the Tin is not a fixed token.\n// Matches TS `jsonic.fixed(18)`.\nfunc (j *Jsonic) FixedTin(tin Tin) string {\n\tfor src, t := range j.parser.Config.FixedTokens {\n\t\tif t == tin {\n\t\t\treturn src\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// registerMatchSpecs materializes the factories in opts.Lex.Match into\n// LexMatchers and appends them to Config.CustomMatchers, keeping the slice\n// sorted by priority (lower first). Built-in matcher priorities:\n//\n//\tfixed=2000000, space=3000000, line=4000000, string=5000000,\n//\tcomment=6000000, number=7000000, text=8000000\n//\n// Use Order < 2000000 to run before all built-ins (matching TS match behavior).\nfunc (j *Jsonic) registerMatchSpecs(opts *Options) {\n\tif opts.Lex == nil || opts.Lex.Match == nil {\n\t\treturn\n\t}\n\tbyName := make(map[string]int, len(j.parser.Config.CustomMatchers))\n\tfor i, m := range j.parser.Config.CustomMatchers {\n\t\tbyName[m.Name] = i\n\t}\n\tfor name, spec := range opts.Lex.Match {\n\t\tif spec == nil || spec.Make == nil {\n\t\t\tcontinue\n\t\t}\n\t\tmatcher := spec.Make(j.parser.Config, j.options)\n\t\tif matcher == nil {\n\t\t\tcontinue\n\t\t}\n\t\tentry := &MatcherEntry{Name: name, Priority: spec.Order, Match: matcher}\n\t\tif idx, ok := byName[name]; ok {\n\t\t\tj.parser.Config.CustomMatchers[idx] = entry\n\t\t} else {\n\t\t\tj.parser.Config.CustomMatchers = append(j.parser.Config.CustomMatchers, entry)\n\t\t\tbyName[name] = len(j.parser.Config.CustomMatchers) - 1\n\t\t}\n\t}\n\tsort.Slice(j.parser.Config.CustomMatchers, func(i, k int) bool {\n\t\treturn j.parser.Config.CustomMatchers[i].Priority < j.parser.Config.CustomMatchers[k].Priority\n\t})\n}\n\n// applyFixedTokens updates the lexer's fixed-token table from opts.Fixed.Token.\n// Keys are token names, values are pointers to the intended source string:\n//   - non-nil: remove any existing src→tin mapping for that name, then set\n//     *srcPtr → tin. Unknown names are registered as new tokens.\n//   - nil: remove any existing src→tin mapping(s) for that name.\n//\n// Mirrors the TS `options.fixed.token` semantics (StrMap with null = delete).\nfunc (j *Jsonic) applyFixedTokens(opts *Options) {\n\tif opts.Fixed == nil || opts.Fixed.Token == nil {\n\t\treturn\n\t}\n\tif j.parser.Config.FixedTokens == nil {\n\t\tj.parser.Config.FixedTokens = make(map[string]Tin)\n\t}\n\tchanged := false\n\tfor name, srcPtr := range opts.Fixed.Token {\n\t\ttin, ok := j.tinByName[name]\n\t\tif !ok {\n\t\t\tif srcPtr == nil {\n\t\t\t\tcontinue // nothing to delete for an unknown name\n\t\t\t}\n\t\t\ttin = j.Token(name) // allocate\n\t\t}\n\t\tfor src, t := range j.parser.Config.FixedTokens {\n\t\t\tif t == tin {\n\t\t\t\tdelete(j.parser.Config.FixedTokens, src)\n\t\t\t\tchanged = true\n\t\t\t}\n\t\t}\n\t\tif srcPtr != nil && *srcPtr != \"\" {\n\t\t\tj.parser.Config.FixedTokens[*srcPtr] = tin\n\t\t\tchanged = true\n\t\t}\n\t}\n\tif changed {\n\t\tj.parser.Config.SortFixedTokens()\n\t}\n}\n\n\n// Plugins returns the list of installed plugins (for introspection).\nfunc (j *Jsonic) Plugins() []Plugin {\n\tout := make([]Plugin, len(j.plugins))\n\tfor i, pe := range j.plugins {\n\t\tout[i] = pe.plugin\n\t}\n\treturn out\n}\n\n// Config returns the parser's LexConfig for direct inspection or modification.\n// Use with care — prefer Token(), Rule(), and options.lex.match for most work.\nfunc (j *Jsonic) Config() *LexConfig {\n\treturn j.parser.Config\n}\n\n// RSM returns the rule spec map for direct inspection or modification.\nfunc (j *Jsonic) RSM() map[string]*RuleSpec {\n\treturn j.parser.RSM\n}\n\n// TinName returns the name for a Tin value, checking both built-in and custom tokens.\nfunc (j *Jsonic) TinName(tin Tin) string {\n\tif name, ok := j.nameByTin[tin]; ok {\n\t\treturn name\n\t}\n\treturn tinName(tin)\n}\n\n// TokenSet returns a named set of Tin values.\n// Built-in sets: \"IGNORE\" (space, line, comment), \"VAL\" (text, number, string, value),\n// \"KEY\" (text, number, string, value).\n// Custom sets can be added via SetTokenSet.\n// Returns nil if the set name is not recognized.\nfunc (j *Jsonic) TokenSet(name string) []Tin {\n\t// Check custom sets first.\n\tif j.customTokenSets != nil {\n\t\tif tins, ok := j.customTokenSets[name]; ok {\n\t\t\tresult := make([]Tin, len(tins))\n\t\t\tcopy(result, tins)\n\t\t\treturn result\n\t\t}\n\t}\n\tswitch name {\n\tcase \"IGNORE\":\n\t\tignoreSet := j.parser.Config.IgnoreSet\n\t\ttins := make([]Tin, 0, len(ignoreSet))\n\t\tfor tin := range ignoreSet {\n\t\t\ttins = append(tins, tin)\n\t\t}\n\t\treturn tins\n\tcase \"VAL\":\n\t\tvalSet := j.parser.Config.ValSet\n\t\tresult := make([]Tin, len(valSet))\n\t\tcopy(result, valSet)\n\t\treturn result\n\tcase \"KEY\":\n\t\tkeySet := j.parser.Config.KeySet\n\t\tresult := make([]Tin, len(keySet))\n\t\tcopy(result, keySet)\n\t\treturn result\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// SetTokenSet registers a custom named token set.\n// Matches TS options.tokenSet.\n// Also updates the per-instance config sets (IgnoreSet, ValSet, KeySet)\n// to keep them in sync, matching TS cfg.tokenSetTins behavior.\nfunc (j *Jsonic) SetTokenSet(name string, tins []Tin) {\n\tif j.customTokenSets == nil {\n\t\tj.customTokenSets = make(map[string][]Tin)\n\t}\n\tj.customTokenSets[name] = tins\n\n\t// Keep per-instance config sets in sync (matching TS cfg.tokenSetTins).\n\tswitch name {\n\tcase \"IGNORE\":\n\t\tignoreSet := make(map[Tin]bool, len(tins))\n\t\tfor _, tin := range tins {\n\t\t\tignoreSet[tin] = true\n\t\t}\n\t\tj.parser.Config.IgnoreSet = ignoreSet\n\tcase \"VAL\":\n\t\tcopied := make([]Tin, len(tins))\n\t\tcopy(copied, tins)\n\t\tj.parser.Config.ValSet = copied\n\tcase \"KEY\":\n\t\tcopied := make([]Tin, len(tins))\n\t\tcopy(copied, tins)\n\t\tj.parser.Config.KeySet = copied\n\t}\n}\n\n// Sub subscribes to lex and/or rule events.\n// LexSub fires after each non-ignored token is lexed.\n// RuleSub fires after each rule processing step.\n// Returns the Jsonic instance for chaining.\nfunc (j *Jsonic) Sub(lexSub LexSub, ruleSub RuleSub) *Jsonic {\n\tif lexSub != nil {\n\t\tj.lexSubs = append(j.lexSubs, lexSub)\n\t}\n\tif ruleSub != nil {\n\t\tj.ruleSubs = append(j.ruleSubs, ruleSub)\n\t}\n\treturn j\n}\n\n// Derive creates a new Jsonic instance inheriting this instance's config,\n// rules, plugins, and custom tokens. Changes to the child do not affect the parent.\n// This matches TypeScript's jsonic.make(options, parent).\nfunc (j *Jsonic) Derive(opts ...Options) *Jsonic {\n\t// Start with parent's options, merge with new ones.\n\tchild := Make(opts...)\n\n\t// Copy parent's custom fixed tokens.\n\tfor k, v := range j.parser.Config.FixedTokens {\n\t\tchild.parser.Config.FixedTokens[k] = v\n\t}\n\tchild.parser.Config.SortFixedTokens()\n\n\t// Copy parent's custom token names.\n\tfor k, v := range j.tinByName {\n\t\tchild.tinByName[k] = v\n\t}\n\tfor k, v := range j.nameByTin {\n\t\tchild.nameByTin[k] = v\n\t}\n\tif child.nextTin < j.nextTin {\n\t\tchild.nextTin = j.nextTin\n\t}\n\n\t// Copy TinNames into child config.\n\tif j.parser.Config.TinNames != nil {\n\t\tif child.parser.Config.TinNames == nil {\n\t\t\tchild.parser.Config.TinNames = make(map[Tin]string)\n\t\t}\n\t\tfor k, v := range j.parser.Config.TinNames {\n\t\t\tchild.parser.Config.TinNames[k] = v\n\t\t}\n\t}\n\n\t// Copy parent's custom matchers.\n\tfor _, m := range j.parser.Config.CustomMatchers {\n\t\tchild.parser.Config.CustomMatchers = append(child.parser.Config.CustomMatchers, m)\n\t}\n\n\t// Copy parent's ender chars.\n\tif j.parser.Config.EnderChars != nil {\n\t\tif child.parser.Config.EnderChars == nil {\n\t\t\tchild.parser.Config.EnderChars = make(map[rune]bool)\n\t\t}\n\t\tfor k, v := range j.parser.Config.EnderChars {\n\t\t\tchild.parser.Config.EnderChars[k] = v\n\t\t}\n\t}\n\n\t// Copy parent's escape map.\n\tif j.parser.Config.EscapeMap != nil {\n\t\tif child.parser.Config.EscapeMap == nil {\n\t\t\tchild.parser.Config.EscapeMap = make(map[string]string)\n\t\t}\n\t\tfor k, v := range j.parser.Config.EscapeMap {\n\t\t\tchild.parser.Config.EscapeMap[k] = v\n\t\t}\n\t}\n\n\t// Copy custom token sets.\n\tif j.customTokenSets != nil {\n\t\tif child.customTokenSets == nil {\n\t\t\tchild.customTokenSets = make(map[string][]Tin)\n\t\t}\n\t\tfor name, tins := range j.customTokenSets {\n\t\t\tcopied := make([]Tin, len(tins))\n\t\t\tcopy(copied, tins)\n\t\t\tchild.customTokenSets[name] = copied\n\t\t}\n\t}\n\n\t// Copy per-instance token sets (matching TS cfg.tokenSetTins inheritance).\n\tif j.parser.Config.IgnoreSet != nil {\n\t\tchild.parser.Config.IgnoreSet = make(map[Tin]bool, len(j.parser.Config.IgnoreSet))\n\t\tfor k, v := range j.parser.Config.IgnoreSet {\n\t\t\tchild.parser.Config.IgnoreSet[k] = v\n\t\t}\n\t}\n\tif j.parser.Config.ValSet != nil {\n\t\tchild.parser.Config.ValSet = make([]Tin, len(j.parser.Config.ValSet))\n\t\tcopy(child.parser.Config.ValSet, j.parser.Config.ValSet)\n\t}\n\tif j.parser.Config.KeySet != nil {\n\t\tchild.parser.Config.KeySet = make([]Tin, len(j.parser.Config.KeySet))\n\t\tcopy(child.parser.Config.KeySet, j.parser.Config.KeySet)\n\t}\n\n\t// Re-apply parent's plugins on the child.\n\tfor _, pe := range j.plugins {\n\t\tchild.plugins = append(child.plugins, pe)\n\t\tif err := pe.plugin(child, pe.opts); err != nil {\n\t\t\t// Plugin errors during Derive are programming errors; panic\n\t\t\t// since Derive doesn't return an error.\n\t\t\tpanic(\"jsonic: plugin error during Derive: \" + err.Error())\n\t\t}\n\t}\n\n\t// Copy subscriptions.\n\tchild.lexSubs = append(child.lexSubs, j.lexSubs...)\n\tchild.ruleSubs = append(child.ruleSubs, j.ruleSubs...)\n\n\t// Copy decorations (TS: parent properties inherited by child).\n\tif j.decorations != nil {\n\t\tif child.decorations == nil {\n\t\t\tchild.decorations = make(map[string]any)\n\t\t}\n\t\tfor k, v := range j.decorations {\n\t\t\tchild.decorations[k] = v\n\t\t}\n\t}\n\n\t// Copy plugin options namespace.\n\tif j.pluginOpts != nil {\n\t\tif child.pluginOpts == nil {\n\t\t\tchild.pluginOpts = make(map[string]map[string]any)\n\t\t}\n\t\tfor name, opts := range j.pluginOpts {\n\t\t\tcopied := make(map[string]any, len(opts))\n\t\t\tfor k, v := range opts {\n\t\t\t\tcopied[k] = v\n\t\t\t}\n\t\t\tchild.pluginOpts[name] = copied\n\t\t}\n\t}\n\n\treturn child\n}\n\n// SetOptions deep-merges new options into this instance and rebuilds the\n// config. Existing grammar rules (including plugin modifications) are\n// preserved — matching the TypeScript clone/inherit pattern where\n// options() does not rebuild the grammar.\n// When called from within a plugin (during re-apply), skips plugin\n// re-application to avoid infinite recursion.\n// Returns the instance for chaining.\nfunc (j *Jsonic) SetOptions(opts Options) *Jsonic {\n\tmerged := Deep(*j.options, opts).(Options)\n\tj.options = &merged\n\n\t// Rebuild config from merged options.\n\tcfg := buildConfig(j.options)\n\n\t// Preserve per-instance state.\n\tcfg.FixedTokens = j.parser.Config.FixedTokens\n\tcfg.FixedSorted = j.parser.Config.FixedSorted\n\tcfg.TinNames = j.parser.Config.TinNames\n\tcfg.CustomMatchers = j.parser.Config.CustomMatchers\n\t// Preserve token sets. buildConfig unconditionally resets IgnoreSet/\n\t// ValSet/KeySet to defaults, which would clobber SetTokenSet mutations\n\t// made by plugins or prior calls. opts.TokenSet below reapplies any\n\t// explicit override from the incoming options.\n\tif j.parser.Config.IgnoreSet != nil {\n\t\tcfg.IgnoreSet = j.parser.Config.IgnoreSet\n\t}\n\tif j.parser.Config.ValSet != nil {\n\t\tcfg.ValSet = j.parser.Config.ValSet\n\t}\n\tif j.parser.Config.KeySet != nil {\n\t\tcfg.KeySet = j.parser.Config.KeySet\n\t}\n\t// Preserve match token/value entries added by prior SetOptions/Grammar calls.\n\tif len(j.parser.Config.MatchTokens) > 0 {\n\t\tfor k, v := range j.parser.Config.MatchTokens {\n\t\t\tcfg.MatchTokens[k] = v\n\t\t}\n\t}\n\tif len(j.parser.Config.MatchValues) > 0 {\n\t\tcfg.MatchValues = append(cfg.MatchValues, j.parser.Config.MatchValues...)\n\t\t// Re-sort: the preserved entries may break the name-ascending order\n\t\t// built by buildConfig. Keep lex-time iteration deterministic.\n\t\tsort.Slice(cfg.MatchValues, func(i, k int) bool {\n\t\t\treturn cfg.MatchValues[i].Name < cfg.MatchValues[k].Name\n\t\t})\n\t}\n\t// Re-project the merged MatchTokens map to its sorted view.\n\tcfg.RebuildMatchTokensSorted()\n\n\t// Update config in-place to preserve pointer identity.\n\t// Grammar closures capture the original *LexConfig pointer, so updating\n\t// the object they point to (rather than replacing it) ensures they see\n\t// the new config values. This matches TS behavior where configure()\n\t// mutates the existing config and parser.clone() inherits the rules.\n\t*j.parser.Config = *cfg\n\n\t// Do NOT rebuild grammar — preserve existing RSM with user rule\n\t// modifications. This matches TS where options() calls parser.clone()\n\t// which inherits existing rules rather than rebuilding from scratch.\n\n\t// Re-apply plugins (with re-entrancy guard to match TS behavior where\n\t// options() setter does not re-trigger plugin application).\n\tif !j.inSetOptions {\n\t\tj.inSetOptions = true\n\t\tfor _, pe := range j.plugins {\n\t\t\t// Ignore errors during re-application: the plugin already\n\t\t\t// succeeded on initial Use(); re-invocation is for config sync.\n\t\t\t_ = pe.plugin(j, pe.opts)\n\t\t}\n\t\tj.inSetOptions = false\n\t}\n\n\t// Apply lex.match specs: create matchers from MakeLexMatcher factories.\n\tj.registerMatchSpecs(&opts)\n\n\t// Apply fixed.token overrides (add, swap, or delete fixed-token mappings).\n\tj.applyFixedTokens(&opts)\n\n\t// Apply match.token: resolve token names to Tins and register regexp matchers.\n\tif opts.Match != nil && opts.Match.Token != nil {\n\t\tfor name, re := range opts.Match.Token {\n\t\t\ttin := j.Token(name)\n\t\t\tj.parser.Config.MatchTokens[tin] = re\n\t\t}\n\t\t// Project map → sorted slice so lex-time iteration is deterministic.\n\t\tj.parser.Config.RebuildMatchTokensSorted()\n\t}\n\n\t// Apply tokenSet: resolve token names and update per-instance sets.\n\tif opts.TokenSet != nil {\n\t\tfor setName, names := range opts.TokenSet {\n\t\t\tvar tins []Tin\n\t\t\tfor _, name := range names {\n\t\t\t\tif name == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tresolved := j.resolveTokenName(name)\n\t\t\t\ttins = append(tins, resolved...)\n\t\t\t}\n\t\t\tj.SetTokenSet(setName, tins)\n\t\t}\n\t}\n\n\t// Apply error messages.\n\tif j.options.Error != nil {\n\t\tfor k, v := range j.options.Error {\n\t\t\tj.parser.ErrorMessages[k] = v\n\t\t}\n\t}\n\n\t// Apply hints.\n\tif j.options.Hint != nil {\n\t\tif j.hints == nil {\n\t\t\tj.hints = make(map[string]string)\n\t\t}\n\t\tif j.parser.Hints == nil {\n\t\t\tj.parser.Hints = make(map[string]string)\n\t\t}\n\t\tfor k, v := range j.options.Hint {\n\t\t\tj.hints[k] = v\n\t\t\tj.parser.Hints[k] = v\n\t\t}\n\t}\n\n\t// Apply errmsg options.\n\tif j.options.ErrMsg != nil && j.options.ErrMsg.Name != \"\" {\n\t\tj.parser.ErrTag = j.options.ErrMsg.Name\n\t}\n\n\t// Apply lex options (empty source handling).\n\t// Uses merged options so that values set at Make() or via prior\n\t// SetOptions/Grammar calls are preserved.\n\tif j.options.Lex != nil {\n\t\tif j.options.Lex.Empty != nil {\n\t\t\tj.emptyAllow = *j.options.Lex.Empty\n\t\t}\n\t\tj.emptyResult = j.options.Lex.EmptyResult\n\t}\n\n\t// Apply custom parser start.\n\tif j.options.Parser != nil && j.options.Parser.Start != nil {\n\t\tj.parserStart = j.options.Parser.Start\n\t}\n\n\t// Apply rule include first, then exclude. Only apply from the\n\t// incoming opts (not merged) to avoid re-filtering groups that\n\t// earlier SetOptions/Grammar calls already handled.\n\tif opts.Rule != nil && opts.Rule.Include != \"\" {\n\t\tj.include(opts.Rule.Include)\n\t}\n\tif opts.Rule != nil && opts.Rule.Exclude != \"\" {\n\t\tj.exclude(opts.Rule.Exclude)\n\t}\n\n\treturn j\n}\n\n// SetOptionsText parses a jsonic-format options string, converts it to an\n// Options struct via MapToOptions, and applies it via SetOptions.\n// Returns the instance for chaining and any parse error encountered.\n// Complement to SetOptions that accepts a textual specification of the\n// desired options tree.\nfunc (j *Jsonic) SetOptionsText(text string) (*Jsonic, error) {\n\tif text == \"\" {\n\t\treturn j, nil\n\t}\n\tparsed, err := Make().Parse(text)\n\tif err != nil {\n\t\treturn j, err\n\t}\n\tif parsed == nil {\n\t\treturn j, nil\n\t}\n\tm, ok := parsed.(map[string]any)\n\tif !ok {\n\t\treturn j, fmt.Errorf(\"SetOptionsText: expected map, got %T\", parsed)\n\t}\n\tj.SetOptions(MapToOptions(m))\n\treturn j, nil\n}\n\n// include keeps only grammar alternates whose G tags intersect the\n// given group names. Group names are comma-separated in AltSpec.G\n// fields and in the supplied arguments. Alts with no G tags are\n// dropped whenever the include set is non-empty.\n// Use rule.include option to opt alts into the grammar (e.g. \"json\"\n// for strict-JSON mode where plugins pre-tag their alts with \"json\").\nfunc (j *Jsonic) include(groups ...string) *Jsonic {\n\tincludeSet := buildTagSet(groups)\n\tif len(includeSet) == 0 {\n\t\treturn j\n\t}\n\tfor _, rs := range j.parser.RSM {\n\t\trs.Open = filterAltsInclude(rs.Open, includeSet)\n\t\trs.Close = filterAltsInclude(rs.Close, includeSet)\n\t}\n\treturn j\n}\n\n// exclude removes grammar alternates tagged with any of the given group names.\n// Group names are comma-separated in AltSpec.G fields.\n// Use rule.exclude option to strip groups (e.g. \"jsonic\" for strict JSON).\nfunc (j *Jsonic) exclude(groups ...string) *Jsonic {\n\texcludeSet := buildTagSet(groups)\n\n\tfor _, rs := range j.parser.RSM {\n\t\trs.Open = filterAlts(rs.Open, excludeSet)\n\t\trs.Close = filterAlts(rs.Close, excludeSet)\n\t}\n\treturn j\n}\n\n// buildTagSet parses one or more comma-separated tag strings into a set.\nfunc buildTagSet(groups []string) map[string]bool {\n\tout := make(map[string]bool)\n\tfor _, g := range groups {\n\t\tfor _, part := range strings.Split(g, \",\") {\n\t\t\tpart = strings.TrimSpace(part)\n\t\t\tif part != \"\" {\n\t\t\t\tout[part] = true\n\t\t\t}\n\t\t}\n\t}\n\treturn out\n}\n\n// filterAlts removes alternates whose G tags overlap with the exclude set.\nfunc filterAlts(alts []*AltSpec, excludeSet map[string]bool) []*AltSpec {\n\tresult := make([]*AltSpec, 0, len(alts))\n\tfor _, alt := range alts {\n\t\tif alt.G == \"\" {\n\t\t\tresult = append(result, alt)\n\t\t\tcontinue\n\t\t}\n\t\texcluded := false\n\t\tfor _, tag := range strings.Split(alt.G, \",\") {\n\t\t\ttag = strings.TrimSpace(tag)\n\t\t\tif excludeSet[tag] {\n\t\t\t\texcluded = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !excluded {\n\t\t\tresult = append(result, alt)\n\t\t}\n\t}\n\treturn result\n}\n\n// filterAltsInclude keeps only alternates whose G tags intersect the\n// include set. Alts with no G tags are dropped when includeSet is\n// non-empty; callers should short-circuit for the empty-set case.\nfunc filterAltsInclude(alts []*AltSpec, includeSet map[string]bool) []*AltSpec {\n\tresult := make([]*AltSpec, 0, len(alts))\n\tfor _, alt := range alts {\n\t\tif alt.G == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkept := false\n\t\tfor _, tag := range strings.Split(alt.G, \",\") {\n\t\t\ttag = strings.TrimSpace(tag)\n\t\t\tif includeSet[tag] {\n\t\t\t\tkept = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif kept {\n\t\t\tresult = append(result, alt)\n\t\t}\n\t}\n\treturn result\n}\n\n// ParseMeta parses a jsonic string with metadata passed through to the parse context.\n// The meta map is accessible in rule actions/conditions via ctx.Meta.\nfunc (j *Jsonic) ParseMeta(src string, meta map[string]any) (any, error) {\n\treturn j.parseInternal(src, meta)\n}\n"
  },
  {
    "path": "go/plugin_test.go",
    "content": "package jsonic\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\n// hasExactTag checks if tagStr (comma-separated) contains the exact tag.\nfunc hasExactTag(tagStr, tag string) bool {\n\tfor _, t := range strings.Split(tagStr, \",\") {\n\t\tif strings.TrimSpace(t) == tag {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// --- Plugin: Use and basic invocation ---\n\nfunc TestUseInvokesPlugin(t *testing.T) {\n\tinvoked := false\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tinvoked = true\n\t\treturn nil\n\t})\n\tif !invoked {\n\t\tt.Error(\"plugin was not invoked\")\n\t}\n}\n\nfunc TestUsePassesOptions(t *testing.T) {\n\tvar got map[string]any\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tgot = opts\n\t\treturn nil\n\t}, map[string]any{\"key\": \"value\"})\n\tif got == nil || got[\"key\"] != \"value\" {\n\t\tt.Errorf(\"plugin options not passed correctly: %v\", got)\n\t}\n}\n\nfunc TestUseChaining(t *testing.T) {\n\torder := []string{}\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\torder = append(order, \"first\")\n\t\treturn nil\n\t})\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\torder = append(order, \"second\")\n\t\treturn nil\n\t})\n\tif len(order) != 2 || order[0] != \"first\" || order[1] != \"second\" {\n\t\tt.Errorf(\"expected [first second], got %v\", order)\n\t}\n}\n\nfunc TestPlugins(t *testing.T) {\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error { return nil })\n\tj.Use(func(j *Jsonic, opts map[string]any) error { return nil })\n\tif len(j.Plugins()) != 2 {\n\t\tt.Errorf(\"expected 2 plugins, got %d\", len(j.Plugins()))\n\t}\n}\n\n// --- Plugin: Token registration ---\n\nfunc TestTokenRegisterNew(t *testing.T) {\n\tj := Make()\n\ttin := j.Token(\"#TL\", \"~\")\n\tif tin < TinMAX {\n\t\tt.Errorf(\"new token should have Tin >= TinMAX(%d), got %d\", TinMAX, tin)\n\t}\n\t// Look up by name returns same Tin.\n\ttin2 := j.Token(\"#TL\")\n\tif tin2 != tin {\n\t\tt.Errorf(\"lookup returned different Tin: %d vs %d\", tin2, tin)\n\t}\n}\n\nfunc TestTokenLookupBuiltin(t *testing.T) {\n\tj := Make()\n\ttin := j.Token(\"#OB\")\n\tif tin != TinOB {\n\t\tt.Errorf(\"expected TinOB=%d, got %d\", TinOB, tin)\n\t}\n}\n\nfunc TestTokenFixedRegistration(t *testing.T) {\n\tj := Make()\n\ttin := j.Token(\"#TL\", \"~\")\n\t// The fixed token map should now contain '~'.\n\tif j.Config().FixedTokens[\"~\"] != tin {\n\t\tt.Errorf(\"fixed token '~' not registered in config\")\n\t}\n}\n\nfunc TestTokenMultipleRegistrations(t *testing.T) {\n\tj := Make()\n\tt1 := j.Token(\"#T1\", \"!\")\n\tt2 := j.Token(\"#T2\", \"@\")\n\tif t1 == t2 {\n\t\tt.Error(\"different tokens got same Tin\")\n\t}\n\tif t1 < TinMAX || t2 < TinMAX {\n\t\tt.Error(\"custom tokens should have Tin >= TinMAX\")\n\t}\n}\n\nfunc TestTinName(t *testing.T) {\n\tj := Make()\n\tj.Token(\"#TL\", \"~\")\n\tname := j.TinName(TinOB)\n\tif name != \"#OB\" {\n\t\tt.Errorf(\"expected #OB, got %s\", name)\n\t}\n\ttin := j.Token(\"#TL\")\n\tname2 := j.TinName(tin)\n\tif name2 != \"#TL\" {\n\t\tt.Errorf(\"expected #TL, got %s\", name2)\n\t}\n}\n\n// --- Plugin: Custom fixed token used in parsing ---\n\nfunc TestPluginCustomFixedToken(t *testing.T) {\n\t// Plugin that makes '~' a separator (like comma).\n\ttildeSep := func(j *Jsonic, opts map[string]any) error {\n\t\t// Register ~ as the comma token (replacing comma behavior).\n\t\tj.Token(\"#CA\", \"~\")\n\t\treturn nil\n\t}\n\n\tj := Make()\n\tj.Use(tildeSep)\n\n\t// Now ~ should act as a comma separator.\n\tresult, err := j.Parse(\"a ~ b ~ c\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tarr, ok := result.([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected array, got %T: %v\", result, result)\n\t}\n\tif len(arr) != 3 {\n\t\tt.Fatalf(\"expected 3 elements, got %d: %v\", len(arr), arr)\n\t}\n\tif arr[0] != \"a\" || arr[1] != \"b\" || arr[2] != \"c\" {\n\t\tt.Errorf(\"expected [a b c], got %v\", arr)\n\t}\n}\n\n// --- Plugin: Rule modification ---\n\nfunc TestPluginRuleModification(t *testing.T) {\n\t// Plugin that makes all string values uppercase.\n\tupperPlugin := func(j *Jsonic, opts map[string]any) error {\n\t\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\t\t// Add an after-close action that uppercases string nodes.\n\t\t\trs.AC = append(rs.AC, func(r *Rule, ctx *Context) {\n\t\t\t\tif s, ok := r.Node.(string); ok {\n\t\t\t\t\tr.Node = strings.ToUpper(s)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t\treturn nil\n\t}\n\n\tj := Make()\n\tj.Use(upperPlugin)\n\n\tresult, err := j.Parse(`\"hello\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"HELLO\" {\n\t\tt.Errorf(\"expected HELLO, got %v\", result)\n\t}\n}\n\nfunc TestPluginRuleAddAlternate(t *testing.T) {\n\t// Plugin that adds a custom \"hundred\" rule.\n\thundredPlugin := func(j *Jsonic, opts map[string]any) error {\n\t\t// Register a custom fixed token 'H'.\n\t\tTH := j.Token(\"#TH\", \"H\")\n\n\t\t// Add a new rule that produces 100.\n\t\tj.Rule(\"hundred\", func(rs *RuleSpec, _ *Parser) {\n\t\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = float64(100)\n\t\t\t})\n\t\t})\n\n\t\t// Modify val rule to recognize 'H' and push to \"hundred\".\n\t\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\t\trs.Open = append([]*AltSpec{{\n\t\t\t\tS: [][]Tin{{TH}},\n\t\t\t\tP: \"hundred\",\n\t\t\t}}, rs.Open...)\n\t\t})\n\t\treturn nil\n\t}\n\n\tj := Make()\n\tj.Use(hundredPlugin)\n\n\tresult, err := j.Parse(\"H\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != float64(100) {\n\t\tt.Errorf(\"expected 100, got %v (%T)\", result, result)\n\t}\n}\n\nfunc TestPluginRuleNewRule(t *testing.T) {\n\tj := Make()\n\t// Verify we can create a new rule.\n\tj.Rule(\"custom\", func(rs *RuleSpec, _ *Parser) {\n\t\tif rs.Name != \"custom\" {\n\t\t\tt.Errorf(\"expected rule name 'custom', got '%s'\", rs.Name)\n\t\t}\n\t})\n\tif j.RSM()[\"custom\"] == nil {\n\t\tt.Error(\"custom rule not created\")\n\t}\n}\n\n// --- Plugin: Custom matcher ---\n\nfunc TestPluginCustomMatcher(t *testing.T) {\n\t// Plugin that matches \"$$\" as a special value.\n\tdollarPlugin := func(j *Jsonic, opts map[string]any) error {\n\t\tj.SetOptions(Options{Lex: &LexOptions{Match: map[string]*MatchSpec{\n\t\t\t\"dollar\": {Order: 1500000, Make: func(_ *LexConfig, _ *Options) LexMatcher {\n\t\t\t\treturn func(lex *Lex, rule *Rule) *Token {\n\t\t\t\t\tpnt := lex.Cursor()\n\t\t\t\t\tif pnt.SI+2 <= pnt.Len && lex.Src[pnt.SI:pnt.SI+2] == \"$$\" {\n\t\t\t\t\t\ttkn := lex.Token(\"#VL\", TinVL, \"DOLLAR\", \"$$\")\n\t\t\t\t\t\tpnt.SI += 2\n\t\t\t\t\t\tpnt.CI += 2\n\t\t\t\t\t\treturn tkn\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}},\n\t\t}}})\n\t\treturn nil\n\t}\n\n\tj := Make()\n\tj.Use(dollarPlugin)\n\n\tresult, err := j.Parse(\"$$\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"DOLLAR\" {\n\t\tt.Errorf(\"expected DOLLAR, got %v\", result)\n\t}\n}\n\nfunc TestPluginCustomMatcherInObject(t *testing.T) {\n\t// Custom matcher that matches \"@\" as a special value.\n\tatPlugin := func(j *Jsonic, opts map[string]any) error {\n\t\tj.SetOptions(Options{Lex: &LexOptions{Match: map[string]*MatchSpec{\n\t\t\t\"at\": {Order: 1500000, Make: func(_ *LexConfig, _ *Options) LexMatcher {\n\t\t\t\treturn func(lex *Lex, rule *Rule) *Token {\n\t\t\t\t\tpnt := lex.Cursor()\n\t\t\t\t\tif pnt.SI < pnt.Len && lex.Src[pnt.SI] == '@' {\n\t\t\t\t\t\ttkn := lex.Token(\"#VL\", TinVL, \"AT_VALUE\", \"@\")\n\t\t\t\t\t\tpnt.SI++\n\t\t\t\t\t\tpnt.CI++\n\t\t\t\t\t\treturn tkn\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}},\n\t\t}}})\n\t\treturn nil\n\t}\n\n\tj := Make()\n\tj.Use(atPlugin)\n\n\tresult, err := j.Parse(\"{a: @}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %T: %v\", result, result)\n\t}\n\tif m[\"a\"] != \"AT_VALUE\" {\n\t\tt.Errorf(\"expected AT_VALUE, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestPluginMatcherPriority(t *testing.T) {\n\t// Verify early matchers (priority < 2e6) run before built-in matchers.\n\t// The early matcher sees '42' before the number matcher does.\n\tearlySawInput := false\n\n\tj := Make()\n\tj.SetOptions(Options{Lex: &LexOptions{Match: map[string]*MatchSpec{\n\t\t\"early\": {Order: 1000000, Make: func(_ *LexConfig, _ *Options) LexMatcher {\n\t\t\treturn func(lex *Lex, rule *Rule) *Token {\n\t\t\t\tpnt := lex.Cursor()\n\t\t\t\tif pnt.SI < pnt.Len && lex.Src[pnt.SI] == '4' {\n\t\t\t\t\tearlySawInput = true\n\t\t\t\t}\n\t\t\t\treturn nil // Pass through to built-in matchers.\n\t\t\t}\n\t\t}},\n\t}}})\n\n\tj.Parse(\"42\")\n\n\tif !earlySawInput {\n\t\tt.Error(\"early matcher was not invoked before built-in number matcher\")\n\t}\n}\n\nfunc TestPluginMatcherLowPriorityCaptures(t *testing.T) {\n\t// An early custom matcher can capture input before built-in matchers.\n\tj := Make()\n\tj.SetOptions(Options{Lex: &LexOptions{Match: map[string]*MatchSpec{\n\t\t\"capture42\": {Order: 1000000, Make: func(_ *LexConfig, _ *Options) LexMatcher {\n\t\t\treturn func(lex *Lex, rule *Rule) *Token {\n\t\t\t\tpnt := lex.Cursor()\n\t\t\t\tif pnt.SI+2 <= pnt.Len && lex.Src[pnt.SI:pnt.SI+2] == \"42\" {\n\t\t\t\t\ttkn := lex.Token(\"#VL\", TinVL, \"FORTY_TWO\", \"42\")\n\t\t\t\t\tpnt.SI += 2\n\t\t\t\t\tpnt.CI += 2\n\t\t\t\t\treturn tkn\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}},\n\t}}})\n\n\tresult, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"FORTY_TWO\" {\n\t\tt.Errorf(\"expected FORTY_TWO, got %v\", result)\n\t}\n}\n\n// --- Plugin: Config and RSM access ---\n\nfunc TestPluginConfigAccess(t *testing.T) {\n\tj := Make()\n\tcfg := j.Config()\n\tif cfg == nil {\n\t\tt.Fatal(\"Config() returned nil\")\n\t}\n\tif !cfg.FixedLex {\n\t\tt.Error(\"expected FixedLex to be true\")\n\t}\n}\n\nfunc TestPluginRSMAccess(t *testing.T) {\n\tj := Make()\n\trsm := j.RSM()\n\tif rsm == nil {\n\t\tt.Fatal(\"RSM() returned nil\")\n\t}\n\tif rsm[\"val\"] == nil {\n\t\tt.Error(\"expected 'val' rule in RSM\")\n\t}\n}\n\n// --- Plugin: Instance isolation ---\n\nfunc TestPluginInstanceIsolation(t *testing.T) {\n\tj1 := Make()\n\tj2 := Make()\n\n\t// Registering a token on j1 should not affect j2.\n\tj1.Token(\"#T1\", \"~\")\n\n\tif _, ok := j2.Config().FixedTokens[\"~\"]; ok {\n\t\tt.Error(\"custom token leaked from j1 to j2\")\n\t}\n}\n\n// --- Plugin: Composite test (full plugin workflow) ---\n\nfunc TestPluginComposite(t *testing.T) {\n\t// A realistic plugin that:\n\t// 1. Registers a custom token ';' as separator (replacing comma)\n\t// 2. Adds a before-open action to list rule\n\n\tsemiPlugin := func(j *Jsonic, opts map[string]any) error {\n\t\tj.Token(\"#CA\", \";\")\n\t\treturn nil\n\t}\n\n\tj := Make()\n\tj.Use(semiPlugin)\n\n\t// Semicolon should work as separator.\n\tresult, err := j.Parse(\"a ; b ; c\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tarr, ok := result.([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected array, got %T: %v\", result, result)\n\t}\n\tif len(arr) != 3 {\n\t\tt.Fatalf(\"expected 3 elements, got %d: %v\", len(arr), arr)\n\t}\n\n\t// Original comma should still work too (it's still in FixedTokens).\n\tresult2, err := j.Parse(\"x , y\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tarr2, ok := result2.([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected array, got %T: %v\", result2, result2)\n\t}\n\tif len(arr2) != 2 {\n\t\tt.Fatalf(\"expected 2 elements, got %d: %v\", len(arr2), arr2)\n\t}\n}\n\n// --- Plugin: Nil options handling ---\n\nfunc TestUseNilOptions(t *testing.T) {\n\tinvoked := false\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tinvoked = true\n\t\tif opts != nil {\n\t\t\tt.Errorf(\"expected nil opts, got %v\", opts)\n\t\t}\n\t\treturn nil\n\t})\n\tif !invoked {\n\t\tt.Error(\"plugin not invoked\")\n\t}\n}\n\n// --- Plugin: Disable built-in features ---\n\nfunc TestPluginDisableComments(t *testing.T) {\n\t// Disable comments entirely by providing empty comment definitions.\n\tj := Make(Options{\n\t\tComment: &CommentOptions{\n\t\t\tLex: boolPtr(false),\n\t\t\tDef: map[string]*CommentDef{},\n\t\t},\n\t})\n\n\t// With comments disabled and no comment defs, # should be treated as text.\n\tresult, err := j.Parse(`{a: #hello}`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %T: %v\", result, result)\n\t}\n\tif v, ok := m[\"a\"].(string); !ok || !strings.HasPrefix(v, \"#hello\") {\n\t\tt.Errorf(\"expected a to start with '#hello', got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestPluginDisableNumbers(t *testing.T) {\n\tj := Make(Options{\n\t\tNumber: &NumberOptions{Lex: boolPtr(false)},\n\t})\n\n\t// With numbers disabled, 42 should be treated as text.\n\tresult, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"42\" {\n\t\tt.Errorf(\"expected string '42', got %v (%T)\", result, result)\n\t}\n}\n\n// --- Multi-character fixed tokens ---\n\nfunc TestMultiCharFixedToken(t *testing.T) {\n\tj := Make()\n\tTA := j.Token(\"#TA\", \"=>\")\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{{\n\t\t\tS: [][]Tin{{TA}},\n\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\tr.Node = \"ARROW\"\n\t\t\t},\n\t\t}}, rs.Open...)\n\t})\n\n\tresult, err := j.Parse(\"=>\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"ARROW\" {\n\t\tt.Errorf(\"expected ARROW, got %v\", result)\n\t}\n}\n\nfunc TestMultiCharFixedTokenLongestMatch(t *testing.T) {\n\tj := Make()\n\tTEQ := j.Token(\"#TEQ\", \"=\")\n\tTARROW := j.Token(\"#TARROW\", \"=>\")\n\n\tmatchedEQ := false\n\tmatchedArrow := false\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append([]*AltSpec{\n\t\t\t{\n\t\t\t\tS: [][]Tin{{TARROW}},\n\t\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\t\tmatchedArrow = true\n\t\t\t\t\tr.Node = \"ARROW\"\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tS: [][]Tin{{TEQ}},\n\t\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\t\tmatchedEQ = true\n\t\t\t\t\tr.Node = \"EQ\"\n\t\t\t\t},\n\t\t\t},\n\t\t}, rs.Open...)\n\t})\n\n\t// \"=>\" should match the arrow (longer), not just \"=\".\n\tresult, err := j.Parse(\"=>\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"ARROW\" {\n\t\tt.Errorf(\"expected ARROW, got %v\", result)\n\t}\n\tif !matchedArrow {\n\t\tt.Error(\"arrow should have been matched\")\n\t}\n\tif matchedEQ {\n\t\tt.Error(\"eq should not have been matched for =>\")\n\t}\n}\n\nfunc TestMultiCharFixedTokenBreaksText(t *testing.T) {\n\tj := Make()\n\tj.Token(\"#TA\", \"=>\")\n\n\t// \"abc=>\" should parse \"abc\" as text, then \"=>\" as fixed token.\n\tresult, err := j.Parse(\"{key: abc=>}\")\n\tif err != nil {\n\t\t// If the parser can't handle \"=>\" in this context, that's OK.\n\t\t// The important thing is that \"=>\" breaks text.\n\t\treturn\n\t}\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\treturn\n\t}\n\t// \"key\" should be \"abc\" since \"=>\" breaks text.\n\tif v, ok := m[\"key\"].(string); ok && v == \"abc\" {\n\t\t// Expected behavior: text stops at \"=>\"\n\t}\n\t_ = m\n}\n\n// --- Ender system ---\n\nfunc TestEnderCharsBreakText(t *testing.T) {\n\tj := Make(Options{\n\t\tEnder: []string{\"|\"},\n\t})\n\n\t// \"|\" should end text tokens.\n\tresult, err := j.Parse(\"abc|def\")\n\tif err != nil {\n\t\t// Ender chars may cause unexpected token errors depending on grammar.\n\t\t// That's expected - the important thing is text stops at \"|\".\n\t\treturn\n\t}\n\t// If it parses successfully, \"abc\" should be separated from \"def\".\n\t_ = result\n}\n\nfunc TestEnderCharsInMap(t *testing.T) {\n\tj := Make(Options{\n\t\tEnder: []string{\"|\"},\n\t})\n\n\t// In a map, ender should break values.\n\tresult, err := j.Parse(\"{a: hello|world}\")\n\tif err != nil {\n\t\treturn // Ender breaking may cause parse issues\n\t}\n\t_ = result\n}\n\n// --- Custom escape mappings ---\n\nfunc TestCustomEscapeMappings(t *testing.T) {\n\tj := Make(Options{\n\t\tString: &StringOptions{\n\t\t\tEscape: map[string]string{\n\t\t\t\t\"a\": \"ALPHA\",\n\t\t\t\t\"d\": \"DELTA\",\n\t\t\t},\n\t\t},\n\t})\n\n\tresult, err := j.Parse(`\"\\a\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"ALPHA\" {\n\t\tt.Errorf(\"expected ALPHA, got %v\", result)\n\t}\n\n\tresult2, err := j.Parse(`\"\\d\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result2 != \"DELTA\" {\n\t\tt.Errorf(\"expected DELTA, got %v\", result2)\n\t}\n\n\t// Standard escapes should still work.\n\tresult3, err := j.Parse(`\"\\n\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result3 != \"\\n\" {\n\t\tt.Errorf(\"expected newline, got %v\", result3)\n\t}\n}\n\n// --- Subscriptions ---\n\nfunc TestSubLex(t *testing.T) {\n\tj := Make()\n\n\ttokens := []string{}\n\tj.Sub(func(tkn *Token, rule *Rule, ctx *Context) {\n\t\ttokens = append(tokens, tkn.Src)\n\t}, nil)\n\n\tj.Parse(\"{a: 1}\")\n\n\tif len(tokens) == 0 {\n\t\tt.Error(\"lex subscriber was not invoked\")\n\t}\n\n\t// Should have seen \"{\", \"a\", \":\", \"1\", \"}\", end\n\tfoundBrace := false\n\tfor _, tok := range tokens {\n\t\tif tok == \"{\" {\n\t\t\tfoundBrace = true\n\t\t}\n\t}\n\tif !foundBrace {\n\t\tt.Errorf(\"expected to see '{' token, got: %v\", tokens)\n\t}\n}\n\nfunc TestSubRule(t *testing.T) {\n\tj := Make()\n\n\truleNames := []string{}\n\tj.Sub(nil, func(rule *Rule, ctx *Context) {\n\t\truleNames = append(ruleNames, rule.Name)\n\t})\n\n\tj.Parse(\"{a: 1}\")\n\n\tif len(ruleNames) == 0 {\n\t\tt.Error(\"rule subscriber was not invoked\")\n\t}\n\n\t// Should see rule processing for val, map, pair, etc.\n\tfoundVal := false\n\tfor _, name := range ruleNames {\n\t\tif name == \"val\" {\n\t\t\tfoundVal = true\n\t\t}\n\t}\n\tif !foundVal {\n\t\tt.Errorf(\"expected to see 'val' rule, got: %v\", ruleNames)\n\t}\n}\n\n// --- Instance derivation ---\n\nfunc TestDerive(t *testing.T) {\n\tparent := Make()\n\tparent.Token(\"#TL\", \"~\")\n\n\tchild := parent.Derive()\n\n\t// Child should inherit parent's custom token.\n\tif _, ok := child.Config().FixedTokens[\"~\"]; !ok {\n\t\tt.Error(\"child should inherit parent's custom fixed token\")\n\t}\n}\n\nfunc TestDeriveIsolation(t *testing.T) {\n\tparent := Make()\n\tchild := parent.Derive()\n\n\t// Modifying child should not affect parent.\n\tchild.Token(\"#TX\", \"!\")\n\n\tif _, ok := parent.Config().FixedTokens[\"!\"]; ok {\n\t\tt.Error(\"child modification leaked to parent\")\n\t}\n}\n\nfunc TestDeriveInheritsPlugins(t *testing.T) {\n\tcount := 0\n\tparent := Make()\n\tparent.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tcount++\n\t\treturn nil\n\t})\n\n\t// Plugin was invoked once on parent.\n\tif count != 1 {\n\t\tt.Fatalf(\"expected count 1, got %d\", count)\n\t}\n\n\tchild := parent.Derive()\n\n\t// Plugin should be re-invoked on child.\n\tif count != 2 {\n\t\tt.Errorf(\"expected count 2 after derive, got %d\", count)\n\t}\n\tif len(child.Plugins()) != 1 {\n\t\tt.Errorf(\"expected 1 plugin, got %d\", len(child.Plugins()))\n\t}\n}\n\n// --- Dynamic options ---\n\nfunc TestSetOptions(t *testing.T) {\n\tj := Make()\n\n\t// Parse with defaults.\n\tresult, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != float64(42) {\n\t\tt.Errorf(\"expected 42, got %v\", result)\n\t}\n\n\t// Disable number lexing.\n\tj.SetOptions(Options{\n\t\tNumber: &NumberOptions{Lex: boolPtr(false)},\n\t})\n\n\t// Now 42 should be text.\n\tresult2, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result2 != \"42\" {\n\t\tt.Errorf(\"expected string '42' after SetOptions, got %v (%T)\", result2, result2)\n\t}\n}\n\nfunc TestSetOptionsDeepMerge(t *testing.T) {\n\t// SetOptions should deep-merge like the TS options() setter:\n\t// setting one sub-field should not clobber sibling sub-fields.\n\tj := Make(Options{\n\t\tNumber: &NumberOptions{Lex: boolPtr(true), Hex: boolPtr(true)},\n\t})\n\n\t// Disable hex but leave Lex untouched.\n\tj.SetOptions(Options{\n\t\tNumber: &NumberOptions{Hex: boolPtr(false)},\n\t})\n\n\topts := j.Options()\n\tif opts.Number == nil {\n\t\tt.Fatal(\"expected Number options to be non-nil\")\n\t}\n\tif opts.Number.Lex == nil || !*opts.Number.Lex {\n\t\tt.Errorf(\"expected Number.Lex to remain true after SetOptions, got %v\", opts.Number.Lex)\n\t}\n\tif opts.Number.Hex == nil || *opts.Number.Hex {\n\t\tt.Errorf(\"expected Number.Hex to be false after SetOptions, got %v\", opts.Number.Hex)\n\t}\n\n\t// Numbers should still parse (Lex is still true).\n\tresult, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != float64(42) {\n\t\tt.Errorf(\"expected 42, got %v (%T)\", result, result)\n\t}\n}\n\nfunc TestSetOptionsDeepMergeMaps(t *testing.T) {\n\t// Map fields like Error and Comment.Def should merge, not replace.\n\tj := Make(Options{\n\t\tError: map[string]string{\"unexpected\": \"custom unexpected\"},\n\t})\n\n\tj.SetOptions(Options{\n\t\tError: map[string]string{\"unterminated_string\": \"custom unterminated\"},\n\t})\n\n\topts := j.Options()\n\tif opts.Error[\"unexpected\"] != \"custom unexpected\" {\n\t\tt.Errorf(\"expected Error['unexpected'] to be preserved, got %q\", opts.Error[\"unexpected\"])\n\t}\n\tif opts.Error[\"unterminated_string\"] != \"custom unterminated\" {\n\t\tt.Errorf(\"expected Error['unterminated_string'] to be set, got %q\", opts.Error[\"unterminated_string\"])\n\t}\n}\n\nfunc TestSetOptionsChaining(t *testing.T) {\n\t// Multiple SetOptions calls should accumulate, not reset.\n\tj := Make()\n\n\tj.SetOptions(Options{\n\t\tComment: &CommentOptions{Lex: boolPtr(false)},\n\t})\n\tj.SetOptions(Options{\n\t\tNumber: &NumberOptions{Lex: boolPtr(false)},\n\t})\n\n\topts := j.Options()\n\tif opts.Comment == nil || opts.Comment.Lex == nil || *opts.Comment.Lex {\n\t\tt.Error(\"expected Comment.Lex to remain false after second SetOptions\")\n\t}\n\tif opts.Number == nil || opts.Number.Lex == nil || *opts.Number.Lex {\n\t\tt.Error(\"expected Number.Lex to be false after second SetOptions\")\n\t}\n}\n\n// --- Rule exclude ---\n\nfunc TestExclude(t *testing.T) {\n\tj := Make()\n\n\t// Count alternates with exact \"json\" group tag before exclude.\n\thasJsonGroup := false\n\tfor _, rs := range j.RSM() {\n\t\tfor _, alt := range rs.Open {\n\t\t\tif hasExactTag(alt.G, \"json\") {\n\t\t\t\thasJsonGroup = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif hasJsonGroup {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !hasJsonGroup {\n\t\t// Grammar doesn't use \"json\" group tags, so exclude won't remove anything.\n\t\t// But the option should still work without error.\n\t\tj.SetOptions(Options{Rule: &RuleOptions{Exclude: \"json\"}})\n\t\treturn\n\t}\n\n\t// If there are \"json\" tagged alts, exclude should remove them.\n\tj.SetOptions(Options{Rule: &RuleOptions{Exclude: \"json\"}})\n\n\tfor _, rs := range j.RSM() {\n\t\tfor _, alt := range rs.Open {\n\t\t\tif hasExactTag(alt.G, \"json\") {\n\t\t\t\tt.Errorf(\"rule %s still has 'json' group alt after Exclude\", rs.Name)\n\t\t\t}\n\t\t}\n\t\tfor _, alt := range rs.Close {\n\t\t\tif hasExactTag(alt.G, \"json\") {\n\t\t\t\tt.Errorf(\"rule %s still has 'json' close alt after Exclude\", rs.Name)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestExcludeCustomGroup(t *testing.T) {\n\tj := Make()\n\n\t// Add a custom alternate with a group tag.\n\tTT := j.Token(\"#TT\", \"!\")\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append(rs.Open, &AltSpec{\n\t\t\tS: [][]Tin{{TT}},\n\t\t\tG: \"custom,test\",\n\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"BANG\" },\n\t\t})\n\t})\n\n\t// Exclude \"custom\" group via option.\n\tj.SetOptions(Options{Rule: &RuleOptions{Exclude: \"custom\"}})\n\n\t// The custom alt should be removed.\n\tfound := false\n\tfor _, alt := range j.RSM()[\"val\"].Open {\n\t\tif strings.Contains(alt.G, \"custom\") {\n\t\t\tfound = true\n\t\t}\n\t}\n\tif found {\n\t\tt.Error(\"custom group alt should have been excluded\")\n\t}\n}\n\n// --- Parse metadata ---\n\nfunc TestParseMeta(t *testing.T) {\n\tj := Make()\n\n\t// Add a rule action that reads metadata.\n\tvar capturedMeta map[string]any\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedMeta = ctx.Meta\n\t\t})\n\t})\n\n\tmeta := map[string]any{\"mode\": \"test\", \"version\": 2}\n\tj.ParseMeta(\"42\", meta)\n\n\tif capturedMeta == nil {\n\t\tt.Fatal(\"meta was not passed to context\")\n\t}\n\tif capturedMeta[\"mode\"] != \"test\" {\n\t\tt.Errorf(\"expected mode=test, got %v\", capturedMeta[\"mode\"])\n\t}\n\tif capturedMeta[\"version\"] != 2 {\n\t\tt.Errorf(\"expected version=2, got %v\", capturedMeta[\"version\"])\n\t}\n}\n\nfunc TestParseMetaNil(t *testing.T) {\n\tj := Make()\n\n\t// ParseMeta with nil meta should work.\n\tresult, err := j.ParseMeta(\"42\", nil)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != float64(42) {\n\t\tt.Errorf(\"expected 42, got %v\", result)\n\t}\n}\n\n// --- isTextChar config-aware ---\n\nfunc TestCustomFixedTokenBreaksText(t *testing.T) {\n\tj := Make()\n\tj.Token(\"#TL\", \"~\")\n\n\t// \"abc~def\" should break at \"~\"\n\tresult, err := j.Parse(\"{key: abc~def}\")\n\tif err != nil {\n\t\t// May cause parse error since ~def is unexpected.\n\t\t// The important test is that text stops at ~.\n\t\treturn\n\t}\n\t_ = result\n}\n\n// --- Empty source handling ---\n\nfunc TestEmptySourceDefault(t *testing.T) {\n\tj := Make()\n\tresult, err := j.Parse(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"empty source should not error by default: %v\", err)\n\t}\n\tif result != nil {\n\t\tt.Errorf(\"expected nil for empty source, got %v\", result)\n\t}\n}\n\nfunc TestEmptySourceDisabled(t *testing.T) {\n\tj := Make(Options{\n\t\tLex: &LexOptions{Empty: boolPtr(false)},\n\t})\n\t_, err := j.Parse(\"\")\n\tif err == nil {\n\t\tt.Error(\"expected error when empty source is disallowed\")\n\t}\n}\n\nfunc TestEmptySourceCustomResult(t *testing.T) {\n\tj := Make(Options{\n\t\tLex: &LexOptions{EmptyResult: \"EMPTY\"},\n\t})\n\tresult, err := j.Parse(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"EMPTY\" {\n\t\tt.Errorf(\"expected 'EMPTY', got %v\", result)\n\t}\n}\n\n// --- Custom parser.start ---\n\nfunc TestCustomParserStart(t *testing.T) {\n\tj := Make(Options{\n\t\tParser: &ParserOptions{\n\t\t\tStart: func(src string, j *Jsonic, meta map[string]any) (any, error) {\n\t\t\t\treturn \"CUSTOM:\" + src, nil\n\t\t\t},\n\t\t},\n\t})\n\tresult, err := j.Parse(\"hello\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"CUSTOM:hello\" {\n\t\tt.Errorf(\"expected 'CUSTOM:hello', got %v\", result)\n\t}\n}\n\nfunc TestCustomParserStartWithMeta(t *testing.T) {\n\tj := Make(Options{\n\t\tParser: &ParserOptions{\n\t\t\tStart: func(src string, j *Jsonic, meta map[string]any) (any, error) {\n\t\t\t\tprefix := \"\"\n\t\t\t\tif meta != nil {\n\t\t\t\t\tif p, ok := meta[\"prefix\"].(string); ok {\n\t\t\t\t\t\tprefix = p\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn prefix + src, nil\n\t\t\t},\n\t\t},\n\t})\n\tresult, err := j.ParseMeta(\"world\", map[string]any{\"prefix\": \"hello-\"})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"hello-world\" {\n\t\tt.Errorf(\"expected 'hello-world', got %v\", result)\n\t}\n}\n\n// --- Error hints ---\n\nfunc TestErrorHints(t *testing.T) {\n\tj := Make(Options{\n\t\tHint: map[string]string{\n\t\t\t\"unexpected\": \"Check your syntax for typos.\",\n\t\t},\n\t})\n\t_, err := j.Parse(\"{a: @}\")\n\tif err == nil {\n\t\t// This input might actually parse in some configs.\n\t\t// Use an input that's guaranteed to fail.\n\t\treturn\n\t}\n\tje, ok := err.(*JsonicError)\n\tif !ok {\n\t\tt.Fatalf(\"expected *JsonicError, got %T\", err)\n\t}\n\tif je.Hint != \"Check your syntax for typos.\" {\n\t\tt.Errorf(\"expected hint text, got %q\", je.Hint)\n\t}\n\t// Hint should appear in error string.\n\terrStr := je.Error()\n\tif !strings.Contains(errStr, \"Hint: Check your syntax for typos.\") {\n\t\tt.Errorf(\"error string should contain hint, got:\\n%s\", errStr)\n\t}\n}\n\nfunc TestErrorHintsInOutput(t *testing.T) {\n\tj := Make(Options{\n\t\tHint: map[string]string{\n\t\t\t\"unterminated_string\": \"Did you forget a closing quote?\",\n\t\t},\n\t})\n\t_, err := j.Parse(`\"unclosed`)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for unterminated string\")\n\t}\n\tje, ok := err.(*JsonicError)\n\tif !ok {\n\t\tt.Fatalf(\"expected *JsonicError, got %T\", err)\n\t}\n\tif je.Hint != \"Did you forget a closing quote?\" {\n\t\tt.Errorf(\"expected hint for unterminated_string, got %q\", je.Hint)\n\t}\n}\n\n// --- Config modify callbacks ---\n\nfunc TestConfigModify(t *testing.T) {\n\tj := Make(Options{Property: &PropertyOptions{\n\t\tConfigModify: map[string]ConfigModifier{\n\t\t\t\"disable-hex\": func(cfg *LexConfig, opts *Options) {\n\t\t\t\tcfg.NumberHex = false\n\t\t\t},\n\t\t},\n\t}})\n\n\t// With hex disabled, 0xFF should be text.\n\tresult, err := j.Parse(\"0xFF\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result == float64(255) {\n\t\tt.Error(\"hex should be disabled by config modifier\")\n\t}\n\tif result != \"0xFF\" {\n\t\tt.Errorf(\"expected string '0xFF', got %v (%T)\", result, result)\n\t}\n}\n\n// --- TokenSet ---\n\nfunc TestTokenSetVAL(t *testing.T) {\n\tj := Make()\n\tval := j.TokenSet(\"VAL\")\n\tif val == nil {\n\t\tt.Fatal(\"TokenSet('VAL') returned nil\")\n\t}\n\tif len(val) != 4 {\n\t\tt.Errorf(\"expected 4 VAL tokens, got %d\", len(val))\n\t}\n\t// Should contain TinTX, TinNR, TinST, TinVL.\n\tfound := map[Tin]bool{}\n\tfor _, tin := range val {\n\t\tfound[tin] = true\n\t}\n\tfor _, expected := range []Tin{TinTX, TinNR, TinST, TinVL} {\n\t\tif !found[expected] {\n\t\t\tt.Errorf(\"VAL set missing Tin %d\", expected)\n\t\t}\n\t}\n}\n\nfunc TestTokenSetIGNORE(t *testing.T) {\n\tj := Make()\n\tign := j.TokenSet(\"IGNORE\")\n\tif ign == nil {\n\t\tt.Fatal(\"TokenSet('IGNORE') returned nil\")\n\t}\n\tif len(ign) != 3 {\n\t\tt.Errorf(\"expected 3 IGNORE tokens, got %d\", len(ign))\n\t}\n}\n\nfunc TestTokenSetKEY(t *testing.T) {\n\tj := Make()\n\tkey := j.TokenSet(\"KEY\")\n\tif key == nil {\n\t\tt.Fatal(\"TokenSet('KEY') returned nil\")\n\t}\n\tif len(key) != 4 {\n\t\tt.Errorf(\"expected 4 KEY tokens, got %d\", len(key))\n\t}\n}\n\nfunc TestTokenSetUnknown(t *testing.T) {\n\tj := Make()\n\tresult := j.TokenSet(\"NONEXISTENT\")\n\tif result != nil {\n\t\tt.Errorf(\"expected nil for unknown set, got %v\", result)\n\t}\n}\n\nfunc TestSetTokenSetIGNORE(t *testing.T) {\n\t// Setting IGNORE via SetTokenSet should update the per-instance IgnoreSet\n\t// used by the lexer, matching TS behavior where cfg.tokenSetTins.IGNORE is mutable.\n\tj := Make()\n\n\t// Default: IGNORE has 3 tokens (SP, LN, CM).\n\tign := j.TokenSet(\"IGNORE\")\n\tif len(ign) != 3 {\n\t\tt.Fatalf(\"expected 3 default IGNORE tokens, got %d\", len(ign))\n\t}\n\n\t// Remove LN from the IGNORE set (keep only SP and CM).\n\tj.SetTokenSet(\"IGNORE\", []Tin{TinSP, TinCM})\n\n\tign2 := j.TokenSet(\"IGNORE\")\n\tif len(ign2) != 2 {\n\t\tt.Errorf(\"expected 2 IGNORE tokens after SetTokenSet, got %d\", len(ign2))\n\t}\n\n\t// Verify the lexer config's IgnoreSet was updated.\n\tif j.Config().IgnoreSet[TinLN] {\n\t\tt.Error(\"expected TinLN removed from IgnoreSet, but it is still present\")\n\t}\n\tif !j.Config().IgnoreSet[TinSP] {\n\t\tt.Error(\"expected TinSP in IgnoreSet\")\n\t}\n\tif !j.Config().IgnoreSet[TinCM] {\n\t\tt.Error(\"expected TinCM in IgnoreSet\")\n\t}\n}\n\nfunc TestSetTokenSetIGNOREPerInstance(t *testing.T) {\n\t// Verify that modifying IGNORE on one instance does not affect another.\n\tj1 := Make()\n\tj2 := Make()\n\n\tj1.SetTokenSet(\"IGNORE\", []Tin{TinSP}) // Only spaces\n\n\t// j1 should have 1 IGNORE token.\n\tif len(j1.TokenSet(\"IGNORE\")) != 1 {\n\t\tt.Errorf(\"j1 should have 1 IGNORE token, got %d\", len(j1.TokenSet(\"IGNORE\")))\n\t}\n\n\t// j2 should still have the default 3 IGNORE tokens.\n\tif len(j2.TokenSet(\"IGNORE\")) != 3 {\n\t\tt.Errorf(\"j2 should still have 3 IGNORE tokens, got %d\", len(j2.TokenSet(\"IGNORE\")))\n\t}\n}\n\nfunc TestDeriveInheritsIgnoreSet(t *testing.T) {\n\t// A derived instance should inherit the parent's customized IGNORE set.\n\tparent := Make()\n\tparent.SetTokenSet(\"IGNORE\", []Tin{TinSP, TinCM}) // Remove LN\n\n\tchild := parent.Derive()\n\n\tchildIgn := child.TokenSet(\"IGNORE\")\n\tif len(childIgn) != 2 {\n\t\tt.Errorf(\"child should inherit 2 IGNORE tokens, got %d\", len(childIgn))\n\t}\n\tif child.Config().IgnoreSet[TinLN] {\n\t\tt.Error(\"child should not have TinLN in IgnoreSet\")\n\t}\n\n\t// Modifying child should not affect parent.\n\tchild.SetTokenSet(\"IGNORE\", []Tin{TinSP})\n\tif len(parent.TokenSet(\"IGNORE\")) != 2 {\n\t\tt.Errorf(\"parent should still have 2 IGNORE tokens, got %d\", len(parent.TokenSet(\"IGNORE\")))\n\t}\n}\n\nfunc TestSetOptionsPreservesIgnoreSet(t *testing.T) {\n\t// A plugin (or prior call) may mutate IGNORE via SetTokenSet. A\n\t// subsequent SetOptions — including one triggered internally by\n\t// Grammar() — must not silently reset IGNORE to defaults.\n\tj := Make()\n\tj.SetTokenSet(\"IGNORE\", []Tin{TinSP, TinCM}) // Remove LN\n\n\t// Unrelated SetOptions call must not reset the IGNORE set.\n\tyes := true\n\tj.SetOptions(Options{Number: &NumberOptions{Hex: &yes}})\n\n\tif j.Config().IgnoreSet[TinLN] {\n\t\tt.Error(\"SetOptions reset IgnoreSet — TinLN re-added after unrelated options change\")\n\t}\n\tif len(j.TokenSet(\"IGNORE\")) != 2 {\n\t\tt.Errorf(\"expected 2 IGNORE tokens after SetOptions, got %d\", len(j.TokenSet(\"IGNORE\")))\n\t}\n\n\t// Grammar() applies Options via SetOptions internally — same guarantee must hold.\n\tif err := j.Grammar(&GrammarSpec{Options: &Options{Tag: \"t\"}}); err != nil {\n\t\tt.Fatalf(\"Grammar failed: %v\", err)\n\t}\n\tif j.Config().IgnoreSet[TinLN] {\n\t\tt.Error(\"Grammar's internal SetOptions reset IgnoreSet — TinLN re-added\")\n\t}\n\tif len(j.TokenSet(\"IGNORE\")) != 2 {\n\t\tt.Errorf(\"expected 2 IGNORE tokens after Grammar, got %d\", len(j.TokenSet(\"IGNORE\")))\n\t}\n\n\t// Explicit TokenSet override in the new options must still win.\n\tj.SetOptions(Options{TokenSet: map[string][]string{\"IGNORE\": {\"#SP\"}}})\n\tif len(j.TokenSet(\"IGNORE\")) != 1 {\n\t\tt.Errorf(\"expected TokenSet override to shrink IGNORE to 1 token, got %d\", len(j.TokenSet(\"IGNORE\")))\n\t}\n}\n\nfunc TestSetTokenSetVAL(t *testing.T) {\n\tj := Make()\n\n\t// Default: VAL has 4 tokens (TX, NR, ST, VL).\n\tval := j.TokenSet(\"VAL\")\n\tif len(val) != 4 {\n\t\tt.Fatalf(\"expected 4 default VAL tokens, got %d\", len(val))\n\t}\n\n\t// Add a custom token to VAL.\n\trl := j.Token(\"#RL\")\n\tj.SetTokenSet(\"VAL\", append(val, rl))\n\n\tval2 := j.TokenSet(\"VAL\")\n\tif len(val2) != 5 {\n\t\tt.Errorf(\"expected 5 VAL tokens after SetTokenSet, got %d\", len(val2))\n\t}\n\n\t// Verify the config's ValSet was updated.\n\tif len(j.Config().ValSet) != 5 {\n\t\tt.Errorf(\"expected Config.ValSet to have 5 entries, got %d\", len(j.Config().ValSet))\n\t}\n}\n\nfunc TestSetTokenSetKEY(t *testing.T) {\n\tj := Make()\n\n\tkey := j.TokenSet(\"KEY\")\n\tif len(key) != 4 {\n\t\tt.Fatalf(\"expected 4 default KEY tokens, got %d\", len(key))\n\t}\n\n\t// Reduce KEY to just text and string.\n\tj.SetTokenSet(\"KEY\", []Tin{TinTX, TinST})\n\n\tkey2 := j.TokenSet(\"KEY\")\n\tif len(key2) != 2 {\n\t\tt.Errorf(\"expected 2 KEY tokens after SetTokenSet, got %d\", len(key2))\n\t}\n\tif len(j.Config().KeySet) != 2 {\n\t\tt.Errorf(\"expected Config.KeySet to have 2 entries, got %d\", len(j.Config().KeySet))\n\t}\n}\n\nfunc TestSetTokenSetVALPerInstance(t *testing.T) {\n\tj1 := Make()\n\tj2 := Make()\n\n\tj1.SetTokenSet(\"VAL\", []Tin{TinTX, TinST})\n\n\tif len(j1.TokenSet(\"VAL\")) != 2 {\n\t\tt.Errorf(\"j1 should have 2 VAL tokens, got %d\", len(j1.TokenSet(\"VAL\")))\n\t}\n\tif len(j2.TokenSet(\"VAL\")) != 4 {\n\t\tt.Errorf(\"j2 should still have 4 VAL tokens, got %d\", len(j2.TokenSet(\"VAL\")))\n\t}\n}\n\nfunc TestDeriveInheritsValKeySet(t *testing.T) {\n\tparent := Make()\n\tparent.SetTokenSet(\"VAL\", []Tin{TinTX, TinNR, TinST}) // Remove VL\n\tparent.SetTokenSet(\"KEY\", []Tin{TinTX})                 // Only TX\n\n\tchild := parent.Derive()\n\n\tchildVal := child.TokenSet(\"VAL\")\n\tif len(childVal) != 3 {\n\t\tt.Errorf(\"child should inherit 3 VAL tokens, got %d\", len(childVal))\n\t}\n\tchildKey := child.TokenSet(\"KEY\")\n\tif len(childKey) != 1 {\n\t\tt.Errorf(\"child should inherit 1 KEY token, got %d\", len(childKey))\n\t}\n\n\t// Modifying child should not affect parent.\n\tchild.SetTokenSet(\"VAL\", []Tin{TinTX})\n\tif len(parent.TokenSet(\"VAL\")) != 3 {\n\t\tt.Errorf(\"parent should still have 3 VAL tokens, got %d\", len(parent.TokenSet(\"VAL\")))\n\t}\n}\n\n// --- LexCheck callbacks ---\n\nfunc TestLexCheckFixed(t *testing.T) {\n\tj := Make()\n\t// Override fixed check to replace '{' with a custom token.\n\tj.Config().FixedCheck = func(lex *Lex) *LexCheckResult {\n\t\tpnt := lex.Cursor()\n\t\tif pnt.SI < pnt.Len && lex.Src[pnt.SI] == '{' {\n\t\t\ttkn := lex.Token(\"#OB\", TinOB, nil, \"{\")\n\t\t\tpnt.SI++\n\t\t\tpnt.CI++\n\t\t\t// Return the token normally (same behavior, but proves check ran).\n\t\t\treturn &LexCheckResult{Done: true, Token: tkn}\n\t\t}\n\t\treturn nil // Continue normal matching.\n\t}\n\n\tresult, err := j.Parse(\"{a: 1}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %T\", result)\n\t}\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"expected a=1, got %v\", m[\"a\"])\n\t}\n}\n\nfunc TestLexCheckSkipMatcher(t *testing.T) {\n\tj := Make()\n\t// Skip number matching for specific inputs.\n\tj.Config().NumberCheck = func(lex *Lex) *LexCheckResult {\n\t\tpnt := lex.Cursor()\n\t\tif pnt.SI+3 <= pnt.Len && lex.Src[pnt.SI:pnt.SI+3] == \"999\" {\n\t\t\t// Return Done=true with nil Token to skip number matching for \"999\".\n\t\t\treturn &LexCheckResult{Done: true, Token: nil}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// 999 should fall through to text matcher.\n\tresult, err := j.Parse(\"999\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"999\" {\n\t\tt.Errorf(\"expected string '999', got %v (%T)\", result, result)\n\t}\n\n\t// 42 should still be a number.\n\tresult2, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result2 != float64(42) {\n\t\tt.Errorf(\"expected 42, got %v\", result2)\n\t}\n}\n\n// --- RuleSpec helpers ---\n\nfunc TestRuleSpecClear(t *testing.T) {\n\trs := &RuleSpec{\n\t\tName:  \"test\",\n\t\tOpen:  []*AltSpec{{}, {}},\n\t\tClose: []*AltSpec{{}},\n\t\tBO:    []StateAction{func(r *Rule, ctx *Context) {}},\n\t}\n\trs.Clear()\n\tif len(rs.Open) != 0 || len(rs.Close) != 0 || len(rs.BO) != 0 {\n\t\tt.Error(\"Clear() should empty all slices\")\n\t}\n}\n\nfunc TestRuleSpecAddOpen(t *testing.T) {\n\trs := &RuleSpec{Name: \"test\"}\n\trs.AddOpen(&AltSpec{P: \"a\"}, &AltSpec{P: \"b\"})\n\tif len(rs.Open) != 2 {\n\t\tt.Errorf(\"expected 2 open alts, got %d\", len(rs.Open))\n\t}\n\tif rs.Open[0].P != \"a\" || rs.Open[1].P != \"b\" {\n\t\tt.Error(\"open alts not in expected order\")\n\t}\n}\n\nfunc TestRuleSpecPrependOpen(t *testing.T) {\n\trs := &RuleSpec{Name: \"test\"}\n\trs.AddOpen(&AltSpec{P: \"b\"})\n\trs.PrependOpen(&AltSpec{P: \"a\"})\n\tif len(rs.Open) != 2 {\n\t\tt.Errorf(\"expected 2 open alts, got %d\", len(rs.Open))\n\t}\n\tif rs.Open[0].P != \"a\" {\n\t\tt.Errorf(\"expected first alt 'a', got '%s'\", rs.Open[0].P)\n\t}\n}\n\nfunc TestRuleSpecAddClose(t *testing.T) {\n\trs := &RuleSpec{Name: \"test\"}\n\trs.AddClose(&AltSpec{P: \"x\"})\n\tif len(rs.Close) != 1 || rs.Close[0].P != \"x\" {\n\t\tt.Error(\"AddClose failed\")\n\t}\n}\n\nfunc TestRuleSpecPrependClose(t *testing.T) {\n\trs := &RuleSpec{Name: \"test\"}\n\trs.AddClose(&AltSpec{P: \"b\"})\n\trs.PrependClose(&AltSpec{P: \"a\"})\n\tif len(rs.Close) != 2 || rs.Close[0].P != \"a\" {\n\t\tt.Error(\"PrependClose failed\")\n\t}\n}\n\nfunc TestRuleSpecStateActions(t *testing.T) {\n\trs := &RuleSpec{Name: \"test\"}\n\tcount := 0\n\taction := func(r *Rule, ctx *Context) { count++ }\n\trs.AddBO(action)\n\trs.AddAO(action)\n\trs.AddBC(action)\n\trs.AddAC(action)\n\tif len(rs.BO) != 1 || len(rs.AO) != 1 || len(rs.BC) != 1 || len(rs.AC) != 1 {\n\t\tt.Error(\"state action addition failed\")\n\t}\n}\n\n// --- Debug plugin ---\n\nfunc TestDebugDescribe(t *testing.T) {\n\tj := Make(Options{Tag: \"test-instance\"})\n\tj.Token(\"#TL\", \"~\")\n\tj.Use(Debug)\n\n\tdesc := Describe(j)\n\tif desc == \"\" {\n\t\tt.Fatal(\"Describe returned empty string\")\n\t}\n\tif !strings.Contains(desc, \"test-instance\") {\n\t\tt.Error(\"description should contain tag\")\n\t}\n\tif !strings.Contains(desc, \"#TL\") {\n\t\tt.Error(\"description should contain custom token\")\n\t}\n\tif !strings.Contains(desc, \"val\") {\n\t\tt.Error(\"description should contain val rule\")\n\t}\n\tif !strings.Contains(desc, \"FixedLex: true\") {\n\t\tt.Error(\"description should contain config settings\")\n\t}\n}\n\nfunc TestDebugPlugin(t *testing.T) {\n\tj := Make()\n\t// Debug without trace should not add subscribers.\n\tj.Use(Debug)\n\tif len(j.Plugins()) != 1 {\n\t\tt.Errorf(\"expected 1 plugin, got %d\", len(j.Plugins()))\n\t}\n}\n\n// --- Rule exclude from options ---\n\nfunc TestRuleExcludeFromOptions(t *testing.T) {\n\tj := Make()\n\n\t// Add tagged alternates.\n\tTT := j.Token(\"#TT\", \"!\")\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append(rs.Open, &AltSpec{\n\t\t\tS: [][]Tin{{TT}},\n\t\t\tG: \"experimental\",\n\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"BANG\" },\n\t\t})\n\t})\n\n\t// Create a new instance with exclude in options.\n\tj2 := Make(Options{\n\t\tRule: &RuleOptions{Exclude: \"experimental\"},\n\t})\n\t// Manually add the same alt.\n\tTT2 := j2.Token(\"#TT\", \"!\")\n\tj2.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.Open = append(rs.Open, &AltSpec{\n\t\t\tS: [][]Tin{{TT2}},\n\t\t\tG: \"experimental\",\n\t\t\tA: func(r *Rule, ctx *Context) { r.Node = \"BANG\" },\n\t\t})\n\t})\n\tj2.SetOptions(Options{Rule: &RuleOptions{Exclude: \"experimental\"}})\n\n\t// The experimental alt should be excluded.\n\tfound := false\n\tfor _, alt := range j2.RSM()[\"val\"].Open {\n\t\tif strings.Contains(alt.G, \"experimental\") {\n\t\t\tfound = true\n\t\t}\n\t}\n\tif found {\n\t\tt.Error(\"experimental group should have been excluded via options\")\n\t}\n}\n\n// --- Multi-level token inheritance and isolation (TS: parent-safe) ---\n\nfunc TestDeriveTokenInheritance(t *testing.T) {\n\t// Parent registers token #B. Child registers token #D.\n\t// Child should see both. Parent should only see #B.\n\tc0 := Make()\n\tc0.Token(\"#B0\", \"b\")\n\n\tc1 := c0.Derive()\n\tc1.Token(\"#D0\", \"d\")\n\n\t// c1 inherits c0's token.\n\tif _, ok := c1.Config().FixedTokens[\"b\"]; !ok {\n\t\tt.Error(\"child should inherit parent's #B0 token\")\n\t}\n\t// c1 has its own token.\n\tif _, ok := c1.Config().FixedTokens[\"d\"]; !ok {\n\t\tt.Error(\"child should have its own #D0 token\")\n\t}\n\t// c0 is unaffected by c1's token.\n\tif _, ok := c0.Config().FixedTokens[\"d\"]; ok {\n\t\tt.Error(\"parent should NOT have child's #D0 token\")\n\t}\n\t// c0 still has its own token.\n\tif _, ok := c0.Config().FixedTokens[\"b\"]; !ok {\n\t\tt.Error(\"parent should still have its #B0 token\")\n\t}\n}\n\n// --- Multi-level plugin inheritance with isolation (TS: naked-make) ---\n\n// makeTokenPlugin creates a plugin that registers a fixed token for `char`\n// and a val rule alternate that produces `val` when that token is seen.\n// This mirrors the TS make_token_plugin helper.\nfunc makeTokenPlugin(char, val string) Plugin {\n\treturn func(j *Jsonic, opts map[string]any) error {\n\t\ttn := \"#T<\" + char + \">\"\n\t\tj.Token(tn, char)\n\t\tTT := j.Token(tn, \"\")\n\n\t\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\t\tcapturedVal := val\n\t\t\tcapturedTT := TT\n\t\t\trs.Open = append([]*AltSpec{{\n\t\t\t\tS: [][]Tin{{capturedTT}},\n\t\t\t\tG: \"cv\" + strings.ToLower(capturedVal),\n\t\t\t\tA: func(r *Rule, ctx *Context) {\n\t\t\t\t\tr.Node = capturedVal\n\t\t\t\t},\n\t\t\t}}, rs.Open...)\n\t\t})\n\t\treturn nil\n\t}\n}\n\nfunc TestDeriveMultiLevelPluginInheritance(t *testing.T) {\n\t// j has plugin A (maps char \"A\" to value \"aaa\").\n\tj := Make()\n\tj.Use(makeTokenPlugin(\"A\", \"aaa\"))\n\n\tresultJ, err := j.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"j.Parse: %v\", err)\n\t}\n\texpectMap(t, \"j\", resultJ, map[string]any{\"x\": \"aaa\", \"y\": \"B\", \"z\": \"C\"})\n\n\t// a1 derives from j. a1 should inherit plugin A.\n\ta1 := j.Derive()\n\tresultA1, err := a1.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"a1.Parse: %v\", err)\n\t}\n\texpectMap(t, \"a1\", resultA1, map[string]any{\"x\": \"aaa\", \"y\": \"B\", \"z\": \"C\"})\n\n\t// a2 derives from j. a2 adds plugin B.\n\ta2 := j.Derive()\n\ta2.Use(makeTokenPlugin(\"B\", \"bbb\"))\n\n\tresultA2, err := a2.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"a2.Parse: %v\", err)\n\t}\n\texpectMap(t, \"a2\", resultA2, map[string]any{\"x\": \"aaa\", \"y\": \"bbb\", \"z\": \"C\"})\n\n\t// a1 and j should be unaffected by a2's plugin B.\n\tresultA1Again, err := a1.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"a1 again: %v\", err)\n\t}\n\texpectMap(t, \"a1 again\", resultA1Again, map[string]any{\"x\": \"aaa\", \"y\": \"B\", \"z\": \"C\"})\n\n\tresultJAgain, err := j.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"j again: %v\", err)\n\t}\n\texpectMap(t, \"j again\", resultJAgain, map[string]any{\"x\": \"aaa\", \"y\": \"B\", \"z\": \"C\"})\n\n\t// a22 derives from a2. Inherits plugins A and B. Adds plugin C.\n\ta22 := a2.Derive()\n\ta22.Use(makeTokenPlugin(\"C\", \"ccc\"))\n\n\tresultA22, err := a22.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"a22.Parse: %v\", err)\n\t}\n\texpectMap(t, \"a22\", resultA22, map[string]any{\"x\": \"aaa\", \"y\": \"bbb\", \"z\": \"ccc\"})\n\n\t// a2 unaffected by a22's plugin C.\n\tresultA2Again, err := a2.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"a2 again: %v\", err)\n\t}\n\texpectMap(t, \"a2 again\", resultA2Again, map[string]any{\"x\": \"aaa\", \"y\": \"bbb\", \"z\": \"C\"})\n\n\t// a1 still unaffected.\n\tresultA1Final, err := a1.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"a1 final: %v\", err)\n\t}\n\texpectMap(t, \"a1 final\", resultA1Final, map[string]any{\"x\": \"aaa\", \"y\": \"B\", \"z\": \"C\"})\n\n\t// j still unaffected.\n\tresultJFinal, err := j.Parse(\"x:A,y:B,z:C\")\n\tif err != nil {\n\t\tt.Fatalf(\"j final: %v\", err)\n\t}\n\texpectMap(t, \"j final\", resultJFinal, map[string]any{\"x\": \"aaa\", \"y\": \"B\", \"z\": \"C\"})\n}\n\n// expectMap asserts that result is a map[string]any matching expected.\nfunc expectMap(t *testing.T, label string, result any, expected map[string]any) {\n\tt.Helper()\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"%s: expected map[string]any, got %T: %v\", label, result, result)\n\t}\n\tfor k, v := range expected {\n\t\tif m[k] != v {\n\t\t\tt.Errorf(\"%s: key %q: got %v (%T), want %v (%T)\", label, k, m[k], m[k], v, v)\n\t\t}\n\t}\n}\n\n// --- Custom parser error propagation (TS: custom-parser-error) ---\n\nfunc TestCustomParserStartError(t *testing.T) {\n\tj := Make(Options{\n\t\tParser: &ParserOptions{\n\t\t\tStart: func(src string, j *Jsonic, meta map[string]any) (any, error) {\n\t\t\t\tif src == \"e:0\" {\n\t\t\t\t\treturn nil, &JsonicError{\n\t\t\t\t\t\tCode:   \"custom\",\n\t\t\t\t\t\tDetail: \"bad-parser:e:0\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn src, nil\n\t\t\t},\n\t\t},\n\t})\n\n\t// Normal input works.\n\tresult, err := j.Parse(\"hello\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif result != \"hello\" {\n\t\tt.Errorf(\"expected 'hello', got %v\", result)\n\t}\n\n\t// Error input propagates the error.\n\t_, err = j.Parse(\"e:0\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for 'e:0'\")\n\t}\n\tje, ok := err.(*JsonicError)\n\tif !ok {\n\t\tt.Fatalf(\"expected *JsonicError, got %T: %v\", err, err)\n\t}\n\tif je.Code != \"custom\" {\n\t\tt.Errorf(\"expected code 'custom', got %q\", je.Code)\n\t}\n\tif !strings.Contains(je.Detail, \"e:0\") {\n\t\tt.Errorf(\"expected detail to contain 'e:0', got %q\", je.Detail)\n\t}\n}\n\n// --- Plugin sets error hints (TS: plugin-errmsg) ---\n\nfunc TestPluginErrorHints(t *testing.T) {\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tj.SetOptions(Options{\n\t\t\tHint: map[string]string{\n\t\t\t\t\"unexpected\": \"FOO\",\n\t\t\t},\n\t\t})\n\t\treturn nil\n\t})\n\n\t_, err := j.Parse(\"x::1\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for 'x::1'\")\n\t}\n\tje, ok := err.(*JsonicError)\n\tif !ok {\n\t\tt.Fatalf(\"expected *JsonicError, got %T\", err)\n\t}\n\terrStr := je.Error()\n\tif !strings.Contains(errStr, \"unexpected\") {\n\t\tt.Errorf(\"error should contain 'unexpected', got:\\n%s\", errStr)\n\t}\n\tif !strings.Contains(errStr, \"FOO\") {\n\t\tt.Errorf(\"error should contain hint 'FOO', got:\\n%s\", errStr)\n\t}\n}\n\n// --- Decorate: dynamic string-keyed properties (TS: jsonic.foo = value) ---\n\nfunc TestDecorate(t *testing.T) {\n\t// TS equivalent:\n\t//   let jp0 = j.use(function foo(jsonic) { jsonic.foo = () => 'FOO' })\n\t//   expect(jp0.foo()).equal('FOO')\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tj.Decorate(\"foo\", \"FOO\")\n\t\treturn nil\n\t})\n\tif j.Decoration(\"foo\") != \"FOO\" {\n\t\tt.Errorf(\"expected Decoration('foo') = 'FOO', got %v\", j.Decoration(\"foo\"))\n\t}\n}\n\nfunc TestDecorateChaining(t *testing.T) {\n\t// TS: jp0 adds foo, jp1 adds bar, both accessible on jp1, foo still on jp0.\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tj.Decorate(\"foo\", \"FOO\")\n\t\treturn nil\n\t})\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tj.Decorate(\"bar\", \"BAR\")\n\t\treturn nil\n\t})\n\n\tif j.Decoration(\"foo\") != \"FOO\" {\n\t\tt.Errorf(\"expected foo=FOO, got %v\", j.Decoration(\"foo\"))\n\t}\n\tif j.Decoration(\"bar\") != \"BAR\" {\n\t\tt.Errorf(\"expected bar=BAR, got %v\", j.Decoration(\"bar\"))\n\t}\n}\n\nfunc TestDecorateInherited(t *testing.T) {\n\t// TS: parent decorations inherited by make/derive.\n\tparent := Make()\n\tparent.Decorate(\"foo\", \"FOO\")\n\n\tchild := parent.Derive()\n\n\t// Child inherits parent decoration.\n\tif child.Decoration(\"foo\") != \"FOO\" {\n\t\tt.Errorf(\"child should inherit foo=FOO, got %v\", child.Decoration(\"foo\"))\n\t}\n\n\t// Child adds its own decoration.\n\tchild.Decorate(\"bar\", \"BAR\")\n\tif child.Decoration(\"bar\") != \"BAR\" {\n\t\tt.Errorf(\"expected child bar=BAR, got %v\", child.Decoration(\"bar\"))\n\t}\n\n\t// Parent unaffected by child's decoration.\n\tif parent.Decoration(\"bar\") != nil {\n\t\tt.Errorf(\"parent should NOT have bar, got %v\", parent.Decoration(\"bar\"))\n\t}\n}\n\nfunc TestDecorateUnset(t *testing.T) {\n\tj := Make()\n\tif j.Decoration(\"nonexistent\") != nil {\n\t\tt.Errorf(\"expected nil for unset decoration, got %v\", j.Decoration(\"nonexistent\"))\n\t}\n}\n\nfunc TestDecorateFunction(t *testing.T) {\n\t// Decorations can hold functions, matching TS jsonic.foo = () => 'FOO'.\n\tj := Make()\n\tj.Decorate(\"greet\", func(name string) string {\n\t\treturn \"hello \" + name\n\t})\n\n\tfn := j.Decoration(\"greet\").(func(string) string)\n\tif fn(\"world\") != \"hello world\" {\n\t\tt.Errorf(\"expected 'hello world', got %q\", fn(\"world\"))\n\t}\n}\n\n// --- Context: fields match TS Context ---\n\nfunc TestContextInst(t *testing.T) {\n\t// TS: ctx.inst() returns the jsonic instance.\n\tj := Make()\n\tvar capturedInst *Jsonic\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedInst = ctx.Inst\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif capturedInst != j {\n\t\tt.Error(\"ctx.Inst should be the Jsonic instance\")\n\t}\n}\n\nfunc TestContextOpts(t *testing.T) {\n\tj := Make(Options{Tag: \"test-tag\"})\n\tvar capturedOpts *Options\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedOpts = ctx.Opts\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif capturedOpts == nil {\n\t\tt.Fatal(\"ctx.Opts should not be nil\")\n\t}\n\tif capturedOpts.Tag != \"test-tag\" {\n\t\tt.Errorf(\"expected Tag 'test-tag', got %q\", capturedOpts.Tag)\n\t}\n}\n\nfunc TestContextCfg(t *testing.T) {\n\tj := Make()\n\tvar capturedCfg *LexConfig\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedCfg = ctx.Cfg\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif capturedCfg == nil {\n\t\tt.Fatal(\"ctx.Cfg should not be nil\")\n\t}\n\tif !capturedCfg.NumberLex {\n\t\tt.Error(\"expected NumberLex=true in default config\")\n\t}\n}\n\nfunc TestContextSrc(t *testing.T) {\n\tj := Make()\n\tvar capturedSrc string\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedSrc = ctx.Src\n\t\t})\n\t})\n\n\tj.Parse(\"hello:world\")\n\n\tif capturedSrc != \"hello:world\" {\n\t\tt.Errorf(\"expected ctx.Src='hello:world', got %q\", capturedSrc)\n\t}\n}\n\nfunc TestContextU(t *testing.T) {\n\t// TS: ctx.u is a custom plugin data bag.\n\tj := Make()\n\tvar capturedU map[string]any\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tctx.U[\"plugin-data\"] = \"hello\"\n\t\t})\n\t\trs.AC = append(rs.AC, func(r *Rule, ctx *Context) {\n\t\t\tcapturedU = ctx.U\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif capturedU == nil {\n\t\tt.Fatal(\"ctx.U should not be nil\")\n\t}\n\tif capturedU[\"plugin-data\"] != \"hello\" {\n\t\tt.Errorf(\"expected ctx.U['plugin-data']='hello', got %v\", capturedU[\"plugin-data\"])\n\t}\n}\n\nfunc TestContextMeta(t *testing.T) {\n\tj := Make()\n\tvar capturedMeta map[string]any\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedMeta = ctx.Meta\n\t\t})\n\t})\n\n\tj.ParseMeta(\"42\", map[string]any{\"file\": \"test.jsonic\"})\n\n\tif capturedMeta == nil || capturedMeta[\"file\"] != \"test.jsonic\" {\n\t\tt.Errorf(\"expected ctx.Meta['file']='test.jsonic', got %v\", capturedMeta)\n\t}\n}\n\n// --- Plugin options namespace (TS: options.plugin) ---\n\nfunc TestPluginOptions(t *testing.T) {\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tj.SetPluginOptions(\"foo\", map[string]any{\"x\": 1})\n\t\treturn nil\n\t})\n\n\tpo := j.PluginOptions(\"foo\")\n\tif po == nil || po[\"x\"] != 1 {\n\t\tt.Errorf(\"expected PluginOptions('foo')['x']=1, got %v\", po)\n\t}\n\tif j.PluginOptions(\"bar\") != nil {\n\t\tt.Error(\"expected nil for unset plugin options\")\n\t}\n}\n\nfunc TestPluginOptionsInherited(t *testing.T) {\n\tj := Make()\n\tj.SetPluginOptions(\"foo\", map[string]any{\"x\": 1})\n\tchild := j.Derive()\n\n\tpo := child.PluginOptions(\"foo\")\n\tif po == nil || po[\"x\"] != 1 {\n\t\tt.Errorf(\"child should inherit plugin options, got %v\", po)\n\t}\n\n\t// Child modification doesn't affect parent.\n\tchild.SetPluginOptions(\"foo\", map[string]any{\"y\": 2})\n\tif j.PluginOptions(\"foo\")[\"y\"] != nil {\n\t\tt.Error(\"parent should not be affected by child plugin options\")\n\t}\n}\n\n// --- ErrMsg: custom error tag (TS: errmsg.name) ---\n\nfunc TestErrMsgName(t *testing.T) {\n\tj := Make(Options{\n\t\tErrMsg: &ErrMsgOptions{Name: \"bar\"},\n\t})\n\t_, err := j.Parse(\"x::1\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n\terrStr := err.Error()\n\tif !strings.Contains(errStr, \"[bar/\") {\n\t\tt.Errorf(\"error should use custom tag 'bar', got:\\n%s\", errStr)\n\t}\n\tif strings.Contains(errStr, \"[jsonic/\") {\n\t\tt.Errorf(\"error should NOT use default 'jsonic' tag, got:\\n%s\", errStr)\n\t}\n}\n\nfunc TestErrMsgNameViaPlugin(t *testing.T) {\n\tj := Make()\n\tj.Use(func(j *Jsonic, opts map[string]any) error {\n\t\tj.SetOptions(Options{\n\t\t\tErrMsg: &ErrMsgOptions{Name: \"myplugin\"},\n\t\t})\n\t\treturn nil\n\t})\n\t_, err := j.Parse(\"x::1\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n\tif !strings.Contains(err.Error(), \"[myplugin/\") {\n\t\tt.Errorf(\"error should use 'myplugin' tag, got:\\n%s\", err.Error())\n\t}\n}\n\n// --- Fixed: fixed token lookup (TS: jsonic.fixed(ref)) ---\n\nfunc TestFixedLookup(t *testing.T) {\n\tj := Make()\n\n\t// Lookup by source string.\n\tif j.FixedSrc(\"{\") != TinOB {\n\t\tt.Errorf(\"FixedSrc('{') = %v, want TinOB(%d)\", j.FixedSrc(\"{\"), TinOB)\n\t}\n\n\t// Lookup by Tin.\n\tif j.FixedTin(TinOB) != \"{\" {\n\t\tt.Errorf(\"FixedTin(TinOB) = %v, want '{'\", j.FixedTin(TinOB))\n\t}\n\n\t// Not found.\n\tif j.FixedSrc(\"@\") != 0 {\n\t\tt.Errorf(\"FixedSrc('@') should return 0 for unknown\")\n\t}\n\tif j.FixedTin(999) != \"\" {\n\t\tt.Errorf(\"FixedTin(999) should return '' for unknown\")\n\t}\n}\n\nfunc TestFixedCustomToken(t *testing.T) {\n\tj := Make()\n\tj.Token(\"#TL\", \"~\")\n\n\ttin := j.FixedSrc(\"~\")\n\tif tin == 0 {\n\t\tt.Error(\"FixedSrc('~') should find custom token\")\n\t}\n\n\tif j.FixedTin(tin) != \"~\" {\n\t\tt.Errorf(\"FixedTin(tin) = %v, want '~'\", j.FixedTin(tin))\n\t}\n}\n\n// --- FixedOptions.Token (TS: options.fixed.token StrMap) ---\n\nfunc TestFixedOptionsTokenOverride(t *testing.T) {\n\t// Swap #CA source from \",\" to \";\" via options; the old \",\" mapping goes away.\n\tsep := \";\"\n\tj := Make()\n\tj.SetOptions(Options{Fixed: &FixedOptions{Token: map[string]*string{\"#CA\": &sep}}})\n\n\tif _, ok := j.Config().FixedTokens[\",\"]; ok {\n\t\tt.Error(\"old #CA source ',' should be removed after override\")\n\t}\n\tif tin := j.Config().FixedTokens[\";\"]; tin != TinCA {\n\t\tt.Errorf(\"';' should map to TinCA, got %v\", tin)\n\t}\n\n\t// A list using ';' as separator should parse.\n\tresult, err := j.Parse(\"a;b;c\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlist, ok := result.([]any)\n\tif !ok || len(list) != 3 {\n\t\tt.Fatalf(\"expected 3-element list, got %T %v\", result, result)\n\t}\n}\n\nfunc TestFixedOptionsTokenDelete(t *testing.T) {\n\t// Deleting #CL (':') via nil should remove the colon mapping.\n\tj := Make()\n\tj.SetOptions(Options{Fixed: &FixedOptions{Token: map[string]*string{\"#CL\": nil}}})\n\n\tif _, ok := j.Config().FixedTokens[\":\"]; ok {\n\t\tt.Error(\"':' should be removed from FixedTokens after nil override\")\n\t}\n}\n\nfunc TestFixedOptionsTokenViaMake(t *testing.T) {\n\t// The same override works when passed to Make(), not just SetOptions().\n\tsep := \"|\"\n\tj := Make(Options{Fixed: &FixedOptions{Token: map[string]*string{\"#CA\": &sep}}})\n\n\tif _, ok := j.Config().FixedTokens[\",\"]; ok {\n\t\tt.Error(\"old ',' mapping should be gone when passed via Make()\")\n\t}\n\tif tin := j.Config().FixedTokens[\"|\"]; tin != TinCA {\n\t\tt.Errorf(\"'|' should map to TinCA, got %v\", tin)\n\t}\n}\n\n// --- ModifyOpen/ModifyClose with ListMods (TS: rs.open(alts, mods)) ---\n\nfunc TestModifyOpen(t *testing.T) {\n\tj := Make()\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\torigLen := len(rs.Open)\n\t\t// Delete the first alternate.\n\t\trs.ModifyOpen(&AltModListOpts{Delete: []int{0}})\n\t\tif len(rs.Open) >= origLen {\n\t\t\tt.Errorf(\"expected fewer open alts after delete, got %d (was %d)\", len(rs.Open), origLen)\n\t\t}\n\t})\n}\n\nfunc TestModifyOpenCustom(t *testing.T) {\n\tj := Make()\n\tvar customCalled bool\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.ModifyOpen(&AltModListOpts{\n\t\t\tCustom: func(list []*AltSpec) []*AltSpec {\n\t\t\t\tcustomCalled = true\n\t\t\t\treturn list\n\t\t\t},\n\t\t})\n\t})\n\tif !customCalled {\n\t\tt.Error(\"custom callback should have been called\")\n\t}\n}\n\n// --- ctx.Root (TS: ctx.root()) ---\n\nfunc TestContextRoot(t *testing.T) {\n\tj := Make()\n\tvar capturedRoot *Rule\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tcapturedRoot = ctx.Root\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif capturedRoot == nil {\n\t\tt.Fatal(\"ctx.Root should not be nil\")\n\t}\n\tif capturedRoot.Name != \"val\" {\n\t\tt.Errorf(\"ctx.Root.Name = %q, want 'val'\", capturedRoot.Name)\n\t}\n}\n\n// --- ctx.NOTOKEN / ctx.NORULE (TS: ctx.NOTOKEN, ctx.NORULE) ---\n\nfunc TestContextSentinels(t *testing.T) {\n\tj := Make()\n\tvar gotNoToken *Token\n\tvar gotNoRule *Rule\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tgotNoToken = ctx.NOTOKEN\n\t\t\tgotNoRule = ctx.NORULE\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif gotNoToken == nil || !gotNoToken.IsNoToken() {\n\t\tt.Error(\"ctx.NOTOKEN should be the sentinel no-token\")\n\t}\n\tif gotNoRule == nil || gotNoRule != NoRule {\n\t\tt.Error(\"ctx.NORULE should be the sentinel no-rule\")\n\t}\n}\n\n// --- ctx.TC (token count) ---\n\nfunc TestContextTC(t *testing.T) {\n\tj := Make()\n\tvar tc int\n\n\tj.Sub(nil, func(rule *Rule, ctx *Context) {\n\t\ttc = ctx.TC\n\t})\n\n\tj.Parse(\"{a:1}\")\n\n\tif tc <= 0 {\n\t\tt.Errorf(\"ctx.TC should be > 0 after parsing, got %d\", tc)\n\t}\n}\n\n// --- ctx.F (format function) ---\n\nfunc TestContextF(t *testing.T) {\n\tj := Make()\n\tvar formatted string\n\n\tj.Rule(\"val\", func(rs *RuleSpec, _ *Parser) {\n\t\trs.AO = append(rs.AO, func(r *Rule, ctx *Context) {\n\t\t\tif ctx.F != nil {\n\t\t\t\tformatted = ctx.F(\"hello\")\n\t\t\t}\n\t\t})\n\t})\n\n\tj.Parse(\"42\")\n\n\tif formatted != \"hello\" {\n\t\tt.Errorf(\"ctx.F('hello') = %q, want 'hello'\", formatted)\n\t}\n}\n\n// --- token.Use (custom metadata) ---\n\nfunc TestTokenUse(t *testing.T) {\n\tj := Make()\n\tvar gotUse map[string]any\n\n\tj.SetOptions(Options{Lex: &LexOptions{Match: map[string]*MatchSpec{\n\t\t\"test\": {Order: 1500000, Make: func(_ *LexConfig, _ *Options) LexMatcher {\n\t\t\treturn func(lex *Lex, rule *Rule) *Token {\n\t\t\t\tpnt := lex.Cursor()\n\t\t\t\tif pnt.SI < pnt.Len && lex.Src[pnt.SI] == '@' {\n\t\t\t\t\ttkn := lex.Token(\"#VL\", TinVL, \"AT\", \"@\")\n\t\t\t\t\ttkn.Use = map[string]any{\"custom\": true}\n\t\t\t\t\tpnt.SI++\n\t\t\t\t\tpnt.CI++\n\t\t\t\t\treturn tkn\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}},\n\t}}})\n\n\tj.Sub(func(tkn *Token, rule *Rule, ctx *Context) {\n\t\tif tkn.Use != nil {\n\t\t\tgotUse = tkn.Use\n\t\t}\n\t}, nil)\n\n\tj.Parse(\"@\")\n\n\tif gotUse == nil || gotUse[\"custom\"] != true {\n\t\tt.Errorf(\"token.Use should contain custom data, got %v\", gotUse)\n\t}\n}\n\n// --- token.Bad() ---\n\nfunc TestTokenBad(t *testing.T) {\n\ttkn := MakeToken(\"#VL\", TinVL, nil, \"x\", Point{})\n\ttkn.Bad(\"test_error\", map[string]any{\"detail\": \"info\"})\n\n\tif tkn.Err != \"test_error\" {\n\t\tt.Errorf(\"Bad() should set Err, got %q\", tkn.Err)\n\t}\n\tif tkn.Use == nil || tkn.Use[\"detail\"] != \"info\" {\n\t\tt.Errorf(\"Bad() should merge details into Use, got %v\", tkn.Use)\n\t}\n}\n\n// --- lex.Bad() ---\n\nfunc TestLexBad(t *testing.T) {\n\tlex := NewLex(\"test\", DefaultLexConfig())\n\ttkn := lex.Bad(\"bad_input\")\n\n\tif tkn.Tin != TinBD {\n\t\tt.Errorf(\"Bad() should create BD token, got Tin=%d\", tkn.Tin)\n\t}\n\tif tkn.Err != \"bad_input\" {\n\t\tt.Errorf(\"Bad() should set Err, got %q\", tkn.Err)\n\t}\n}\n\n// --- jsonic.Id ---\n\nfunc TestJsonicId(t *testing.T) {\n\tj := Make()\n\tif j.Id() == \"\" {\n\t\tt.Error(\"Id() should not be empty\")\n\t}\n\tif !strings.HasPrefix(j.Id(), \"Jsonic/\") {\n\t\tt.Errorf(\"Id() should start with 'Jsonic/', got %q\", j.Id())\n\t}\n}\n\nfunc TestJsonicIdWithTag(t *testing.T) {\n\tj := Make(Options{Tag: \"test\"})\n\tif !strings.Contains(j.Id(), \"/test\") {\n\t\tt.Errorf(\"Id() with tag should contain '/test', got %q\", j.Id())\n\t}\n}\n\nfunc TestJsonicIdUnique(t *testing.T) {\n\tj1 := Make()\n\tj2 := Make()\n\tif j1.Id() == j2.Id() {\n\t\tt.Errorf(\"different instances should have different IDs: %q\", j1.Id())\n\t}\n}\n\n// --- jsonic.Empty() ---\n\nfunc TestEmpty(t *testing.T) {\n\tj := Empty()\n\t// All rules should be cleared.\n\tfor name, rs := range j.RSM() {\n\t\tif len(rs.Open) > 0 || len(rs.Close) > 0 {\n\t\t\tt.Errorf(\"Empty() rule %q should have no alts, got %d open, %d close\",\n\t\t\t\tname, len(rs.Open), len(rs.Close))\n\t\t}\n\t}\n}\n\n// --- options.result.fail ---\n\nfunc TestResultFail(t *testing.T) {\n\tj := Make(Options{\n\t\tResult: &ResultOptions{Fail: []any{\"BAD\"}},\n\t\tValue: &ValueOptions{\n\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\"BAD\": {Val: \"BAD\"},\n\t\t\t},\n\t\t},\n\t})\n\t_, err := j.Parse(\"BAD\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for result in fail list\")\n\t}\n}\n\nfunc TestResultFailCustomValue(t *testing.T) {\n\tj := Make(Options{\n\t\tResult: &ResultOptions{Fail: []any{\"FAIL\"}},\n\t\tProperty: &PropertyOptions{\n\t\t\tConfigModify: map[string]ConfigModifier{\n\t\t\t\t\"fail\": func(cfg *LexConfig, opts *Options) {\n\t\t\t\t\tcfg.ResultFail = opts.Result.Fail\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\t// Add a custom value that produces \"FAIL\".\n\tj.SetOptions(Options{\n\t\tValue: &ValueOptions{\n\t\t\tDef: map[string]*ValueDef{\n\t\t\t\t\"FAIL\": {Val: \"FAIL\"},\n\t\t\t},\n\t\t},\n\t})\n\n\t_, err := j.Parse(\"FAIL\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for result.fail value\")\n\t}\n}\n\n// --- lex.Fwd (forward lookahead) ---\n\nfunc TestLexFwd(t *testing.T) {\n\tlex := NewLex(\"hello world\", DefaultLexConfig())\n\tif lex.Fwd(5) != \"hello\" {\n\t\tt.Errorf(\"Fwd(5) = %q, want 'hello'\", lex.Fwd(5))\n\t}\n\tif lex.Fwd(100) != \"hello world\" {\n\t\tt.Errorf(\"Fwd(100) = %q, want 'hello world'\", lex.Fwd(100))\n\t}\n\t// Advance position.\n\tpnt := lex.Cursor()\n\tpnt.SI = 6\n\tif lex.Fwd(5) != \"world\" {\n\t\tt.Errorf(\"Fwd(5) after advance = %q, want 'world'\", lex.Fwd(5))\n\t}\n}\n\n// --- Custom token sets (TS: options.tokenSet) ---\n\nfunc TestCustomTokenSet(t *testing.T) {\n\tj := Make()\n\tj.SetTokenSet(\"CUSTOM\", []Tin{TinNR, TinST})\n\n\ttins := j.TokenSet(\"CUSTOM\")\n\tif len(tins) != 2 || tins[0] != TinNR || tins[1] != TinST {\n\t\tt.Errorf(\"expected [TinNR, TinST], got %v\", tins)\n\t}\n\n\t// Built-in sets still work.\n\tif j.TokenSet(\"VAL\") == nil {\n\t\tt.Error(\"VAL set should still exist\")\n\t}\n}\n\nfunc TestCustomTokenSetInherited(t *testing.T) {\n\tj := Make()\n\tj.SetTokenSet(\"CUSTOM\", []Tin{TinNR})\n\tchild := j.Derive()\n\n\tif child.TokenSet(\"CUSTOM\") == nil {\n\t\tt.Error(\"child should inherit custom token set\")\n\t}\n}\n"
  },
  {
    "path": "go/readme_test.go",
    "content": "package jsonic\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n)\n\nfunc boolp(b bool) *bool { return &b }\n\n// toJSON converts a value to its JSON string for easy comparison.\nfunc toJSON(t *testing.T, v any) string {\n\tt.Helper()\n\tb, err := json.Marshal(v)\n\tif err != nil {\n\t\tt.Fatalf(\"json.Marshal failed: %v\", err)\n\t}\n\treturn string(b)\n}\n\n// --- Main README examples ---\n\nfunc TestReadmeQuickExample(t *testing.T) {\n\t// The main README shows:\n\t//   result, err := jsonic.Parse(\"a:1, b:2\")\n\tresult, err := Parse(\"a:1, b:2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot := toJSON(t, result)\n\tif got != `{\"a\":1,\"b\":2}` {\n\t\tt.Errorf(\"got %s, want %s\", got, `{\"a\":1,\"b\":2}`)\n\t}\n}\n\nfunc TestReadmeSyntaxExamples(t *testing.T) {\n\t// All should parse to {\"a\": 1, \"b\": \"B\"}\n\twant := `{\"a\":1,\"b\":\"B\"}`\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t}{\n\t\t{\"unquoted\", `a:1,b:B`},\n\t\t{\"newline-separated\", \"a:1\\nb:B\"},\n\t\t{\"with-comments\", \"a:1\\n// a:2\\n# a:3\\n/* b wants\\n * to B\\n */\\nb:B\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult, err := Parse(tc.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tgot := toJSON(t, result)\n\t\t\tif got != want {\n\t\t\t\tt.Errorf(\"got %s, want %s\", got, want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReadmeRelaxations(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  string\n\t}{\n\t\t{\"unquoted-keys\", `a:1`, `{\"a\":1}`},\n\t\t{\"implicit-object\", `a:1,b:2`, `{\"a\":1,\"b\":2}`},\n\t\t{\"implicit-array\", `a,b`, `[\"a\",\"b\"]`},\n\t\t{\"trailing-commas\", `{a:1,b:2,}`, `{\"a\":1,\"b\":2}`},\n\t\t{\"single-quoted\", `'hello'`, `\"hello\"`},\n\t\t{\"backtick\", \"`hello`\", `\"hello\"`},\n\t\t{\"object-merging\", `a:{b:1},a:{c:2}`, `{\"a\":{\"b\":1,\"c\":2}}`},\n\t\t{\"path-diving\", `a:b:1,a:c:2`, `{\"a\":{\"b\":1,\"c\":2}}`},\n\t\t{\"number-1e1\", `1e1`, `10`},\n\t\t{\"number-hex\", `0xa`, `10`},\n\t\t{\"number-octal\", `0o12`, `10`},\n\t\t{\"number-binary\", `0b1010`, `10`},\n\t\t{\"number-separator\", `1_000`, `1000`},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult, err := Parse(tc.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tgot := toJSON(t, result)\n\t\t\tif got != tc.want {\n\t\t\t\tt.Errorf(\"got %s, want %s\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// --- Go README examples ---\n\nfunc TestGoReadmeQuickExample(t *testing.T) {\n\tresult, err := Parse(\"a:1, b:2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %T\", result)\n\t}\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"a: got %v, want 1\", m[\"a\"])\n\t}\n\tif m[\"b\"] != float64(2) {\n\t\tt.Errorf(\"b: got %v, want 2\", m[\"b\"])\n\t}\n}\n\nfunc TestGoReadmeConfiguredInstance(t *testing.T) {\n\t// Go README shows: disabling numbers makes them strings\n\tj := Make(Options{\n\t\tNumber: &NumberOptions{Lex: boolp(false)},\n\t})\n\n\tresult, err := j.Parse(\"a:1, b:2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm, ok := result.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %T\", result)\n\t}\n\t// With numbers disabled, values should be strings\n\tif m[\"a\"] != \"1\" {\n\t\tt.Errorf(\"a: got %v (%T), want string \\\"1\\\"\", m[\"a\"], m[\"a\"])\n\t}\n\tif m[\"b\"] != \"2\" {\n\t\tt.Errorf(\"b: got %v (%T), want string \\\"2\\\"\", m[\"b\"], m[\"b\"])\n\t}\n}\n\nfunc TestGoReadmeSyntaxHighlights(t *testing.T) {\n\t// Go README lists these syntax highlights\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  string\n\t}{\n\t\t{\"unquoted-keys\", `a:1`, `{\"a\":1}`},\n\t\t{\"implicit-objects\", `a:1,b:2`, `{\"a\":1,\"b\":2}`},\n\t\t{\"implicit-arrays\", `a,b,c`, `[\"a\",\"b\",\"c\"]`},\n\t\t{\"comments-hash\", \"a:1\\n#ignored\\nb:2\", `{\"a\":1,\"b\":2}`},\n\t\t{\"comments-line\", \"a:1\\n// ignored\\nb:2\", `{\"a\":1,\"b\":2}`},\n\t\t{\"comments-block\", \"a:1\\n/* ignored */\\nb:2\", `{\"a\":1,\"b\":2}`},\n\t\t{\"single-quotes\", `'hello'`, `\"hello\"`},\n\t\t{\"backtick-quotes\", \"`hello`\", `\"hello\"`},\n\t\t{\"path-diving\", `a:b:1`, `{\"a\":{\"b\":1}}`},\n\t\t{\"trailing-commas\", `{a:1,}`, `{\"a\":1}`},\n\t\t{\"hex-number\", `0xff`, `255`},\n\t\t{\"octal-number\", `0o12`, `10`},\n\t\t{\"binary-number\", `0b1010`, `10`},\n\t\t{\"number-separator\", `1_000`, `1000`},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult, err := Parse(tc.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tgot := toJSON(t, result)\n\t\t\tif got != tc.want {\n\t\t\t\tt.Errorf(\"got %s, want %s\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "go/rule.go",
    "content": "package jsonic\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// groupTagRe is the regex every g tag must match: a lowercase letter\n// followed by one or more lowercase letters, digits, or hyphens.\n// Validated by NormAlt (and, transitively, by Grammar/GrammarText).\nvar groupTagRe = regexp.MustCompile(`^[a-z][a-z0-9-]+$`)\n\n// ValidateGroupTags returns an error if any tag in the supplied\n// comma-separated string fails the group-tag regex.\nfunc ValidateGroupTags(g string) error {\n\tif g == \"\" {\n\t\treturn nil\n\t}\n\tfor _, tag := range strings.Split(g, \",\") {\n\t\ttag = strings.TrimSpace(tag)\n\t\tif tag == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif !groupTagRe.MatchString(tag) {\n\t\t\treturn fmt.Errorf(\"Grammar: invalid group tag %q — must match %s\", tag, groupTagRe)\n\t\t}\n\t}\n\treturn nil\n}\n\n// RuleState represents whether a rule is in open or close state.\ntype RuleState = string\n\nconst (\n\tOPEN  RuleState = \"o\"\n\tCLOSE RuleState = \"c\"\n)\n\n// Undefined is a sentinel value distinguishing \"no value\" from nil (null).\n// In TypeScript, undefined !== null. In Go, we use this sentinel.\ntype undefinedType struct{}\n\nvar Undefined any = &undefinedType{}\n\n// IsUndefined checks if a value is the Undefined sentinel.\nfunc IsUndefined(v any) bool {\n\t_, ok := v.(*undefinedType)\n\treturn ok\n}\n\n// Skip is a sentinel value that acts as undefined in deep merge — the base\n// value is preserved. Represented as \"@SKIP\" in grammar options.\ntype skipType struct{}\n\nvar Skip any = &skipType{}\n\n// IsSkip checks if a value is the Skip sentinel.\nfunc IsSkip(v any) bool {\n\t_, ok := v.(*skipType)\n\treturn ok\n}\n\n// UnwrapUndefined converts Undefined sentinels to nil in the result.\nfunc UnwrapUndefined(v any) any {\n\tif IsUndefined(v) {\n\t\treturn nil\n\t}\n\tswitch val := v.(type) {\n\tcase map[string]any:\n\t\tfor k, vv := range val {\n\t\t\tval[k] = UnwrapUndefined(vv)\n\t\t}\n\t\treturn val\n\tcase []any:\n\t\tfor i, vv := range val {\n\t\t\tval[i] = UnwrapUndefined(vv)\n\t\t}\n\t\treturn val\n\t}\n\treturn v\n}\n\n// AltCond is a condition function for an alternate.\ntype AltCond func(r *Rule, ctx *Context) bool\n\n// AltAction is an action function for an alternate.\ntype AltAction func(r *Rule, ctx *Context)\n\n// AltError is an error function for an alternate.\ntype AltError func(r *Rule, ctx *Context) *Token\n\n// AltModifier can modify an alt match result. Returns the (possibly modified) AltSpec.\ntype AltModifier func(alt *AltSpec, r *Rule, ctx *Context) *AltSpec\n\n// StateAction is a before/after action on a rule state transition.\ntype StateAction func(r *Rule, ctx *Context)\n\n// CondOp represents a comparison operator with a value for declarative conditions.\n// Used in the CD field of AltSpec to define conditions declaratively,\n// matching the TypeScript c: { 'n.pk': { $lte: 0 } } syntax.\ntype CondOp struct {\n\tOp  string\n\tVal int\n}\n\n// Comparison operator constructors for declarative conditions (AltSpec.CD field).\nfunc CEq(val int) CondOp  { return CondOp{Op: \"$eq\", Val: val} }\nfunc CNe(val int) CondOp  { return CondOp{Op: \"$ne\", Val: val} }\nfunc CLt(val int) CondOp  { return CondOp{Op: \"$lt\", Val: val} }\nfunc CLte(val int) CondOp { return CondOp{Op: \"$lte\", Val: val} }\nfunc CGt(val int) CondOp  { return CondOp{Op: \"$gt\", Val: val} }\nfunc CGte(val int) CondOp { return CondOp{Op: \"$gte\", Val: val} }\n\n// AltSpec defines a parse alternate specification.\ntype AltSpec struct {\n\tS  [][]Tin                            // Token Tin sequences to match: s[0] for t0, s[1] for t1\n\tP  string                             // Push rule name (create child)\n\tR  string                             // Replace rule name (create sibling)\n\tB  int                                // Move token pointer backward (backtrack)\n\tC  AltCond                            // Custom condition (function)\n\tCD map[string]any                     // Declarative condition (converted to C by NormAlt)\n\tN  map[string]int                     // Counter increments\n\tA  AltAction                          // Match action\n\tU  map[string]any                     // Custom props added to Rule.u\n\tK  map[string]any                     // Custom props added to Rule.k (propagated)\n\tG  string                             // Named group tags (comma-separated)\n\tH  AltModifier                        // Alt modifier (called after match to potentially modify the alt)\n\tE  AltError                           // Error generation\n\tPF func(r *Rule, ctx *Context) string // Dynamic push rule name\n\tRF func(r *Rule, ctx *Context) string // Dynamic replace rule name\n\tBF func(r *Rule, ctx *Context) int    // Dynamic backtrack\n}\n\n// RuleSpec defines the specification for a parsing rule.\ntype RuleSpec struct {\n\tName  string\n\tOpen  []*AltSpec\n\tClose []*AltSpec\n\tBO    []StateAction // Before-open actions\n\tBC    []StateAction // Before-close actions\n\tAO    []StateAction // After-open actions\n\tAC    []StateAction // After-close actions\n\n\t// fnrefInstalled tracks which StateAction functions have already\n\t// been wired into each phase via wireStateActions, deduped by\n\t// function pointer. Prevents multiple Grammar() calls from stacking\n\t// duplicate state actions when they re-register the same handler\n\t// for the same reserved `@<rulename>-<phase>` slot.\n\tfnrefInstalled map[string]map[uintptr]bool\n}\n\n// Clear removes all alternates and state actions from this RuleSpec.\nfunc (rs *RuleSpec) Clear() *RuleSpec {\n\trs.Open = rs.Open[:0]\n\trs.Close = rs.Close[:0]\n\trs.BO = rs.BO[:0]\n\trs.BC = rs.BC[:0]\n\trs.AO = rs.AO[:0]\n\trs.AC = rs.AC[:0]\n\treturn rs\n}\n\n// AddOpen appends alternates to the open list (at the end).\nfunc (rs *RuleSpec) AddOpen(alts ...*AltSpec) *RuleSpec {\n\trs.Open = append(rs.Open, alts...)\n\treturn rs\n}\n\n// AddClose appends alternates to the close list (at the end).\nfunc (rs *RuleSpec) AddClose(alts ...*AltSpec) *RuleSpec {\n\trs.Close = append(rs.Close, alts...)\n\treturn rs\n}\n\n// PrependOpen inserts alternates at the beginning of the open list.\nfunc (rs *RuleSpec) PrependOpen(alts ...*AltSpec) *RuleSpec {\n\trs.Open = append(alts, rs.Open...)\n\treturn rs\n}\n\n// PrependClose inserts alternates at the beginning of the close list.\nfunc (rs *RuleSpec) PrependClose(alts ...*AltSpec) *RuleSpec {\n\trs.Close = append(alts, rs.Close...)\n\treturn rs\n}\n\n// AltModListOpts configures modifications for RuleSpec alternate lists.\n// Matches the TS ListMods parameter to rs.open(alts, mods)/rs.close(alts, mods).\ntype AltModListOpts struct {\n\tDelete []int                            // Indices to delete (supports negative).\n\tMove   []int                            // Pairs: [from, to, from, to, ...].\n\tCustom func(list []*AltSpec) []*AltSpec // Custom modification callback.\n}\n\n// ModifyOpen applies delete/move/custom modifications to the open alternates list.\n// Matches TS `rs.open(alts, mods)` where mods has delete/move/custom.\nfunc (rs *RuleSpec) ModifyOpen(mods *AltModListOpts) *RuleSpec {\n\trs.Open = modifyAltList(rs.Open, mods)\n\treturn rs\n}\n\n// ModifyClose applies delete/move/custom modifications to the close alternates list.\nfunc (rs *RuleSpec) ModifyClose(mods *AltModListOpts) *RuleSpec {\n\trs.Close = modifyAltList(rs.Close, mods)\n\treturn rs\n}\n\nfunc modifyAltList(list []*AltSpec, mods *AltModListOpts) []*AltSpec {\n\tif mods == nil || list == nil {\n\t\treturn list\n\t}\n\t// Convert to []any, apply ModList, convert back.\n\tanyList := make([]any, len(list))\n\tfor i, v := range list {\n\t\tanyList[i] = v\n\t}\n\tanyList = ModList(anyList, &ModListOpts{\n\t\tDelete: mods.Delete,\n\t\tMove:   mods.Move,\n\t})\n\tresult := make([]*AltSpec, len(anyList))\n\tfor i, v := range anyList {\n\t\tresult[i] = v.(*AltSpec)\n\t}\n\tif mods.Custom != nil {\n\t\tif newList := mods.Custom(result); newList != nil {\n\t\t\tresult = newList\n\t\t}\n\t}\n\treturn result\n}\n\n// AddBO appends a before-open action.\nfunc (rs *RuleSpec) AddBO(action StateAction) *RuleSpec {\n\trs.BO = append(rs.BO, action)\n\treturn rs\n}\n\n// AddAO appends an after-open action.\nfunc (rs *RuleSpec) AddAO(action StateAction) *RuleSpec {\n\trs.AO = append(rs.AO, action)\n\treturn rs\n}\n\n// AddBC appends a before-close action.\nfunc (rs *RuleSpec) AddBC(action StateAction) *RuleSpec {\n\trs.BC = append(rs.BC, action)\n\treturn rs\n}\n\n// AddAC appends an after-close action.\nfunc (rs *RuleSpec) AddAC(action StateAction) *RuleSpec {\n\trs.AC = append(rs.AC, action)\n\treturn rs\n}\n\n// getRuleProp accesses a rule property by path (e.g. \"d\", \"n.pk\").\n// Returns the integer value and whether it was found.\n// Matches the TypeScript getRuleProp(r, prop, subprop) function.\nfunc getRuleProp(r *Rule, prop string, subprop string) (int, bool) {\n\tif r == nil {\n\t\treturn 0, false\n\t}\n\tswitch prop {\n\tcase \"d\":\n\t\treturn r.D, true\n\tcase \"n\":\n\t\tif subprop != \"\" {\n\t\t\tval, ok := r.N[subprop]\n\t\t\treturn val, ok\n\t\t}\n\t}\n\treturn 0, false\n}\n\n// MakeRuleCond creates an AltCond function from a comparison operator, property path, and value.\n// Matches the TypeScript makeRuleCond(co, prop, subprop, val) function.\n// When the property is not set (missing), the condition returns true.\nfunc MakeRuleCond(op string, prop string, subprop string, val int) AltCond {\n\tswitch op {\n\tcase \"$eq\":\n\t\treturn func(r *Rule, ctx *Context) bool {\n\t\t\trval, ok := getRuleProp(r, prop, subprop)\n\t\t\treturn !ok || rval == val\n\t\t}\n\tcase \"$ne\":\n\t\treturn func(r *Rule, ctx *Context) bool {\n\t\t\trval, ok := getRuleProp(r, prop, subprop)\n\t\t\treturn !ok || rval != val\n\t\t}\n\tcase \"$lt\":\n\t\treturn func(r *Rule, ctx *Context) bool {\n\t\t\trval, ok := getRuleProp(r, prop, subprop)\n\t\t\treturn !ok || rval < val\n\t\t}\n\tcase \"$lte\":\n\t\treturn func(r *Rule, ctx *Context) bool {\n\t\t\trval, ok := getRuleProp(r, prop, subprop)\n\t\t\treturn !ok || rval <= val\n\t\t}\n\tcase \"$gt\":\n\t\treturn func(r *Rule, ctx *Context) bool {\n\t\t\trval, ok := getRuleProp(r, prop, subprop)\n\t\t\treturn !ok || rval > val\n\t\t}\n\tcase \"$gte\":\n\t\treturn func(r *Rule, ctx *Context) bool {\n\t\t\trval, ok := getRuleProp(r, prop, subprop)\n\t\t\treturn !ok || rval >= val\n\t\t}\n\tdefault:\n\t\tpanic(\"MakeRuleCond: unknown comparison operator: \" + op)\n\t}\n}\n\n// NormAlt normalizes an AltSpec by converting a declarative CD condition\n// into a C function and validating the G tag format.  Returns a non-nil\n// error if any G tag fails the group-tag regex; callers must check the\n// return value and surface the error (no panics).\nfunc NormAlt(alt *AltSpec) error {\n\tif alt == nil {\n\t\treturn nil\n\t}\n\n\tif err := ValidateGroupTags(alt.G); err != nil {\n\t\treturn err\n\t}\n\n\tif alt.CD == nil || alt.C != nil {\n\t\treturn nil\n\t}\n\n\tvar conds []AltCond\n\tfor propdef, pspec := range alt.CD {\n\t\tparts := strings.SplitN(propdef, \".\", 2)\n\t\tprop := parts[0]\n\t\tsubprop := \"\"\n\t\tif len(parts) == 2 {\n\t\t\tsubprop = parts[1]\n\t\t}\n\n\t\tswitch v := pspec.(type) {\n\t\tcase int:\n\t\t\tconds = append(conds, MakeRuleCond(\"$eq\", prop, subprop, v))\n\t\tcase CondOp:\n\t\t\tconds = append(conds, MakeRuleCond(v.Op, prop, subprop, v.Val))\n\t\t}\n\t}\n\n\tif len(conds) == 1 {\n\t\talt.C = conds[0]\n\t} else if len(conds) > 1 {\n\t\talt.C = func(r *Rule, ctx *Context) bool {\n\t\t\tfor _, cond := range conds {\n\t\t\t\tif !cond(r, ctx) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// NormAlts normalizes all alternates in a RuleSpec.  Returns the first\n// validation error encountered, if any.\nfunc NormAlts(spec *RuleSpec) error {\n\tfor _, alt := range spec.Open {\n\t\tif err := NormAlt(alt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor _, alt := range spec.Close {\n\t\tif err := NormAlt(alt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Rule represents a rule instance during parsing.\ntype Rule struct {\n\tI      int\n\tName   string\n\tSpec   *RuleSpec\n\tNode   any\n\tState  RuleState\n\tD      int\n\tChild  *Rule\n\tParent *Rule\n\tPrev   *Rule\n\tNext   *Rule\n\n\t// Generalized per-position matched tokens. O[i] holds the token\n\t// matched at the i-th lookahead position during OPEN (mirroring C\n\t// for CLOSE). ON / CN give the number of matched positions. This\n\t// supersedes the legacy O0/O1/OS (and C0/C1/CS) two-slot fields,\n\t// which are still maintained below for backward compatibility.\n\tO  []*Token\n\tON int\n\tC  []*Token\n\tCN int\n\n\t// Legacy two-slot aliases. Kept in sync with O[0..1] / C[0..1] by\n\t// ParseAlts so existing grammar code and plugins that read r.O0,\n\t// r.O1, r.C0, r.C1, r.OS, r.CS continue to work unchanged.\n\tO0 *Token\n\tO1 *Token\n\tC0 *Token\n\tC1 *Token\n\tOS int\n\tCS int\n\n\tN      map[string]int\n\tU      map[string]any\n\tK      map[string]any\n\tWhy    string\n}\n\n// NoRule is a sentinel rule.\n// Node is Undefined (like TS where NORULE.node = undefined).\nvar NoRule *Rule\n\nfunc init() {\n\tNoRule = &Rule{Name: \"norule\", I: -1, State: OPEN, Node: Undefined,\n\t\tN: make(map[string]int), U: make(map[string]any), K: make(map[string]any)}\n}\n\n// Eq checks if counter equals limit (nil/missing → true).\nfunc (r *Rule) Eq(counter string, limit int) bool {\n\tval, ok := r.N[counter]\n\treturn !ok || val == limit\n}\n\n// Lt checks if counter < limit (nil/missing → true).\nfunc (r *Rule) Lt(counter string, limit int) bool {\n\tval, ok := r.N[counter]\n\treturn !ok || val < limit\n}\n\n// Gt checks if counter > limit (nil/missing → true).\nfunc (r *Rule) Gt(counter string, limit int) bool {\n\tval, ok := r.N[counter]\n\treturn !ok || val > limit\n}\n\n// Lte checks if counter <= limit (nil/missing → true).\nfunc (r *Rule) Lte(counter string, limit int) bool {\n\tval, ok := r.N[counter]\n\treturn !ok || val <= limit\n}\n\n// Gte checks if counter >= limit (nil/missing → true).\nfunc (r *Rule) Gte(counter string, limit int) bool {\n\tval, ok := r.N[counter]\n\treturn !ok || val >= limit\n}\n\n// MakeRule creates a new Rule from a RuleSpec.\nfunc MakeRule(spec *RuleSpec, ctx *Context, node any) *Rule {\n\tr := &Rule{\n\t\tI: ctx.UI, Name: spec.Name, Spec: spec, Node: node,\n\t\tState: OPEN, D: ctx.RSI,\n\t\tChild: NoRule, Parent: NoRule, Prev: NoRule, Next: NoRule,\n\t\tO: nil, ON: 0, C: nil, CN: 0,\n\t\tO0: NoToken, O1: NoToken, C0: NoToken, C1: NoToken,\n\t\tN: make(map[string]int), U: make(map[string]any), K: make(map[string]any),\n\t}\n\tctx.UI++\n\treturn r\n}\n\n// Process processes this rule, returning the next rule to process.\nfunc (r *Rule) Process(ctx *Context, lex *Lex) *Rule {\n\tisOpen := r.State == OPEN\n\tvar next *Rule\n\tif isOpen {\n\t\tnext = r\n\t} else {\n\t\tnext = NoRule\n\t}\n\n\tdef := r.Spec\n\tvar alts []*AltSpec\n\tif isOpen {\n\t\talts = def.Open\n\t} else {\n\t\talts = def.Close\n\t}\n\n\t// Before actions\n\tif isOpen && len(def.BO) > 0 {\n\t\tfor _, action := range def.BO {\n\t\t\taction(r, ctx)\n\t\t}\n\t} else if !isOpen && len(def.BC) > 0 {\n\t\tfor _, action := range def.BC {\n\t\t\taction(r, ctx)\n\t\t}\n\t}\n\n\t// Match alternates\n\talt, _ := ParseAlts(isOpen, alts, lex, r, ctx)\n\n\t// No alternate matched: immediate parse error (matching TS parse_alts behavior).\n\t// In TS, when alts exist but none match, out.e = ctx.t0 which triggers this.bad().\n\tif alt == nil && len(alts) > 0 {\n\t\tctx.ParseErr = ctx.T0\n\t\treturn next\n\t}\n\n\t// Alt modifier\n\tif alt != nil && alt.H != nil {\n\t\talt = alt.H(alt, r, ctx)\n\t}\n\n\t// Error check: if alt.E returns a token, signal a parse error.\n\tif alt != nil && alt.E != nil {\n\t\terrTkn := alt.E(r, ctx)\n\t\tif errTkn != nil {\n\t\t\tctx.ParseErr = errTkn\n\t\t}\n\t}\n\n\t// Update counters\n\tif alt != nil && alt.N != nil {\n\t\tfor cn, cv := range alt.N {\n\t\t\tif cv == 0 {\n\t\t\t\tr.N[cn] = 0\n\t\t\t} else {\n\t\t\t\tif _, ok := r.N[cn]; !ok {\n\t\t\t\t\tr.N[cn] = 0\n\t\t\t\t}\n\t\t\t\tr.N[cn] += cv\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set custom properties\n\tif alt != nil && alt.U != nil {\n\t\tfor k, v := range alt.U {\n\t\t\tr.U[k] = v\n\t\t}\n\t}\n\tif alt != nil && alt.K != nil {\n\t\tfor k, v := range alt.K {\n\t\t\tr.K[k] = v\n\t\t}\n\t}\n\n\t// Action callback\n\tif alt != nil && alt.A != nil {\n\t\talt.A(r, ctx)\n\t}\n\n\t// Push / Replace / Pop\n\tif alt != nil {\n\t\t// Resolve push rule name (static or dynamic)\n\t\tpushName := alt.P\n\t\tif alt.PF != nil {\n\t\t\tpushName = alt.PF(r, ctx)\n\t\t}\n\t\t// Resolve replace rule name (static or dynamic)\n\t\treplaceName := alt.R\n\t\tif alt.RF != nil {\n\t\t\treplaceName = alt.RF(r, ctx)\n\t\t}\n\n\t\tif pushName != \"\" {\n\t\t\trulespec, ok := ctx.RSM[pushName]\n\t\t\tif ok {\n\t\t\t\tif ctx.RSI < len(ctx.RS) {\n\t\t\t\t\tctx.RS[ctx.RSI] = r\n\t\t\t\t} else {\n\t\t\t\t\tctx.RS = append(ctx.RS, r)\n\t\t\t\t}\n\t\t\t\tctx.RSI++\n\t\t\t\tnext = MakeRule(rulespec, ctx, r.Node)\n\t\t\t\tr.Child = next\n\t\t\t\tnext.Parent = r\n\t\t\t\tfor k, v := range r.N {\n\t\t\t\t\tnext.N[k] = v\n\t\t\t\t}\n\t\t\t\tif len(r.K) > 0 {\n\t\t\t\t\tfor k, v := range r.K {\n\t\t\t\t\t\tnext.K[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if replaceName != \"\" {\n\t\t\trulespec, ok := ctx.RSM[replaceName]\n\t\t\tif ok {\n\t\t\t\tnext = MakeRule(rulespec, ctx, r.Node)\n\t\t\t\tnext.Parent = r.Parent\n\t\t\t\tnext.Prev = r\n\t\t\t\tfor k, v := range r.N {\n\t\t\t\t\tnext.N[k] = v\n\t\t\t\t}\n\t\t\t\tif len(r.K) > 0 {\n\t\t\t\t\tfor k, v := range r.K {\n\t\t\t\t\t\tnext.K[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if !isOpen {\n\t\t\t// Pop\n\t\t\tif ctx.RSI > 0 {\n\t\t\t\tctx.RSI--\n\t\t\t\tnext = ctx.RS[ctx.RSI]\n\t\t\t} else {\n\t\t\t\tnext = NoRule\n\t\t\t}\n\t\t}\n\t} else if !isOpen {\n\t\t// No alt matched AND we're closing → pop\n\t\tif ctx.RSI > 0 {\n\t\t\tctx.RSI--\n\t\t\tnext = ctx.RS[ctx.RSI]\n\t\t} else {\n\t\t\tnext = NoRule\n\t\t}\n\t}\n\n\tr.Next = next\n\n\t// After actions\n\tif isOpen && len(def.AO) > 0 {\n\t\tfor _, action := range def.AO {\n\t\t\taction(r, ctx)\n\t\t}\n\t} else if !isOpen && len(def.AC) > 0 {\n\t\tfor _, action := range def.AC {\n\t\t\taction(r, ctx)\n\t\t}\n\t}\n\n\t// State transition\n\tif r.State == OPEN {\n\t\tr.State = CLOSE\n\t}\n\n\t// Token consumption with backtrack (only when an alt matched).\n\t// Generalized from the previous 2-slot shift to any number of\n\t// consumed positions, to match the N-token lookahead support in\n\t// ParseAlts.\n\tif alt != nil {\n\t\tbacktrack := alt.B\n\t\tif alt.BF != nil {\n\t\t\tbacktrack = alt.BF(r, ctx)\n\t\t}\n\t\tvar consumed int\n\t\tif isOpen {\n\t\t\tconsumed = r.ON - backtrack\n\t\t} else {\n\t\t\tconsumed = r.CN - backtrack\n\t\t}\n\t\tif consumed < 0 {\n\t\t\tconsumed = 0\n\t\t}\n\n\t\tif consumed > 0 {\n\t\t\t// Maintain the 2-slot history (V1 = last consumed,\n\t\t\t// V2 = prior). Semantics preserved for consumed == 1, 2.\n\t\t\tif consumed == 1 {\n\t\t\t\tctx.V2 = ctx.V1\n\t\t\t\tctx.V1 = ctx.T[0]\n\t\t\t} else {\n\t\t\t\tctx.V2 = ctx.T[consumed-2]\n\t\t\t\tctx.V1 = ctx.T[consumed-1]\n\t\t\t}\n\n\t\t\t// Shift the lookahead buffer left by `consumed` slots,\n\t\t\t// filling vacated tail positions with NoToken so later\n\t\t\t// alts re-fetch from the lexer.\n\t\t\tL := len(ctx.T)\n\t\t\tfor i := 0; i < L-consumed; i++ {\n\t\t\t\tctx.T[i] = ctx.T[i+consumed]\n\t\t\t}\n\t\t\tstart := L - consumed\n\t\t\tif start < 0 {\n\t\t\t\tstart = 0\n\t\t\t}\n\t\t\tfor i := start; i < L; i++ {\n\t\t\t\tctx.T[i] = NoToken\n\t\t\t}\n\n\t\t\t// Sync legacy T0 / T1 aliases.\n\t\t\tif len(ctx.T) >= 1 {\n\t\t\t\tctx.T0 = ctx.T[0]\n\t\t\t} else {\n\t\t\t\tctx.T0 = NoToken\n\t\t\t}\n\t\t\tif len(ctx.T) >= 2 {\n\t\t\t\tctx.T1 = ctx.T[1]\n\t\t\t} else {\n\t\t\t\tctx.T1 = NoToken\n\t\t\t}\n\n\t\t\tctx.TC += consumed\n\t\t}\n\t}\n\n\treturn next\n}\n\n// ParseAlts attempts to match one of the alternates.\n//\n// Supports arbitrary N-token lookahead: an alt's S slice may declare\n// any number of positions (previously capped at 2). Tokens are fetched\n// lazily - position i is only requested after position i-1 matches.\nfunc ParseAlts(isOpen bool, alts []*AltSpec, lex *Lex, rule *Rule, ctx *Context) (*AltSpec, bool) {\n\tif len(alts) == 0 {\n\t\treturn nil, false\n\t}\n\n\tfor _, alt := range alts {\n\t\tmatched := 0\n\t\tcond := true\n\n\t\tsN := len(alt.S)\n\t\tfor i := 0; i < sN; i++ {\n\t\t\t// Grow the lookahead buffer on demand.\n\t\t\tfor len(ctx.T) <= i {\n\t\t\t\tctx.T = append(ctx.T, NoToken)\n\t\t\t}\n\n\t\t\t// Lazy fetch: only pull a new token from the lexer if this\n\t\t\t// slot has not been populated by a previous alt / fetch.\n\t\t\tif ctx.T[i].IsNoToken() {\n\t\t\t\ttkn := lex.Next(rule)\n\t\t\t\tctx.T[i] = tkn\n\t\t\t\t// Keep the legacy T0 / T1 aliases in sync so existing\n\t\t\t\t// grammar / plugin code that reads them observes the\n\t\t\t\t// same values as ctx.T[0] / ctx.T[1].\n\t\t\t\tif i == 0 {\n\t\t\t\t\tctx.T0 = tkn\n\t\t\t\t} else if i == 1 {\n\t\t\t\t\tctx.T1 = tkn\n\t\t\t\t}\n\t\t\t\tif len(ctx.LexSubs) > 0 {\n\t\t\t\t\tfor _, sub := range ctx.LexSubs {\n\t\t\t\t\t\tsub(tkn, rule, ctx)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Empty alt.S[i] means \"no Tin constraint at this position\"\n\t\t\t// (wildcard) - the token is still fetched and consumed but\n\t\t\t// the match check is skipped. This prevents silently\n\t\t\t// dropping the check at a later required position.\n\t\t\tif len(alt.S[i]) != 0 {\n\t\t\t\tif !tinMatch(ctx.T[i].Tin, alt.S[i]) {\n\t\t\t\t\tcond = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tmatched = i + 1\n\t\t}\n\n\t\t// Record the matched tokens on the rule. Both the generalized\n\t\t// O / ON (or C / CN) slice form and the legacy O0 / O1 / OS\n\t\t// (or C0 / C1 / CS) two-slot form are populated.\n\t\tif isOpen {\n\t\t\tif cap(rule.O) < matched {\n\t\t\t\trule.O = make([]*Token, matched)\n\t\t\t} else {\n\t\t\t\trule.O = rule.O[:matched]\n\t\t\t}\n\t\t\tfor i := 0; i < matched; i++ {\n\t\t\t\trule.O[i] = ctx.T[i]\n\t\t\t}\n\t\t\trule.ON = matched\n\t\t\trule.OS = matched\n\t\t\tif matched >= 1 {\n\t\t\t\trule.O0 = rule.O[0]\n\t\t\t} else {\n\t\t\t\trule.O0 = NoToken\n\t\t\t}\n\t\t\tif matched >= 2 {\n\t\t\t\trule.O1 = rule.O[1]\n\t\t\t} else {\n\t\t\t\trule.O1 = NoToken\n\t\t\t}\n\t\t} else {\n\t\t\tif cap(rule.C) < matched {\n\t\t\t\trule.C = make([]*Token, matched)\n\t\t\t} else {\n\t\t\t\trule.C = rule.C[:matched]\n\t\t\t}\n\t\t\tfor i := 0; i < matched; i++ {\n\t\t\t\trule.C[i] = ctx.T[i]\n\t\t\t}\n\t\t\trule.CN = matched\n\t\t\trule.CS = matched\n\t\t\tif matched >= 1 {\n\t\t\t\trule.C0 = rule.C[0]\n\t\t\t} else {\n\t\t\t\trule.C0 = NoToken\n\t\t\t}\n\t\t\tif matched >= 2 {\n\t\t\t\trule.C1 = rule.C[1]\n\t\t\t} else {\n\t\t\t\trule.C1 = NoToken\n\t\t\t}\n\t\t}\n\n\t\tif cond && alt.C != nil {\n\t\t\tcond = alt.C(rule, ctx)\n\t\t}\n\n\t\tif cond {\n\t\t\treturn alt, true\n\t\t}\n\t}\n\n\treturn nil, false\n}\n\nfunc tinMatch(tin Tin, tins []Tin) bool {\n\tfor _, t := range tins {\n\t\tif tin == t {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "go/rule_include_test.go",
    "content": "package jsonic\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\n// --- include-only filter ---\n\nfunc TestRuleIncludeKeepsOnlyTaggedAlts(t *testing.T) {\n\t// Seed a rule with alts carrying different tag sets, then apply\n\t// rule.include = \"keep\" and verify only keep-tagged alts survive.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"keep,json\"},\n\t\t\t\t\t{S: \"#CA\", G: \"drop\"},\n\t\t\t\t\t{S: \"#CS\", G: \"keep\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"keep\"}})\n\n\tkept := j.RSM()[\"val\"].Close\n\n\t// Expected: the two alts tagged \"keep\" are preserved; the \"drop\"\n\t// alt and all built-in val-close alts without \"keep\" are gone.\n\tcountByTag := func(alts []*AltSpec, tag string) int {\n\t\tn := 0\n\t\tfor _, a := range alts {\n\t\t\tfor _, part := range strings.Split(a.G, \",\") {\n\t\t\t\tif strings.TrimSpace(part) == tag {\n\t\t\t\t\tn++\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn n\n\t}\n\tif got := countByTag(kept, \"keep\"); got != 2 {\n\t\tt.Errorf(\"expected 2 alts with 'keep', got %d\", got)\n\t}\n\tif got := countByTag(kept, \"drop\"); got != 0 {\n\t\tt.Errorf(\"expected 0 alts with 'drop', got %d\", got)\n\t}\n}\n\nfunc TestRuleIncludeDropsUntaggedAlts(t *testing.T) {\n\t// Alts with no G tag are dropped when include is active — matches TS.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"keep\"},\n\t\t\t\t\t{S: \"#CA\"}, // no G\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"keep\"}})\n\n\tfor _, a := range j.RSM()[\"val\"].Close {\n\t\tif a.G == \"\" {\n\t\t\tt.Error(\"expected alts with no G to be dropped by include filter\")\n\t\t}\n\t}\n}\n\nfunc TestRuleIncludeEmptyIsNoop(t *testing.T) {\n\t// Empty include string must not filter anything.\n\tj := Make()\n\toriginalCount := len(j.RSM()[\"val\"].Close)\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"\"}})\n\n\tif got := len(j.RSM()[\"val\"].Close); got != originalCount {\n\t\tt.Errorf(\"empty Include should be noop; before=%d after=%d\", originalCount, got)\n\t}\n}\n\nfunc TestRuleIncludeMultipleTags(t *testing.T) {\n\t// Comma-separated includes: an alt survives if it matches any listed tag.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"alpha\"},\n\t\t\t\t\t{S: \"#CA\", G: \"beta\"},\n\t\t\t\t\t{S: \"#CS\", G: \"gamma\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"alpha,gamma\"}})\n\n\t// We expect the alpha and gamma alts preserved; beta dropped.\n\tsawAlpha := false\n\tsawBeta := false\n\tsawGamma := false\n\tfor _, a := range j.RSM()[\"val\"].Close {\n\t\tfor _, part := range strings.Split(a.G, \",\") {\n\t\t\tswitch strings.TrimSpace(part) {\n\t\t\tcase \"alpha\":\n\t\t\t\tsawAlpha = true\n\t\t\tcase \"beta\":\n\t\t\t\tsawBeta = true\n\t\t\tcase \"gamma\":\n\t\t\t\tsawGamma = true\n\t\t\t}\n\t\t}\n\t}\n\tif !sawAlpha || !sawGamma {\n\t\tt.Errorf(\"expected alpha and gamma preserved, sawAlpha=%v sawGamma=%v\", sawAlpha, sawGamma)\n\t}\n\tif sawBeta {\n\t\tt.Error(\"expected beta dropped by include=alpha,gamma\")\n\t}\n}\n\n// --- include + exclude ordering ---\n\nfunc TestRuleIncludeThenExclude(t *testing.T) {\n\t// Apply include first, then exclude, within a single SetOptions call.\n\t// An alt tagged \"keep,drop\" makes it past include but is removed by\n\t// exclude — verifying the documented ordering.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"keep\"},\n\t\t\t\t\t{S: \"#CA\", G: \"keep,drop\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"keep\", Exclude: \"drop\"}})\n\n\tgotKeepOnly := false\n\tfor _, a := range j.RSM()[\"val\"].Close {\n\t\tif a.G == \"keep\" {\n\t\t\tgotKeepOnly = true\n\t\t}\n\t\tif strings.Contains(a.G, \"drop\") {\n\t\t\tt.Errorf(\"expected 'drop'-tagged alt removed, got G=%q\", a.G)\n\t\t}\n\t}\n\tif !gotKeepOnly {\n\t\tt.Error(\"expected the solo 'keep'-tagged alt to survive\")\n\t}\n}\n\n// --- text + grammar-text paths ---\n\nfunc TestGrammarTextAppliesInclude(t *testing.T) {\n\t// rule.include declared in a jsonic grammar text string is honoured.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"keep\"},\n\t\t\t\t\t{S: \"#CA\", G: \"other\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := j.GrammarText(`options: { rule: { include: \"keep\" } }`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, a := range j.RSM()[\"val\"].Close {\n\t\tif a.G == \"\" {\n\t\t\tt.Error(\"untagged alt should be dropped by include\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, tag := range strings.Split(a.G, \",\") {\n\t\t\tif strings.TrimSpace(tag) == \"keep\" {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\tt.Errorf(\"alt with G=%q survived include=keep\", a.G)\n\t\t}\n\t}\n}\n\nfunc TestSetOptionsTextInclude(t *testing.T) {\n\t// SetOptionsText can flip the include filter via a jsonic snippet.\n\tj := Make()\n\terr := j.Grammar(&GrammarSpec{\n\t\tRule: map[string]*GrammarRuleSpec{\n\t\t\t\"val\": {\n\t\t\t\tClose: []*GrammarAltSpec{\n\t\t\t\t\t{S: \"#ZZ\", G: \"picked\"},\n\t\t\t\t\t{S: \"#CA\", G: \"dropped\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, err := j.SetOptionsText(`rule: { include: \"picked\" }`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, a := range j.RSM()[\"val\"].Close {\n\t\tfor _, tag := range strings.Split(a.G, \",\") {\n\t\t\tif strings.TrimSpace(tag) == \"dropped\" {\n\t\t\t\tt.Errorf(\"alt with G=%q survived include=picked\", a.G)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- end-to-end parse behaviour ---\n\nfunc TestRuleIncludePreservesParsingForTaggedAlts(t *testing.T) {\n\t// Walk every rule in the default grammar and directly mutate each\n\t// AltSpec.G to prefix \"json,\" — this simulates a \"json\"-tagged\n\t// variant that includes every existing alt. Then include=\"json\"\n\t// must keep the whole grammar, so normal parsing still works.\n\tj := Make()\n\tfor _, rs := range j.RSM() {\n\t\tfor _, a := range rs.Open {\n\t\t\ta.G = appendTag(a.G, \"json\")\n\t\t}\n\t\tfor _, a := range rs.Close {\n\t\t\ta.G = appendTag(a.G, \"json\")\n\t\t}\n\t}\n\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"json\"}})\n\n\tout, err := j.Parse(`{\"a\":1,\"b\":[2,3]}`)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tm, ok := out.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %T\", out)\n\t}\n\tif m[\"a\"] != float64(1) {\n\t\tt.Errorf(\"a: expected 1, got %v\", m[\"a\"])\n\t}\n\tl, ok := m[\"b\"].([]any)\n\tif !ok || len(l) != 2 || l[0] != float64(2) || l[1] != float64(3) {\n\t\tt.Errorf(\"b: expected [2,3], got %v\", m[\"b\"])\n\t}\n}\n\nfunc TestRuleIncludeBreaksParsingWhenGrammarHasNoMatchingTags(t *testing.T) {\n\t// If nothing in the grammar is tagged with the include group, the\n\t// filter wipes out every alt — parsing the simplest input must fail.\n\tj := Make()\n\tj.SetOptions(Options{Rule: &RuleOptions{Include: \"doesnotexist\"}})\n\n\tif _, err := j.Parse(`{\"a\":1}`); err == nil {\n\t\tt.Error(\"expected parse error after include wiped the grammar\")\n\t}\n}\n\n// appendTag returns g with \"tag\" appended; handles the empty-G case.\nfunc appendTag(g, tag string) string {\n\tif g == \"\" {\n\t\treturn tag\n\t}\n\treturn g + \",\" + tag\n}\n"
  },
  {
    "path": "go/safe_test.go",
    "content": "package jsonic\n\nimport \"testing\"\n\n// Mirrors test/safe.test.js. TS protects against __proto__ / constructor\n// key pollution on arrays. Go maps have no prototype chain so object-form\n// __proto__ is simply stored as a key, but list-property syntax still\n// needs the guard to prevent callers who build arrays from user input\n// from exposing constructor-shaped keys in unsafe downstream code.\n\n// --- constructor key ---\n\nfunc TestSafeKey_ConstructorBlockedOnList(t *testing.T) {\n\tj := Make(Options{List: &ListOptions{Property: boolPtr(true)}})\n\tgot, err := j.Parse(\"[1,2,constructor:fail]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := a(1.0, 2.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"constructor pair on list should be dropped: got %s, want %s\",\n\t\t\tformatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\nfunc TestSafeKey_ConstructorAllowedOnObject(t *testing.T) {\n\t// Go maps have no prototype, so constructor on an object is just a key.\n\tj := Make()\n\tgot, err := j.Parse(\"{constructor:1,a:2}\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := m(\"constructor\", 1.0, \"a\", 2.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"got %s, want %s\", formatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n\n// --- safe.key=false + list property allows pollution ---\n\nfunc TestSafeKey_FalseAllowsProtoOnList(t *testing.T) {\n\tj := Make(Options{\n\t\tSafe: &SafeOptions{Key: boolPtr(false)},\n\t\tList: &ListOptions{Property: boolPtr(true)},\n\t})\n\tgot, err := j.Parse(\"[1,2,__proto__:fail]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// With safe.key=false the __proto__ pair survives — observable as an\n\t// extra element in the slice because Go lists carry key-value pairs\n\t// under list.property syntax only via the implicit map grammar path.\n\t// The exact survival shape depends on how list.property renders the\n\t// pair; the guarantee is: it is NOT dropped.\n\tarr, ok := stripRefs(got).([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected []any, got %T: %v\", got, got)\n\t}\n\tif len(arr) < 2 {\n\t\tt.Errorf(\"expected __proto__ pair to survive, got %v\", arr)\n\t}\n}\n\nfunc TestSafeKey_FalseAllowsConstructorOnList(t *testing.T) {\n\tj := Make(Options{\n\t\tSafe: &SafeOptions{Key: boolPtr(false)},\n\t\tList: &ListOptions{Property: boolPtr(true)},\n\t})\n\tgot, err := j.Parse(\"[1,2,constructor:fail]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tarr, ok := stripRefs(got).([]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected []any, got %T: %v\", got, got)\n\t}\n\tif len(arr) < 2 {\n\t\tt.Errorf(\"expected constructor pair to survive, got %v\", arr)\n\t}\n}\n\n// --- path diving with __proto__ ---\n\nfunc TestSafeKey_PathDiveWithProtoOnList(t *testing.T) {\n\t// With list.property and safe.key default (true), a path-dive key that\n\t// starts with __proto__ must not establish a __proto__ child entry.\n\tj := Make(Options{List: &ListOptions{Property: boolPtr(true)}})\n\tgot, err := j.Parse(\"[1,2,__proto__:x:1]\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpected := a(1.0, 2.0)\n\tif !valuesEqual(stripRefs(got), expected) {\n\t\tt.Errorf(\"path-dived __proto__ should be dropped: got %s, want %s\",\n\t\t\tformatValue(stripRefs(got)), formatValue(expected))\n\t}\n}\n"
  },
  {
    "path": "go/text.go",
    "content": "package jsonic\n\n// Text represents a string value with metadata about how it was quoted\n// in the source. When the TextInfo option is enabled, string and text\n// values in the output are wrapped in Text instead of plain strings.\ntype Text struct {\n\t// Quote is the quote character used in the source.\n\t// For quoted strings: `\"`, `'`, or \"`\".\n\t// For unquoted text: \"\" (empty string).\n\tQuote string\n\n\t// Str is the actual string value (with escapes processed for quoted strings).\n\tStr string\n}\n\n// ListRef wraps a list value with metadata about how it was created.\n// When the ListRef option is enabled, list values in the output are\n// returned as ListRef instead of plain []any slices.\n// ListRef is created early (in the BO phase) so that custom parsers\n// can store additional information in the Meta map during parsing.\ntype ListRef struct {\n\t// Val is the list contents.\n\tVal []any\n\n\t// Implicit is true when the list was created implicitly\n\t// (e.g. comma-separated or space-separated values without brackets),\n\t// and false when brackets were used explicitly.\n\tImplicit bool\n\n\t// Child is the optional child value set by bare colon syntax (:value)\n\t// inside a list. Enabled by the List.Child option.\n\t// For example, `[:1, a, b]` produces Val=[a, b] with Child=1.\n\t// Multiple child values are merged (deep merge if Map.Extend is true).\n\t// Nil when no child value is present.\n\tChild any\n\n\t// Meta is a map for custom parsers to attach additional information\n\t// during parsing. It is initialized when the ListRef is created in\n\t// the BO (before-open) phase.\n\tMeta map[string]any\n}\n\n// MapRef wraps a map value with metadata about how it was created.\n// When the MapRef option is enabled, map values in the output are\n// returned as MapRef instead of plain map[string]any.\n// MapRef is created early (in the BO phase) so that custom parsers\n// can store additional information in the Meta map during parsing.\ntype MapRef struct {\n\t// Val is the map contents.\n\tVal map[string]any\n\n\t// Implicit is true when the map was created implicitly\n\t// (e.g. key:value pairs without braces),\n\t// and false when braces were used explicitly.\n\tImplicit bool\n\n\t// Meta is a map for custom parsers to attach additional information\n\t// during parsing. It is initialized when the MapRef is created in\n\t// the BO (before-open) phase.\n\tMeta map[string]any\n}\n"
  },
  {
    "path": "go/textinfo_test.go",
    "content": "package jsonic\n\nimport (\n\t\"testing\"\n)\n\n// expectTextInfo parses input with TextInfo enabled and checks the result.\nfunc expectTextInfo(t *testing.T, input string, expected any) {\n\tt.Helper()\n\tj := Make(Options{Info: &InfoOptions{Text: boolPtr(true)}})\n\tgot, err := j.Parse(input)\n\tif err != nil {\n\t\tt.Errorf(\"Parse(%q) unexpected error: %v\", input, err)\n\t\treturn\n\t}\n\tif !textInfoEqual(got, expected) {\n\t\tt.Errorf(\"Parse(%q)\\n  got:      %#v\\n  expected: %#v\",\n\t\t\tinput, got, expected)\n\t}\n}\n\n// textInfoEqual compares values including Text structs.\nfunc textInfoEqual(a, b any) bool {\n\tif a == nil && b == nil {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\n\tswitch av := a.(type) {\n\tcase Text:\n\t\tbv, ok := b.(Text)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn av.Quote == bv.Quote && av.Str == bv.Str\n\tcase map[string]any:\n\t\tbv, ok := b.(map[string]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor k, v := range av {\n\t\t\tbval, exists := bv[k]\n\t\t\tif !exists || !textInfoEqual(v, bval) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase []any:\n\t\tbv, ok := b.([]any)\n\t\tif !ok || len(av) != len(bv) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range av {\n\t\t\tif !textInfoEqual(av[i], bv[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tcase float64:\n\t\tbv, ok := b.(float64)\n\t\treturn ok && av == bv\n\tcase bool:\n\t\tbv, ok := b.(bool)\n\t\treturn ok && av == bv\n\tcase string:\n\t\tbv, ok := b.(string)\n\t\treturn ok && av == bv\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// tx is shorthand to create a Text value.\nfunc tx(quote, str string) Text {\n\treturn Text{Quote: quote, Str: str}\n}\n\nfunc TestTextInfoOff(t *testing.T) {\n\t// Default (TextInfo off) - plain strings in output, no wrapping.\n\tj := Make()\n\tgot, err := j.Parse(`\"hello\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif s, ok := got.(string); !ok || s != \"hello\" {\n\t\tt.Errorf(\"expected plain string \\\"hello\\\", got %#v\", got)\n\t}\n\n\tgot, err = j.Parse(\"hello\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif s, ok := got.(string); !ok || s != \"hello\" {\n\t\tt.Errorf(\"expected plain string \\\"hello\\\", got %#v\", got)\n\t}\n}\n\nfunc TestTextInfoDoubleQuote(t *testing.T) {\n\texpectTextInfo(t, `\"hello\"`, tx(`\"`, \"hello\"))\n}\n\nfunc TestTextInfoSingleQuote(t *testing.T) {\n\texpectTextInfo(t, `'hello'`, tx(\"'\", \"hello\"))\n}\n\nfunc TestTextInfoBacktickQuote(t *testing.T) {\n\texpectTextInfo(t, \"`hello`\", tx(\"`\", \"hello\"))\n}\n\nfunc TestTextInfoUnquotedText(t *testing.T) {\n\t// Unquoted text has empty quote.\n\texpectTextInfo(t, \"hello\", tx(\"\", \"hello\"))\n}\n\nfunc TestTextInfoEmptyStrings(t *testing.T) {\n\texpectTextInfo(t, `\"\"`, tx(`\"`, \"\"))\n\texpectTextInfo(t, `''`, tx(\"'\", \"\"))\n\texpectTextInfo(t, \"``\", tx(\"`\", \"\"))\n}\n\nfunc TestTextInfoEscapes(t *testing.T) {\n\t// Escapes should still be processed; Str holds the processed value.\n\texpectTextInfo(t, `\"a\\tb\"`, tx(`\"`, \"a\\tb\"))\n\texpectTextInfo(t, `'a\\nb'`, tx(\"'\", \"a\\nb\"))\n}\n\nfunc TestTextInfoMapValues(t *testing.T) {\n\t// Values in maps should be wrapped; keys remain plain strings.\n\texpectTextInfo(t, `a:\"hello\"`, map[string]any{\n\t\t\"a\": tx(`\"`, \"hello\"),\n\t})\n\texpectTextInfo(t, `a:'hello'`, map[string]any{\n\t\t\"a\": tx(\"'\", \"hello\"),\n\t})\n\texpectTextInfo(t, \"a:hello\", map[string]any{\n\t\t\"a\": tx(\"\", \"hello\"),\n\t})\n}\n\nfunc TestTextInfoMapMultipleKeys(t *testing.T) {\n\texpectTextInfo(t, `a:\"x\",b:'y'`, map[string]any{\n\t\t\"a\": tx(`\"`, \"x\"),\n\t\t\"b\": tx(\"'\", \"y\"),\n\t})\n}\n\nfunc TestTextInfoArrayValues(t *testing.T) {\n\texpectTextInfo(t, `[\"a\",'b',c]`, []any{\n\t\ttx(`\"`, \"a\"),\n\t\ttx(\"'\", \"b\"),\n\t\ttx(\"\", \"c\"),\n\t})\n}\n\nfunc TestTextInfoMixedTypes(t *testing.T) {\n\t// Numbers, booleans, and null should not be wrapped.\n\texpectTextInfo(t, `[\"hello\",1,true,null]`, []any{\n\t\ttx(`\"`, \"hello\"),\n\t\t1.0,\n\t\ttrue,\n\t\tnil,\n\t})\n}\n\nfunc TestTextInfoNestedMap(t *testing.T) {\n\texpectTextInfo(t, `{a:{b:\"c\"}}`, map[string]any{\n\t\t\"a\": map[string]any{\n\t\t\t\"b\": tx(`\"`, \"c\"),\n\t\t},\n\t})\n}\n\nfunc TestTextInfoKeysRemainStrings(t *testing.T) {\n\t// Even with TextInfo on, map keys must be plain strings.\n\tj := Make(Options{Info: &InfoOptions{Text: boolPtr(true)}})\n\tgot, err := j.Parse(`\"k\":\"v\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm, ok := got.(map[string]any)\n\tif !ok {\n\t\tt.Fatalf(\"expected map, got %#v\", got)\n\t}\n\t// Key should be plain string \"k\".\n\tif _, exists := m[\"k\"]; !exists {\n\t\tt.Errorf(\"expected key \\\"k\\\" in map, got keys: %v\", m)\n\t}\n\t// Value should be Text.\n\tval, ok := m[\"k\"].(Text)\n\tif !ok {\n\t\tt.Errorf(\"expected Text value, got %#v\", m[\"k\"])\n\t} else if val.Quote != `\"` || val.Str != \"v\" {\n\t\tt.Errorf(\"expected Text{Quote:`\\\"`, Str:\\\"v\\\"}, got %#v\", val)\n\t}\n}\n\nfunc TestTextInfoNumbersUnaffected(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Text: boolPtr(true)}})\n\tgot, err := j.Parse(\"42\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif f, ok := got.(float64); !ok || f != 42.0 {\n\t\tt.Errorf(\"expected 42.0, got %#v\", got)\n\t}\n}\n\nfunc TestTextInfoBoolsUnaffected(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Text: boolPtr(true)}})\n\tgot, err := j.Parse(\"true\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif b, ok := got.(bool); !ok || b != true {\n\t\tt.Errorf(\"expected true, got %#v\", got)\n\t}\n}\n\nfunc TestTextInfoNullUnaffected(t *testing.T) {\n\tj := Make(Options{Info: &InfoOptions{Text: boolPtr(true)}})\n\tgot, err := j.Parse(\"null\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expected nil, got %#v\", got)\n\t}\n}\n\nfunc TestTextInfoExplicitOff(t *testing.T) {\n\t// Explicitly setting TextInfo to false should behave like default.\n\tj := Make(Options{Info: &InfoOptions{Text: boolPtr(false)}})\n\tgot, err := j.Parse(`\"hello\"`)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif s, ok := got.(string); !ok || s != \"hello\" {\n\t\tt.Errorf(\"expected plain string \\\"hello\\\", got %#v\", got)\n\t}\n}\n\nfunc TestTextInfoImplicitList(t *testing.T) {\n\t// Implicit list (comma-separated values).\n\texpectTextInfo(t, `\"a\",\"b\"`, []any{\n\t\ttx(`\"`, \"a\"),\n\t\ttx(`\"`, \"b\"),\n\t})\n}\n\nfunc TestTextInfoSpaceSeparatedList(t *testing.T) {\n\t// Space-separated implicit list.\n\texpectTextInfo(t, \"a b c\", []any{\n\t\ttx(\"\", \"a\"),\n\t\ttx(\"\", \"b\"),\n\t\ttx(\"\", \"c\"),\n\t})\n}\n\nfunc TestTextInfoPathDive(t *testing.T) {\n\texpectTextInfo(t, `a:b:\"c\"`, map[string]any{\n\t\t\"a\": map[string]any{\n\t\t\t\"b\": tx(`\"`, \"c\"),\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "go/token.go",
    "content": "package jsonic\n\n// Tin is a token identification number.\ntype Tin = int\n\n// Standard token Tins - assigned in order matching the TypeScript implementation.\nconst (\n\tTinBD  Tin = 1  // #BD - BAD\n\tTinZZ  Tin = 2  // #ZZ - END\n\tTinUK  Tin = 3  // #UK - UNKNOWN\n\tTinAA  Tin = 4  // #AA - ANY\n\tTinSP  Tin = 5  // #SP - SPACE\n\tTinLN  Tin = 6  // #LN - LINE\n\tTinCM  Tin = 7  // #CM - COMMENT\n\tTinNR  Tin = 8  // #NR - NUMBER\n\tTinST  Tin = 9  // #ST - STRING\n\tTinTX  Tin = 10 // #TX - TEXT\n\tTinVL  Tin = 11 // #VL - VALUE (true, false, null)\n\tTinOB  Tin = 12 // #OB - Open Brace {\n\tTinCB  Tin = 13 // #CB - Close Brace }\n\tTinOS  Tin = 14 // #OS - Open Square [\n\tTinCS  Tin = 15 // #CS - Close Square ]\n\tTinCL  Tin = 16 // #CL - Colon :\n\tTinCA  Tin = 17 // #CA - Comma ,\n\tTinMAX Tin = 18 // Next available Tin\n)\n\n// Token set constants\nvar (\n\t// IGNORE tokens: space, line, comment\n\tTinSetIGNORE = map[Tin]bool{TinSP: true, TinLN: true, TinCM: true}\n\t// VAL tokens: text, number, string, value\n\tTinSetVAL = []Tin{TinTX, TinNR, TinST, TinVL}\n\t// KEY tokens: text, number, string, value (same as VAL)\n\tTinSetKEY = []Tin{TinTX, TinNR, TinST, TinVL}\n)\n\n// Point tracks position in source text.\ntype Point struct {\n\tLen int // Source length\n\tSI  int // String index (0-based)\n\tRI  int // Row index (1-based)\n\tCI  int // Column index (1-based)\n}\n\n// Token represents a lexical token.\ntype Token struct {\n\tName string         // Token name (#OB, #ST, etc.)\n\tTin  Tin            // Token identification number\n\tVal  any            // Resolved value\n\tSrc  string         // Source text\n\tSI   int            // Start position\n\tRI   int            // Row\n\tCI   int            // Column\n\tErr  string         // Error code\n\tWhy  string         // Tracing/reason\n\tUse  map[string]any // Custom plugin metadata (TS: token.use)\n}\n\n// Bad converts this token to an error token with the given error code.\n// Matches TS token.bad(err, details).\nfunc (t *Token) Bad(err string, details ...map[string]any) *Token {\n\tt.Err = err\n\tif len(details) > 0 && details[0] != nil {\n\t\tif t.Use == nil {\n\t\t\tt.Use = make(map[string]any)\n\t\t}\n\t\tfor k, v := range details[0] {\n\t\t\tt.Use[k] = v\n\t\t}\n\t}\n\treturn t\n}\n\n// IsNoToken returns true if this is a sentinel/empty token.\nfunc (t *Token) IsNoToken() bool {\n\treturn t.Tin == -1\n}\n\n// TokenValFunc is the signature for a lazy token value. When Token.Val is\n// set to a TokenValFunc, ResolveVal invokes it with the current rule and\n// context to produce the value at parse time. Mirrors TS src/lexer.ts:115.\ntype TokenValFunc func(r *Rule, ctx *Context) any\n\n// ResolveVal returns the token's value. If Val is a TokenValFunc, it is\n// invoked with the current rule and context; otherwise Val is returned as-is.\n// The (r, ctx) arguments may be nil when the caller has no rule/context.\nfunc (t *Token) ResolveVal(r *Rule, ctx *Context) any {\n\tif fn, ok := t.Val.(TokenValFunc); ok {\n\t\treturn fn(r, ctx)\n\t}\n\treturn t.Val\n}\n\n// MakeToken creates a new Token.\nfunc MakeToken(name string, tin Tin, val any, src string, pnt Point) *Token {\n\treturn &Token{\n\t\tName: name,\n\t\tTin:  tin,\n\t\tVal:  val,\n\t\tSrc:  src,\n\t\tSI:   pnt.SI,\n\t\tRI:   pnt.RI,\n\t\tCI:   pnt.CI,\n\t}\n}\n\n// NoToken is a sentinel token indicating \"no token\".\nvar NoToken = &Token{Name: \"\", Tin: -1, SI: -1, RI: -1, CI: -1}\n\n// Fixed token source map: character -> Tin\nvar FixedTokens = map[string]Tin{\n\t\"{\": TinOB,\n\t\"}\": TinCB,\n\t\"[\": TinOS,\n\t\"]\": TinCS,\n\t\":\": TinCL,\n\t\",\": TinCA,\n}\n"
  },
  {
    "path": "go/token_test.go",
    "content": "package jsonic\n\nimport \"testing\"\n\n// Mirrors TS src/lexer.ts:115 — when Token.Val is a function, ResolveVal\n// invokes it with (rule, ctx) rather than returning the function itself.\n\nfunc TestResolveVal_StaticValue(t *testing.T) {\n\ttk := &Token{Val: 42}\n\tif got := tk.ResolveVal(nil, nil); got != 42 {\n\t\tt.Errorf(\"expected 42, got %v\", got)\n\t}\n}\n\nfunc TestResolveVal_NilValueUnchanged(t *testing.T) {\n\ttk := &Token{Val: nil}\n\tif got := tk.ResolveVal(nil, nil); got != nil {\n\t\tt.Errorf(\"expected nil, got %v\", got)\n\t}\n}\n\nfunc TestResolveVal_LazyFunction(t *testing.T) {\n\tcalls := 0\n\ttk := &Token{Val: TokenValFunc(func(r *Rule, ctx *Context) any {\n\t\tcalls++\n\t\treturn \"lazy\"\n\t})}\n\tif got := tk.ResolveVal(nil, nil); got != \"lazy\" {\n\t\tt.Errorf(\"expected 'lazy', got %v\", got)\n\t}\n\tif calls != 1 {\n\t\tt.Errorf(\"expected 1 call, got %d\", calls)\n\t}\n}\n\nfunc TestResolveVal_LazyFunctionReceivesRuleAndCtx(t *testing.T) {\n\tvar seenRule *Rule\n\tvar seenCtx *Context\n\ttk := &Token{Val: TokenValFunc(func(r *Rule, ctx *Context) any {\n\t\tseenRule = r\n\t\tseenCtx = ctx\n\t\treturn nil\n\t})}\n\trule := &Rule{}\n\tctx := &Context{}\n\ttk.ResolveVal(rule, ctx)\n\tif seenRule != rule {\n\t\tt.Errorf(\"rule not forwarded: got %p want %p\", seenRule, rule)\n\t}\n\tif seenCtx != ctx {\n\t\tt.Errorf(\"ctx not forwarded: got %p want %p\", seenCtx, ctx)\n\t}\n}\n\n// Integration: a custom matcher emits a Token whose Val is a TokenValFunc.\n// The parser must call it at resolution time, not store the function.\nfunc TestResolveVal_LazyValueThroughParser(t *testing.T) {\n\tmatcher := LexMatcher(func(lex *Lex, _ *Rule) *Token {\n\t\tif lex.pnt.SI >= len(lex.Src) || lex.Src[lex.pnt.SI] != '@' {\n\t\t\treturn nil\n\t\t}\n\t\tstart := lex.pnt.SI\n\t\tlex.pnt.SI++\n\t\treturn &Token{\n\t\t\tName: \"#TX\",\n\t\t\tTin:  TinTX,\n\t\t\tSrc:  lex.Src[start:lex.pnt.SI],\n\t\t\tVal: TokenValFunc(func(r *Rule, ctx *Context) any {\n\t\t\t\treturn \"resolved\"\n\t\t\t}),\n\t\t\tSI: start,\n\t\t\tRI: lex.pnt.RI,\n\t\t\tCI: lex.pnt.CI,\n\t\t}\n\t})\n\n\tj := Make(Options{\n\t\tLex: &LexOptions{\n\t\t\tMatch: map[string]*MatchSpec{\n\t\t\t\t\"at\": {\n\t\t\t\t\tOrder: 1,\n\t\t\t\t\tMake: func(_ *LexConfig, _ *Options) LexMatcher {\n\t\t\t\t\t\treturn matcher\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tout, err := j.Parse(`@`)\n\tif err != nil {\n\t\tt.Fatalf(\"parse failed: %v\", err)\n\t}\n\tif out != \"resolved\" {\n\t\tt.Errorf(\"expected 'resolved', got %v\", out)\n\t}\n}\n"
  },
  {
    "path": "go/util.go",
    "content": "package jsonic\n\nimport \"sort\"\n\n// Util exposes helper functions that plugins commonly need, mirroring\n// TS jsonic.util (src/jsonic.ts:109). The fields point at package-level\n// functions so plugin authors porting from TS can keep calls shaped like\n// `j.Util().Deep(a, b)` instead of re-importing each helper.\ntype UtilBag struct {\n\tDeep       func(base any, rest ...any) any\n\tKeys       func(m map[string]any) []string\n\tValues     func(m map[string]any) []any\n\tEntries    func(m map[string]any) []Entry\n\tOmap       func(m map[string]any, fn func(Entry) []any) map[string]any\n\tStr        func(val any, maxlen int) string\n\tStrInject  func(template string, vals any) string\n}\n\n// Entry is a (key, value) pair returned by Entries. Matches the 2-tuple\n// shape of TS Object.entries(), but as a struct for Go ergonomics.\ntype Entry struct {\n\tKey   string\n\tValue any\n}\n\n// Keys returns the map's keys in sorted order. Matches TS Object.keys()\n// with null-safety — a nil map returns an empty slice.\nfunc Keys(m map[string]any) []string {\n\tif m == nil {\n\t\treturn []string{}\n\t}\n\tout := make([]string, 0, len(m))\n\tfor k := range m {\n\t\tout = append(out, k)\n\t}\n\tsort.Strings(out)\n\treturn out\n}\n\n// Values returns the map's values in key-sorted order. Matches TS\n// Object.values() with null-safety.\nfunc Values(m map[string]any) []any {\n\tif m == nil {\n\t\treturn []any{}\n\t}\n\tout := make([]any, 0, len(m))\n\tfor _, k := range Keys(m) {\n\t\tout = append(out, m[k])\n\t}\n\treturn out\n}\n\n// Entries returns (key, value) pairs in key-sorted order. Matches TS\n// Object.entries() with null-safety.\nfunc Entries(m map[string]any) []Entry {\n\tif m == nil {\n\t\treturn []Entry{}\n\t}\n\tout := make([]Entry, 0, len(m))\n\tfor _, k := range Keys(m) {\n\t\tout = append(out, Entry{Key: k, Value: m[k]})\n\t}\n\treturn out\n}\n\n// Omap maps over an object's entries. For each entry, fn returns a slice\n// of alternating key/value items: `[k, v]` renames/rewrites the pair;\n// `[k, v, k2, v2, ...]` adds extra keys; `[nil, _]` drops the entry.\n// Mirrors TS omap (src/utility.ts:44).\nfunc Omap(m map[string]any, fn func(Entry) []any) map[string]any {\n\tout := map[string]any{}\n\tif m == nil {\n\t\treturn out\n\t}\n\tfor _, e := range Entries(m) {\n\t\tvar me []any\n\t\tif fn != nil {\n\t\t\tme = fn(e)\n\t\t} else {\n\t\t\tme = []any{e.Key, e.Value}\n\t\t}\n\t\tif len(me) < 2 || me[0] == nil {\n\t\t\t// drop\n\t\t} else if k, ok := me[0].(string); ok {\n\t\t\tout[k] = me[1]\n\t\t}\n\t\ti := 2\n\t\tfor i+1 < len(me) && me[i] != nil {\n\t\t\tif k, ok := me[i].(string); ok {\n\t\t\t\tout[k] = me[i+1]\n\t\t\t}\n\t\t\ti += 2\n\t\t}\n\t}\n\treturn out\n}\n\n// Util returns a UtilBag of helper functions. Matches TS jsonic.util.\n// The returned bag is a thin pointer wrapper over the package-level\n// helpers — safe to cache and call from any goroutine.\nfunc (j *Jsonic) Util() UtilBag {\n\treturn UtilBag{\n\t\tDeep:      Deep,\n\t\tKeys:      Keys,\n\t\tValues:    Values,\n\t\tEntries:   Entries,\n\t\tOmap:      Omap,\n\t\tStr:       Str,\n\t\tStrInject: StrInject,\n\t}\n}\n"
  },
  {
    "path": "go/util_test.go",
    "content": "package jsonic\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\n// Tests for the Util bag and its helpers. Mirrors the TS util functions\n// (src/utility.ts: keys, values, entries, omap) under TS jsonic.util.\n\nfunc TestKeys_NilMapReturnsEmpty(t *testing.T) {\n\tif got := Keys(nil); !reflect.DeepEqual(got, []string{}) {\n\t\tt.Errorf(\"expected [], got %v\", got)\n\t}\n}\n\nfunc TestKeys_Sorted(t *testing.T) {\n\tm := map[string]any{\"b\": 2, \"a\": 1, \"c\": 3}\n\tif got := Keys(m); !reflect.DeepEqual(got, []string{\"a\", \"b\", \"c\"}) {\n\t\tt.Errorf(\"expected sorted, got %v\", got)\n\t}\n}\n\nfunc TestValues_NilMapReturnsEmpty(t *testing.T) {\n\tif got := Values(nil); !reflect.DeepEqual(got, []any{}) {\n\t\tt.Errorf(\"expected [], got %v\", got)\n\t}\n}\n\nfunc TestValues_KeySortedOrder(t *testing.T) {\n\tm := map[string]any{\"b\": 2, \"a\": 1, \"c\": 3}\n\tif got := Values(m); !reflect.DeepEqual(got, []any{1, 2, 3}) {\n\t\tt.Errorf(\"expected [1 2 3], got %v\", got)\n\t}\n}\n\nfunc TestEntries_NilMapReturnsEmpty(t *testing.T) {\n\tif got := Entries(nil); !reflect.DeepEqual(got, []Entry{}) {\n\t\tt.Errorf(\"expected [], got %v\", got)\n\t}\n}\n\nfunc TestEntries_KeySortedOrder(t *testing.T) {\n\tm := map[string]any{\"b\": 2, \"a\": 1}\n\tgot := Entries(m)\n\twant := []Entry{{Key: \"a\", Value: 1}, {Key: \"b\", Value: 2}}\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"expected %v, got %v\", want, got)\n\t}\n}\n\nfunc TestOmap_Identity(t *testing.T) {\n\tm := map[string]any{\"a\": 1, \"b\": 2}\n\tgot := Omap(m, nil)\n\tif !reflect.DeepEqual(got, m) {\n\t\tt.Errorf(\"identity omap: expected %v, got %v\", m, got)\n\t}\n}\n\nfunc TestOmap_Rewrite(t *testing.T) {\n\tm := map[string]any{\"a\": 1}\n\tgot := Omap(m, func(e Entry) []any {\n\t\treturn []any{e.Key + \"X\", e.Value.(int) * 10}\n\t})\n\tif !reflect.DeepEqual(got, map[string]any{\"aX\": 10}) {\n\t\tt.Errorf(\"expected rewritten map, got %v\", got)\n\t}\n}\n\nfunc TestOmap_Drop(t *testing.T) {\n\tm := map[string]any{\"a\": 1, \"b\": 2}\n\tgot := Omap(m, func(e Entry) []any {\n\t\tif e.Key == \"a\" {\n\t\t\treturn []any{nil, nil}\n\t\t}\n\t\treturn []any{e.Key, e.Value}\n\t})\n\tif !reflect.DeepEqual(got, map[string]any{\"b\": 2}) {\n\t\tt.Errorf(\"expected b-only map, got %v\", got)\n\t}\n}\n\nfunc TestOmap_ExtraPairs(t *testing.T) {\n\tm := map[string]any{\"a\": 1}\n\tgot := Omap(m, func(e Entry) []any {\n\t\treturn []any{e.Key, e.Value, \"extra\", 99}\n\t})\n\twant := map[string]any{\"a\": 1, \"extra\": 99}\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"expected %v, got %v\", want, got)\n\t}\n}\n\nfunc TestJsonic_Util_ExposesHelpers(t *testing.T) {\n\tj := Make()\n\tu := j.Util()\n\n\tif u.Deep == nil || u.Keys == nil || u.Values == nil ||\n\t\tu.Entries == nil || u.Omap == nil || u.Str == nil || u.StrInject == nil {\n\t\tt.Fatalf(\"Util bag has nil helper fields: %+v\", u)\n\t}\n\n\t// Sanity-check one of each, to ensure they're wired to the real functions.\n\tmerged := u.Deep(map[string]any{\"a\": 1}, map[string]any{\"b\": 2}).(map[string]any)\n\tif merged[\"a\"] != 1 || merged[\"b\"] != 2 {\n\t\tt.Errorf(\"Util().Deep failed: %v\", merged)\n\t}\n\tif ks := u.Keys(map[string]any{\"x\": 1}); !reflect.DeepEqual(ks, []string{\"x\"}) {\n\t\tt.Errorf(\"Util().Keys failed: %v\", ks)\n\t}\n}\n"
  },
  {
    "path": "go/utility.go",
    "content": "package jsonic\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Deep performs a recursive deep merge of multiple values.\n// Works on map[string]any, []any, and Go structs (via reflection),\n// matching the TypeScript deep() utility.\n// Zero/nil values in the overlay do not overwrite base values.\nfunc Deep(base any, rest ...any) any {\n\tfor _, over := range rest {\n\t\tbase = deepMerge(base, over)\n\t}\n\treturn base\n}\n\nfunc deepMerge(base, over any) any {\n\t// Match TS: undefined and Skip preserve base.\n\tif IsUndefined(over) || IsSkip(over) {\n\t\treturn base\n\t}\n\n\t// Extract maps from MapRef if present.\n\tbaseMap, baseIsMap := base.(map[string]any)\n\tbaseMR, baseIsMR := base.(MapRef)\n\tif baseIsMR {\n\t\tbaseMap = baseMR.Val\n\t\tbaseIsMap = true\n\t}\n\toverMap, overIsMap := over.(map[string]any)\n\toverMR, overIsMR := over.(MapRef)\n\tif overIsMR {\n\t\toverMap = overMR.Val\n\t\toverIsMap = true\n\t}\n\n\t// Extract arrays from ListRef if present.\n\tbaseArr, baseIsArr := base.([]any)\n\tbaseLR, baseIsLR := base.(ListRef)\n\tif baseIsLR {\n\t\tbaseArr = baseLR.Val\n\t\tbaseIsArr = true\n\t}\n\toverArr, overIsArr := over.([]any)\n\toverLR, overIsLR := over.(ListRef)\n\tif overIsLR {\n\t\toverArr = overLR.Val\n\t\toverIsArr = true\n\t}\n\n\tif baseIsMap && overIsMap {\n\t\t// Both maps: recursively merge\n\t\tresult := make(map[string]any)\n\t\tfor k, v := range baseMap {\n\t\t\tresult[k] = v\n\t\t}\n\t\tfor k, v := range overMap {\n\t\t\tif existing, ok := result[k]; ok {\n\t\t\t\tresult[k] = deepMerge(existing, v)\n\t\t\t} else {\n\t\t\t\tresult[k] = deepClone(v)\n\t\t\t}\n\t\t}\n\t\t// Preserve MapRef wrapper if the over value was a MapRef.\n\t\tif overIsMR {\n\t\t\tmeta := mergeMeta(baseMR.Meta, overMR.Meta)\n\t\t\treturn MapRef{Val: result, Implicit: overMR.Implicit, Meta: meta}\n\t\t}\n\t\treturn result\n\t}\n\n\tif baseIsArr && overIsArr {\n\t\t// Both arrays: recursively merge elements at same index\n\t\tmaxLen := len(baseArr)\n\t\tif len(overArr) > maxLen {\n\t\t\tmaxLen = len(overArr)\n\t\t}\n\t\tresult := make([]any, maxLen)\n\t\tfor i := 0; i < maxLen; i++ {\n\t\t\tif i < len(baseArr) && i < len(overArr) {\n\t\t\t\tresult[i] = deepMerge(deepClone(baseArr[i]), overArr[i])\n\t\t\t} else if i < len(overArr) {\n\t\t\t\tresult[i] = deepClone(overArr[i])\n\t\t\t} else if i < len(baseArr) {\n\t\t\t\tresult[i] = deepClone(baseArr[i])\n\t\t\t}\n\t\t}\n\t\t// Preserve ListRef wrapper if the over value was a ListRef.\n\t\tif overIsLR {\n\t\t\t// Merge Child fields if both are ListRef.\n\t\t\tvar child any\n\t\t\tif baseIsLR && baseLR.Child != nil && overLR.Child != nil {\n\t\t\t\tchild = deepMerge(baseLR.Child, overLR.Child)\n\t\t\t} else if overLR.Child != nil {\n\t\t\t\tchild = deepClone(overLR.Child)\n\t\t\t} else if baseIsLR {\n\t\t\t\tchild = deepClone(baseLR.Child)\n\t\t\t}\n\t\t\tmeta := mergeMeta(baseLR.Meta, overLR.Meta)\n\t\t\treturn ListRef{Val: result, Implicit: overLR.Implicit, Child: child, Meta: meta}\n\t\t}\n\t\treturn result\n\t}\n\n\t// Struct handling via reflection — matches TS deep() on plain objects.\n\tif merged, ok := deepMergeStruct(base, over); ok {\n\t\treturn merged\n\t}\n\n\t// Type mismatch or non-container: over wins\n\t// Match TS: undefined preserves base, null replaces.\n\tif IsUndefined(over) {\n\t\treturn base\n\t}\n\tif over == nil {\n\t\treturn nil\n\t}\n\treturn deepClone(over)\n}\n\n// deepMergeStruct merges two struct values field-by-field via reflection.\n// Zero-value fields in over do not overwrite base, matching TS deep() where\n// undefined properties are skipped. Returns (merged, true) if both values\n// are structs of the same type, or (nil, false) if not applicable.\nfunc deepMergeStruct(base, over any) (any, bool) {\n\tif base == nil || over == nil {\n\t\treturn nil, false\n\t}\n\n\tbv := reflect.ValueOf(base)\n\tov := reflect.ValueOf(over)\n\n\t// Unwrap pointers.\n\tbIsPtr := bv.Kind() == reflect.Ptr\n\toIsPtr := ov.Kind() == reflect.Ptr\n\n\tif bIsPtr && bv.IsNil() {\n\t\tif oIsPtr || ov.Kind() == reflect.Struct {\n\t\t\treturn over, true\n\t\t}\n\t\treturn nil, false\n\t}\n\tif oIsPtr && ov.IsNil() {\n\t\tif bIsPtr || bv.Kind() == reflect.Struct {\n\t\t\treturn base, true\n\t\t}\n\t\treturn nil, false\n\t}\n\n\tbElem := bv\n\toElem := ov\n\tif bIsPtr {\n\t\tbElem = bv.Elem()\n\t}\n\tif oIsPtr {\n\t\toElem = ov.Elem()\n\t}\n\n\tif bElem.Kind() != reflect.Struct || oElem.Kind() != reflect.Struct {\n\t\treturn nil, false\n\t}\n\tif bElem.Type() != oElem.Type() {\n\t\treturn nil, false\n\t}\n\n\tresult := reflect.New(bElem.Type()).Elem()\n\tfor i := 0; i < bElem.NumField(); i++ {\n\t\tbf := bElem.Field(i)\n\t\tof := oElem.Field(i)\n\n\t\tif !bf.CanInterface() {\n\t\t\t// Unexported field: keep base.\n\t\t\tcontinue\n\t\t}\n\n\t\tif of.IsZero() {\n\t\t\tresult.Field(i).Set(bf)\n\t\t\tcontinue\n\t\t}\n\t\tif bf.IsZero() {\n\t\t\tresult.Field(i).Set(of)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Both non-zero: merge based on kind.\n\t\tswitch of.Kind() {\n\t\tcase reflect.Ptr:\n\t\t\tif of.Elem().Kind() == reflect.Struct {\n\t\t\t\t// Pointer to struct: recurse.\n\t\t\t\tmerged, ok := deepMergeStruct(bf.Interface(), of.Interface())\n\t\t\t\tif ok {\n\t\t\t\t\tresult.Field(i).Set(reflect.ValueOf(merged))\n\t\t\t\t} else {\n\t\t\t\t\tresult.Field(i).Set(of)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Pointer to primitive (*bool, *int): over wins.\n\t\t\t\tresult.Field(i).Set(of)\n\t\t\t}\n\t\tcase reflect.Map:\n\t\t\t// Merge map entries: base first, then over overwrites.\n\t\t\tmerged := reflect.MakeMap(bf.Type())\n\t\t\tfor _, k := range bf.MapKeys() {\n\t\t\t\tmerged.SetMapIndex(k, bf.MapIndex(k))\n\t\t\t}\n\t\t\tfor _, k := range of.MapKeys() {\n\t\t\t\tmerged.SetMapIndex(k, of.MapIndex(k))\n\t\t\t}\n\t\t\tresult.Field(i).Set(merged)\n\t\tdefault:\n\t\t\t// String, slice, func, etc.: over wins.\n\t\t\tresult.Field(i).Set(of)\n\t\t}\n\t}\n\n\t// Return with same pointer wrapping as inputs.\n\tif bIsPtr || oIsPtr {\n\t\tptr := reflect.New(result.Type())\n\t\tptr.Elem().Set(result)\n\t\treturn ptr.Interface(), true\n\t}\n\treturn result.Interface(), true\n}\n\n// cloneMeta creates a shallow copy of a Meta map.\nfunc cloneMeta(meta map[string]any) map[string]any {\n\tif meta == nil {\n\t\treturn nil\n\t}\n\tresult := make(map[string]any, len(meta))\n\tfor k, v := range meta {\n\t\tresult[k] = v\n\t}\n\treturn result\n}\n\n// mergeMeta merges two Meta maps. The over map's values take precedence.\nfunc mergeMeta(base, over map[string]any) map[string]any {\n\tif base == nil && over == nil {\n\t\treturn nil\n\t}\n\tresult := make(map[string]any)\n\tfor k, v := range base {\n\t\tresult[k] = v\n\t}\n\tfor k, v := range over {\n\t\tresult[k] = v\n\t}\n\treturn result\n}\n\n// deepClone creates a deep copy of a value.\nfunc deepClone(val any) any {\n\tif val == nil {\n\t\treturn nil\n\t}\n\tswitch v := val.(type) {\n\tcase map[string]any:\n\t\tresult := make(map[string]any)\n\t\tfor k, val := range v {\n\t\t\tresult[k] = deepClone(val)\n\t\t}\n\t\treturn result\n\tcase []any:\n\t\tresult := make([]any, len(v))\n\t\tfor i, val := range v {\n\t\t\tresult[i] = deepClone(val)\n\t\t}\n\t\treturn result\n\tcase ListRef:\n\t\tresult := make([]any, len(v.Val))\n\t\tfor i, val := range v.Val {\n\t\t\tresult[i] = deepClone(val)\n\t\t}\n\t\treturn ListRef{Val: result, Implicit: v.Implicit, Child: deepClone(v.Child), Meta: cloneMeta(v.Meta)}\n\tcase MapRef:\n\t\tresult := make(map[string]any)\n\t\tfor k, val := range v.Val {\n\t\t\tresult[k] = deepClone(val)\n\t\t}\n\t\treturn MapRef{Val: result, Implicit: v.Implicit, Meta: cloneMeta(v.Meta)}\n\tdefault:\n\t\treturn v\n\t}\n}\n\n// Snip returns the first maxlen characters of s, replacing \\r, \\n, \\t with '.'.\n// Matches the TypeScript snip() utility used for debug/display output.\nfunc Snip(s string, maxlen int) string {\n\tif maxlen <= 0 {\n\t\treturn \"\"\n\t}\n\tif len(s) > maxlen {\n\t\ts = s[:maxlen]\n\t}\n\treturn strings.NewReplacer(\"\\r\", \".\", \"\\n\", \".\", \"\\t\", \".\").Replace(s)\n}\n\n// Str converts a value to a truncated string representation.\n// If maxlen is <= 0, returns empty string.\n// If the string representation exceeds maxlen, it is truncated with \"...\" appended.\n// Matches the TypeScript str() + snip() pipeline: converts to string, truncates,\n// then replaces \\r\\n\\t with '.'.\nfunc Str(val any, maxlen int) string {\n\tif maxlen <= 0 {\n\t\treturn \"\"\n\t}\n\n\tvar s string\n\tswitch v := val.(type) {\n\tcase string:\n\t\ts = v\n\tcase float64:\n\t\tif v == float64(int64(v)) {\n\t\t\ts = strconv.FormatInt(int64(v), 10)\n\t\t} else {\n\t\t\ts = strconv.FormatFloat(v, 'f', -1, 64)\n\t\t}\n\tcase bool:\n\t\tif v {\n\t\t\ts = \"true\"\n\t\t} else {\n\t\t\ts = \"false\"\n\t\t}\n\tcase nil:\n\t\t// Match TS: JSON.stringify(null) === \"null\"\n\t\ts = \"null\"\n\tdefault:\n\t\tb, err := json.Marshal(val)\n\t\tif err != nil {\n\t\t\ts = fmt.Sprintf(\"%v\", val)\n\t\t} else {\n\t\t\ts = string(b)\n\t\t}\n\t}\n\n\tif len(s) > maxlen {\n\t\tif maxlen >= 4 {\n\t\t\ts = s[:maxlen-3] + \"...\"\n\t\t} else {\n\t\t\ts = \"...\"[:maxlen]\n\t\t}\n\t}\n\n\t// Match TS: str() calls snip() which replaces \\r\\n\\t with '.'\n\treturn Snip(s, maxlen)\n}\n\n// StrInject replaces template placeholders like {key} or {key.subkey} in a\n// template string with values from a map or array.\n// Returns the template unchanged if vals is not a map or array.\nfunc StrInject(template string, vals any) string {\n\tif template == \"\" {\n\t\treturn \"\"\n\t}\n\n\tvalsMap, isMap := vals.(map[string]any)\n\tvalsArr, isArr := vals.([]any)\n\n\tif !isMap && !isArr {\n\t\treturn template\n\t}\n\n\tvar result strings.Builder\n\trunes := []rune(template)\n\ti := 0\n\tfor i < len(runes) {\n\t\tif runes[i] == '{' {\n\t\t\t// Find closing brace\n\t\t\tj := i + 1\n\t\t\tfor j < len(runes) && runes[j] != '}' {\n\t\t\t\tj++\n\t\t\t}\n\t\t\tif j < len(runes) {\n\t\t\t\tpath := string(runes[i+1 : j])\n\t\t\t\t// Resolve path\n\t\t\t\tresolved, ok := resolvePath(path, valsMap, valsArr, isMap)\n\t\t\t\tif ok {\n\t\t\t\t\tresult.WriteString(formatInjectValue(resolved))\n\t\t\t\t} else {\n\t\t\t\t\t// Keep original placeholder\n\t\t\t\t\tresult.WriteString(string(runes[i : j+1]))\n\t\t\t\t}\n\t\t\t\ti = j + 1\n\t\t\t} else {\n\t\t\t\tresult.WriteRune(runes[i])\n\t\t\t\ti++\n\t\t\t}\n\t\t} else {\n\t\t\tresult.WriteRune(runes[i])\n\t\t\ti++\n\t\t}\n\t}\n\treturn result.String()\n}\n\n// resolvePath resolves a dotted path like \"a.b.0\" against a map or array.\nfunc resolvePath(path string, valsMap map[string]any, valsArr []any, isMap bool) (any, bool) {\n\tparts := strings.Split(path, \".\")\n\tvar current any\n\tif isMap {\n\t\tcurrent = any(valsMap)\n\t} else {\n\t\tcurrent = any(valsArr)\n\t}\n\n\tfor _, part := range parts {\n\t\tswitch c := current.(type) {\n\t\tcase map[string]any:\n\t\t\tv, ok := c[part]\n\t\t\tif !ok {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tcurrent = v\n\t\tcase []any:\n\t\t\tidx, err := strconv.Atoi(part)\n\t\t\tif err != nil || idx < 0 || idx >= len(c) {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t\tcurrent = c[idx]\n\t\tdefault:\n\t\t\treturn nil, false\n\t\t}\n\t}\n\treturn current, true\n}\n\n// formatInjectValue formats a value for injection into a template.\nfunc formatInjectValue(val any) string {\n\tswitch v := val.(type) {\n\tcase string:\n\t\treturn v\n\tcase float64:\n\t\tif v == float64(int64(v)) {\n\t\t\treturn strconv.FormatInt(int64(v), 10)\n\t\t}\n\t\treturn strconv.FormatFloat(v, 'f', -1, 64)\n\tcase bool:\n\t\tif v {\n\t\t\treturn \"true\"\n\t\t}\n\t\treturn \"false\"\n\tcase nil:\n\t\treturn \"null\"\n\tcase map[string]any:\n\t\treturn formatCompactValue(v)\n\tcase []any:\n\t\treturn formatCompactValue(v)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%v\", v)\n\t}\n}\n\n// formatCompactValue formats maps/arrays in a compact non-JSON format\n// similar to jsonic's output: {key:value} instead of {\"key\":\"value\"}.\nfunc formatCompactValue(val any) string {\n\tswitch v := val.(type) {\n\tcase map[string]any:\n\t\tvar sb strings.Builder\n\t\tsb.WriteRune('{')\n\t\tfirst := true\n\t\tfor k, v := range v {\n\t\t\tif !first {\n\t\t\t\tsb.WriteRune(',')\n\t\t\t}\n\t\t\tfirst = false\n\t\t\tsb.WriteString(k)\n\t\t\tsb.WriteRune(':')\n\t\t\tsb.WriteString(formatCompactValue(v))\n\t\t}\n\t\tsb.WriteRune('}')\n\t\treturn sb.String()\n\tcase []any:\n\t\tvar sb strings.Builder\n\t\tsb.WriteRune('[')\n\t\tfor i, v := range v {\n\t\t\tif i > 0 {\n\t\t\t\tsb.WriteRune(',')\n\t\t\t}\n\t\t\tsb.WriteString(formatCompactValue(v))\n\t\t}\n\t\tsb.WriteRune(']')\n\t\treturn sb.String()\n\tcase string:\n\t\treturn v\n\tcase float64:\n\t\tif v == float64(int64(v)) {\n\t\t\treturn strconv.FormatInt(int64(v), 10)\n\t\t}\n\t\treturn strconv.FormatFloat(v, 'f', -1, 64)\n\tcase bool:\n\t\tif v {\n\t\t\treturn \"true\"\n\t\t}\n\t\treturn \"false\"\n\tcase nil:\n\t\treturn \"null\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"%v\", v)\n\t}\n}\n\n// ModListOpts configures list modifications for ModList.\n// Matches the TypeScript ListMods type.\ntype ModListOpts struct {\n\tDelete []int                   // Indices to delete (supports negative indices).\n\tMove   []int                   // Pairs: [from, to, from, to, ...].\n\tCustom func(list []any) []any  // Custom modification callback, applied last.\n}\n\n// ModList modifies a list by applying delete, move, and custom operations.\n// Matches the TypeScript modlist() utility.\nfunc ModList(list []any, opts *ModListOpts) []any {\n\tif opts == nil || list == nil {\n\t\treturn list\n\t}\n\n\tif len(list) > 0 {\n\t\ttype sentinel struct{}\n\t\tdeleteMarker := sentinel{}\n\n\t\t// Phase 1: Mark elements for deletion (before move so indexes still make sense).\n\t\tif len(opts.Delete) > 0 {\n\t\t\tfor _, idx := range opts.Delete {\n\t\t\t\tn := len(list)\n\t\t\t\tif idx < 0 {\n\t\t\t\t\tif -idx <= n {\n\t\t\t\t\t\tdI := (n + idx) % n\n\t\t\t\t\t\tlist[dI] = deleteMarker\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif idx < n {\n\t\t\t\t\t\tlist[idx] = deleteMarker\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Phase 2: Move operations (on array with markers still present).\n\t\tif len(opts.Move) >= 2 {\n\t\t\tfor i := 0; i+1 < len(opts.Move); i += 2 {\n\t\t\t\tn := len(list)\n\t\t\t\tif n == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tfromI := ((opts.Move[i] % n) + n) % n\n\t\t\t\ttoI := ((opts.Move[i+1] % n) + n) % n\n\t\t\t\tentry := list[fromI]\n\t\t\t\tlist = append(list[:fromI], list[fromI+1:]...)\n\t\t\t\tnewList := make([]any, len(list)+1)\n\t\t\t\tcopy(newList, list[:toI])\n\t\t\t\tnewList[toI] = entry\n\t\t\t\tcopy(newList[toI+1:], list[toI:])\n\t\t\t\tlist = newList\n\t\t\t}\n\t\t}\n\n\t\t// Phase 3: Filter out deleted entries.\n\t\tif len(opts.Delete) > 0 {\n\t\t\tfiltered := make([]any, 0, len(list))\n\t\t\tfor _, v := range list {\n\t\t\t\tif _, ok := v.(sentinel); !ok {\n\t\t\t\t\tfiltered = append(filtered, v)\n\t\t\t\t}\n\t\t\t}\n\t\t\tlist = filtered\n\t\t}\n\t}\n\n\t// Phase 4: Custom modification (matches TS mods.custom).\n\tif opts.Custom != nil {\n\t\tif newList := opts.Custom(list); newList != nil {\n\t\t\tlist = newList\n\t\t}\n\t}\n\n\treturn list\n}\n\n// deepEqual compares two values for deep equality.\n// Used internally for testing.\nfunc deepEqual(a, b any) bool {\n\treturn reflect.DeepEqual(a, b)\n}\n\n// IsFuncRef checks if a string is a function reference (starts with \"@\").\nfunc IsFuncRef(s string) bool {\n\treturn len(s) > 0 && s[0] == '@'\n}\n\n// RequireRef looks up a FuncRef in the ref map and returns an error if not found.\nfunc RequireRef(ref map[FuncRef]any, name string, kind string) (any, error) {\n\tif ref == nil {\n\t\treturn nil, fmt.Errorf(\"Grammar: unknown %s function reference: %s (no ref map)\", kind, name)\n\t}\n\tfn, ok := ref[name]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"Grammar: unknown %s function reference: %s\", kind, name)\n\t}\n\treturn fn, nil\n}\n\n// LookupRef looks up a FuncRef in the ref map. Returns nil if not found.\nfunc LookupRef(ref map[FuncRef]any, name string) any {\n\tif ref == nil {\n\t\treturn nil\n\t}\n\treturn ref[name]\n}\n\n// MapToOptions converts a map[string]any (with resolved FuncRefs) to an Options struct.\n// Only handles fields that are commonly set via grammar options.\nfunc MapToOptions(m map[string]any) Options {\n\tvar opts Options\n\n\tif v, ok := m[\"tag\"].(string); ok {\n\t\topts.Tag = v\n\t}\n\n\t// safe\n\tif safe, ok := m[\"safe\"].(map[string]any); ok {\n\t\topts.Safe = &SafeOptions{}\n\t\tif key, ok := safe[\"key\"].(bool); ok {\n\t\t\topts.Safe.Key = &key\n\t\t}\n\t}\n\n\t// fixed\n\tif fm, ok := m[\"fixed\"].(map[string]any); ok {\n\t\topts.Fixed = &FixedOptions{}\n\t\tif lex, ok := fm[\"lex\"].(bool); ok {\n\t\t\topts.Fixed.Lex = &lex\n\t\t}\n\t}\n\n\t// space\n\tif sp, ok := m[\"space\"].(map[string]any); ok {\n\t\topts.Space = &SpaceOptions{}\n\t\tif lex, ok := sp[\"lex\"].(bool); ok {\n\t\t\topts.Space.Lex = &lex\n\t\t}\n\t\tif chars, ok := sp[\"chars\"].(string); ok {\n\t\t\topts.Space.Chars = chars\n\t\t}\n\t}\n\n\t// line\n\tif ln, ok := m[\"line\"].(map[string]any); ok {\n\t\topts.Line = &LineOptions{}\n\t\tif lex, ok := ln[\"lex\"].(bool); ok {\n\t\t\topts.Line.Lex = &lex\n\t\t}\n\t\tif chars, ok := ln[\"chars\"].(string); ok {\n\t\t\topts.Line.Chars = chars\n\t\t}\n\t\tif rowChars, ok := ln[\"rowChars\"].(string); ok {\n\t\t\topts.Line.RowChars = rowChars\n\t\t}\n\t\tif single, ok := ln[\"single\"].(bool); ok {\n\t\t\topts.Line.Single = &single\n\t\t}\n\t}\n\n\t// text\n\tif tm, ok := m[\"text\"].(map[string]any); ok {\n\t\topts.Text = &TextOptions{}\n\t\tif lex, ok := tm[\"lex\"].(bool); ok {\n\t\t\topts.Text.Lex = &lex\n\t\t}\n\t}\n\n\t// number\n\tif nm, ok := m[\"number\"].(map[string]any); ok {\n\t\topts.Number = &NumberOptions{}\n\t\tif lex, ok := nm[\"lex\"].(bool); ok {\n\t\t\topts.Number.Lex = &lex\n\t\t}\n\t\tif hex, ok := nm[\"hex\"].(bool); ok {\n\t\t\topts.Number.Hex = &hex\n\t\t}\n\t\tif oct, ok := nm[\"oct\"].(bool); ok {\n\t\t\topts.Number.Oct = &oct\n\t\t}\n\t\tif bin, ok := nm[\"bin\"].(bool); ok {\n\t\t\topts.Number.Bin = &bin\n\t\t}\n\t\tif sep, ok := nm[\"sep\"].(string); ok {\n\t\t\topts.Number.Sep = sep\n\t\t}\n\t\tif fn, ok := nm[\"exclude\"].(func(string) bool); ok {\n\t\t\topts.Number.Exclude = fn\n\t\t} else if re, ok := nm[\"exclude\"].(*regexp.Regexp); ok {\n\t\t\topts.Number.Exclude = func(s string) bool {\n\t\t\t\treturn re.MatchString(s)\n\t\t\t}\n\t\t}\n\t}\n\n\t// comment\n\tif cm, ok := m[\"comment\"].(map[string]any); ok {\n\t\topts.Comment = &CommentOptions{}\n\t\tif lex, ok := cm[\"lex\"].(bool); ok {\n\t\t\topts.Comment.Lex = &lex\n\t\t}\n\t\tif defm, ok := cm[\"def\"].(map[string]any); ok {\n\t\t\topts.Comment.Def = make(map[string]*CommentDef, len(defm))\n\t\t\tfor k, v := range defm {\n\t\t\t\tdm, ok := v.(map[string]any)\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcd := &CommentDef{}\n\t\t\t\tif line, ok := dm[\"line\"].(bool); ok {\n\t\t\t\t\tcd.Line = line\n\t\t\t\t}\n\t\t\t\tif start, ok := dm[\"start\"].(string); ok {\n\t\t\t\t\tcd.Start = start\n\t\t\t\t}\n\t\t\t\tif end, ok := dm[\"end\"].(string); ok {\n\t\t\t\t\tcd.End = end\n\t\t\t\t}\n\t\t\t\tif lex, ok := dm[\"lex\"].(bool); ok {\n\t\t\t\t\tcd.Lex = &lex\n\t\t\t\t}\n\t\t\t\tif eatline, ok := dm[\"eatline\"].(bool); ok {\n\t\t\t\t\tcd.EatLine = &eatline\n\t\t\t\t}\n\t\t\t\t// Suffix round-trip via text: accept string or array.\n\t\t\t\t// The LexMatcher form requires the typed Go API.\n\t\t\t\tif suffix, ok := dm[\"suffix\"]; ok {\n\t\t\t\t\tswitch v := suffix.(type) {\n\t\t\t\t\tcase string:\n\t\t\t\t\t\tcd.Suffix = v\n\t\t\t\t\tcase []any:\n\t\t\t\t\t\tstrs := make([]string, 0, len(v))\n\t\t\t\t\t\tfor _, el := range v {\n\t\t\t\t\t\t\tif s, ok := el.(string); ok {\n\t\t\t\t\t\t\t\tstrs = append(strs, s)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcd.Suffix = strs\n\t\t\t\t\tcase []string:\n\t\t\t\t\t\tcd.Suffix = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\topts.Comment.Def[k] = cd\n\t\t\t}\n\t\t}\n\t}\n\n\t// string\n\tif sm, ok := m[\"string\"].(map[string]any); ok {\n\t\topts.String = &StringOptions{}\n\t\tif lex, ok := sm[\"lex\"].(bool); ok {\n\t\t\topts.String.Lex = &lex\n\t\t}\n\t\tif chars, ok := sm[\"chars\"].(string); ok {\n\t\t\topts.String.Chars = chars\n\t\t}\n\t\tif multiChars, ok := sm[\"multiChars\"].(string); ok {\n\t\t\topts.String.MultiChars = multiChars\n\t\t}\n\t\tif escapeChar, ok := sm[\"escapeChar\"].(string); ok {\n\t\t\topts.String.EscapeChar = escapeChar\n\t\t}\n\t\tif allowUnknown, ok := sm[\"allowUnknown\"].(bool); ok {\n\t\t\topts.String.AllowUnknown = &allowUnknown\n\t\t}\n\t\tif abandon, ok := sm[\"abandon\"].(bool); ok {\n\t\t\topts.String.Abandon = &abandon\n\t\t}\n\t\tif esc, ok := sm[\"escape\"].(map[string]any); ok {\n\t\t\topts.String.Escape = make(map[string]string, len(esc))\n\t\t\tfor k, v := range esc {\n\t\t\t\tif s, ok := v.(string); ok {\n\t\t\t\t\topts.String.Escape[k] = s\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif rep, ok := sm[\"replace\"].(map[string]any); ok {\n\t\t\topts.String.Replace = make(map[rune]string, len(rep))\n\t\t\tfor k, v := range rep {\n\t\t\t\tif len(k) > 0 {\n\t\t\t\t\tif s, ok := v.(string); ok {\n\t\t\t\t\t\topts.String.Replace[rune(k[0])] = s\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// map\n\tif mm, ok := m[\"map\"].(map[string]any); ok {\n\t\topts.Map = &MapOptions{}\n\t\tif ext, ok := mm[\"extend\"].(bool); ok {\n\t\t\topts.Map.Extend = &ext\n\t\t}\n\t\tif child, ok := mm[\"child\"].(bool); ok {\n\t\t\topts.Map.Child = &child\n\t\t}\n\t\tif fn, ok := mm[\"merge\"].(func(any, any, *Rule, *Context) any); ok {\n\t\t\topts.Map.Merge = fn\n\t\t}\n\t}\n\n\t// list\n\tif lm, ok := m[\"list\"].(map[string]any); ok {\n\t\topts.List = &ListOptions{}\n\t\tif prop, ok := lm[\"property\"].(bool); ok {\n\t\t\topts.List.Property = &prop\n\t\t}\n\t\tif pair, ok := lm[\"pair\"].(bool); ok {\n\t\t\topts.List.Pair = &pair\n\t\t}\n\t\tif child, ok := lm[\"child\"].(bool); ok {\n\t\t\topts.List.Child = &child\n\t\t}\n\t}\n\n\t// value\n\tif vm, ok := m[\"value\"].(map[string]any); ok {\n\t\topts.Value = &ValueOptions{}\n\t\tif lex, ok := vm[\"lex\"].(bool); ok {\n\t\t\topts.Value.Lex = &lex\n\t\t}\n\t\tif defm, ok := vm[\"def\"].(map[string]any); ok {\n\t\t\topts.Value.Def = make(map[string]*ValueDef, len(defm))\n\t\t\tfor k, v := range defm {\n\t\t\t\tswitch vv := v.(type) {\n\t\t\t\tcase map[string]any:\n\t\t\t\t\tvd := &ValueDef{}\n\t\t\t\t\tif val, ok := vv[\"val\"]; ok {\n\t\t\t\t\t\tvd.Val = val\n\t\t\t\t\t\tif fn, ok := val.(func([]string) any); ok {\n\t\t\t\t\t\t\tvd.ValFunc = fn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif m, ok := vv[\"match\"].(*regexp.Regexp); ok {\n\t\t\t\t\t\tvd.Match = m\n\t\t\t\t\t}\n\t\t\t\t\tif c, ok := vv[\"consume\"].(bool); ok {\n\t\t\t\t\t\tvd.Consume = c\n\t\t\t\t\t}\n\t\t\t\t\topts.Value.Def[k] = vd\n\t\t\t\tcase nil, bool:\n\t\t\t\t\t// nil or false removes the value def\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// ender\n\tif ender, ok := m[\"ender\"]; ok {\n\t\tswitch v := ender.(type) {\n\t\tcase string:\n\t\t\topts.Ender = []string{v}\n\t\tcase []any:\n\t\t\tfor _, item := range v {\n\t\t\t\tif s, ok := item.(string); ok {\n\t\t\t\t\topts.Ender = append(opts.Ender, s)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// rule\n\tif rm, ok := m[\"rule\"].(map[string]any); ok {\n\t\topts.Rule = &RuleOptions{}\n\t\tif start, ok := rm[\"start\"].(string); ok {\n\t\t\topts.Rule.Start = start\n\t\t}\n\t\tif finish, ok := rm[\"finish\"].(bool); ok {\n\t\t\topts.Rule.Finish = &finish\n\t\t}\n\t\tif include, ok := rm[\"include\"].(string); ok {\n\t\t\topts.Rule.Include = include\n\t\t}\n\t\tif exclude, ok := rm[\"exclude\"].(string); ok {\n\t\t\topts.Rule.Exclude = exclude\n\t\t}\n\t}\n\n\t// lex\n\tif lx, ok := m[\"lex\"].(map[string]any); ok {\n\t\topts.Lex = &LexOptions{}\n\t\tif empty, ok := lx[\"empty\"].(bool); ok {\n\t\t\topts.Lex.Empty = &empty\n\t\t}\n\t\tif emptyResult, ok := lx[\"emptyResult\"]; ok {\n\t\t\topts.Lex.EmptyResult = emptyResult\n\t\t}\n\t}\n\n\t// error\n\tif em, ok := m[\"error\"].(map[string]any); ok {\n\t\topts.Error = make(map[string]string, len(em))\n\t\tfor k, v := range em {\n\t\t\tif s, ok := v.(string); ok {\n\t\t\t\topts.Error[k] = s\n\t\t\t}\n\t\t}\n\t}\n\n\t// hint\n\tif hm, ok := m[\"hint\"].(map[string]any); ok {\n\t\topts.Hint = make(map[string]string, len(hm))\n\t\tfor k, v := range hm {\n\t\t\tif s, ok := v.(string); ok {\n\t\t\t\topts.Hint[k] = s\n\t\t\t}\n\t\t}\n\t}\n\n\t// errmsg\n\tif em, ok := m[\"errmsg\"].(map[string]any); ok {\n\t\topts.ErrMsg = &ErrMsgOptions{}\n\t\tif name, ok := em[\"name\"].(string); ok {\n\t\t\topts.ErrMsg.Name = name\n\t\t}\n\t\tif suffix, ok := em[\"suffix\"]; ok {\n\t\t\t// TS accepts bool | string | function; only the JSON-serialisable\n\t\t\t// subset round-trips through a jsonic text source, so we only\n\t\t\t// pass those along. Functions need the typed API.\n\t\t\tswitch v := suffix.(type) {\n\t\t\tcase bool, string:\n\t\t\t\topts.ErrMsg.Suffix = v\n\t\t\t}\n\t\t}\n\t}\n\n\t// match\n\tif mm, ok := m[\"match\"].(map[string]any); ok {\n\t\topts.Match = &MatchOptions{}\n\t\tif lex, ok := mm[\"lex\"].(bool); ok {\n\t\t\topts.Match.Lex = &lex\n\t\t}\n\t\tif tok, ok := mm[\"token\"].(map[string]any); ok {\n\t\t\topts.Match.Token = make(map[string]*regexp.Regexp, len(tok))\n\t\t\tfor name, v := range tok {\n\t\t\t\tif re, ok := v.(*regexp.Regexp); ok {\n\t\t\t\t\topts.Match.Token[name] = re\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif val, ok := mm[\"value\"].(map[string]any); ok {\n\t\t\topts.Match.Value = make(map[string]*MatchValueSpec, len(val))\n\t\t\tfor name, v := range val {\n\t\t\t\tif spec, ok := v.(map[string]any); ok {\n\t\t\t\t\tmvs := &MatchValueSpec{}\n\t\t\t\t\tif re, ok := spec[\"match\"].(*regexp.Regexp); ok {\n\t\t\t\t\t\tmvs.Match = re\n\t\t\t\t\t}\n\t\t\t\t\tif fn, ok := spec[\"val\"].(func([]string) any); ok {\n\t\t\t\t\t\tmvs.Val = fn\n\t\t\t\t\t}\n\t\t\t\t\topts.Match.Value[name] = mvs\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// tokenSet\n\tif ts, ok := m[\"tokenSet\"].(map[string]any); ok {\n\t\topts.TokenSet = make(map[string][]string, len(ts))\n\t\tfor name, v := range ts {\n\t\t\tswitch arr := v.(type) {\n\t\t\tcase []any:\n\t\t\t\tvar names []string\n\t\t\t\tfor _, item := range arr {\n\t\t\t\t\tif s, ok := item.(string); ok {\n\t\t\t\t\t\tnames = append(names, s)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\topts.TokenSet[name] = names\n\t\t\tcase []string:\n\t\t\t\topts.TokenSet[name] = arr\n\t\t\t}\n\t\t}\n\t}\n\n\t// info\n\tif im, ok := m[\"info\"].(map[string]any); ok {\n\t\topts.Info = &InfoOptions{}\n\t\tif v, ok := im[\"map\"].(bool); ok {\n\t\t\topts.Info.Map = &v\n\t\t}\n\t\tif v, ok := im[\"list\"].(bool); ok {\n\t\t\topts.Info.List = &v\n\t\t}\n\t\tif v, ok := im[\"text\"].(bool); ok {\n\t\t\topts.Info.Text = &v\n\t\t}\n\t\tif v, ok := im[\"marker\"].(string); ok {\n\t\t\topts.Info.Marker = v\n\t\t}\n\t}\n\n\t// color\n\tif cm, ok := m[\"color\"].(map[string]any); ok {\n\t\topts.Color = &ColorOptions{}\n\t\tif v, ok := cm[\"active\"].(bool); ok {\n\t\t\topts.Color.Active = &v\n\t\t}\n\t\tif v, ok := cm[\"reset\"].(string); ok {\n\t\t\topts.Color.Reset = v\n\t\t}\n\t\tif v, ok := cm[\"hi\"].(string); ok {\n\t\t\topts.Color.Hi = v\n\t\t}\n\t\tif v, ok := cm[\"lo\"].(string); ok {\n\t\t\topts.Color.Lo = v\n\t\t}\n\t\tif v, ok := cm[\"line\"].(string); ok {\n\t\t\topts.Color.Line = v\n\t\t}\n\t}\n\n\treturn opts\n}\n\n// ResolveFuncRefs recursively resolves FuncRef strings in a map[string]any:\n//   - \"@@prefix\" → literal \"@prefix\"\n//   - \"@SKIP\" → Skip sentinel\n//   - \"@/pattern/flags\" → *regexp.Regexp\n//   - \"@name\" → function from ref map\nfunc ResolveFuncRefs(obj any, ref map[FuncRef]any) any {\n\tif obj == nil {\n\t\treturn nil\n\t}\n\tif s, ok := obj.(string); ok && len(s) > 0 && s[0] == '@' {\n\t\t// Escape: @@ → literal @-prefixed string\n\t\tif len(s) > 1 && s[1] == '@' {\n\t\t\treturn s[1:]\n\t\t}\n\t\t// Sentinel: @SKIP → Skip\n\t\tif s == \"@SKIP\" {\n\t\t\treturn Skip\n\t\t}\n\t\t// Regex: @/pattern/flags → *regexp.Regexp\n\t\tif len(s) > 2 && s[1] == '/' {\n\t\t\tif idx := strings.LastIndex(s, \"/\"); idx > 1 {\n\t\t\t\tpattern := s[2:idx]\n\t\t\t\tflags := s[idx+1:]\n\t\t\t\tif flags != \"\" {\n\t\t\t\t\tpattern = \"(?\" + flags + \")\" + pattern\n\t\t\t\t}\n\t\t\t\tre, err := regexp.Compile(pattern)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn re\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// FuncRef: @name → function from ref\n\t\tif ref != nil {\n\t\t\tif fn, ok := ref[s]; ok {\n\t\t\t\treturn fn\n\t\t\t}\n\t\t}\n\t\treturn obj\n\t}\n\n\t// Recurse into maps\n\tif m, ok := obj.(map[string]any); ok {\n\t\tout := make(map[string]any, len(m))\n\t\tfor k, v := range m {\n\t\t\tout[k] = ResolveFuncRefs(v, ref)\n\t\t}\n\t\treturn out\n\t}\n\n\t// Recurse into slices\n\tif arr, ok := obj.([]any); ok {\n\t\tout := make([]any, len(arr))\n\t\tfor i, v := range arr {\n\t\t\tout[i] = ResolveFuncRefs(v, ref)\n\t\t}\n\t\treturn out\n\t}\n\n\treturn obj\n}\n"
  },
  {
    "path": "go/variant_test.go",
    "content": "package jsonic\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\n// Port of test/variant.test.js `just-json-happy`. Uses MakeJSON(),\n// Go's equivalent of TS Jsonic.make('json').\n\nfunc TestVariant_StrictJSON_Happy(t *testing.T) {\n\tj := MakeJSON()\n\n\tcases := []struct {\n\t\tsrc  string\n\t\twant any\n\t}{\n\t\t{`{\"a\":1}`, map[string]any{\"a\": 1.0}},\n\t\t{\n\t\t\t`{\"a\":1,\"b\":\"x\",\"c\":true,\"d\":{\"e\":[-1.1e2,{\"f\":null}]}}`,\n\t\t\tmap[string]any{\n\t\t\t\t\"a\": 1.0,\n\t\t\t\t\"b\": \"x\",\n\t\t\t\t\"c\": true,\n\t\t\t\t\"d\": map[string]any{\n\t\t\t\t\t\"e\": []any{-110.0, map[string]any{\"f\": nil}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{` \"a\" `, \"a\"},\n\t\t{\"\\r\\n\\t1.0\\n\", 1.0},\n\t\t// NOTE: as per JSON.parse — last-wins on duplicate keys.\n\t\t{`{\"a\":1,\"a\":2}`, map[string]any{\"a\": 2.0}},\n\t}\n\n\tfor _, c := range cases {\n\t\tgot, err := j.Parse(c.src)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"parse(%q) error: %v\", c.src, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !reflect.DeepEqual(got, c.want) {\n\t\t\tt.Errorf(\"parse(%q)\\n  got:  %#v\\n  want: %#v\", c.src, got, c.want)\n\t\t}\n\t}\n}\n\nfunc TestVariant_StrictJSON_Rejects(t *testing.T) {\n\tj := MakeJSON()\n\n\t// Inputs that are valid jsonic but not strict JSON — must fail under\n\t// the approximation. Matches the `assert.throws` cases in TS.\n\tbad := []string{\n\t\t`{a:1}`,          // unquoted key\n\t\t`{\"a\":1,}`,       // trailing comma\n\t\t`[a]`,            // unquoted value\n\t\t`[\"a\",]`,         // trailing comma in array\n\t\t\"\\\"a\\\" # foo\",    // comment\n\t\t`0xA`,            // hex literal\n\t\t\"`a`\",            // backtick string\n\t\t`'a'`,            // single-quoted string\n\t\t`{\"a\":1`,         // unterminated object\n\t\t`[,a]`,           // leading comma\n\t\t`00`,             // non-standard numeric prefix\n\t}\n\n\tfor _, src := range bad {\n\t\tif _, err := j.Parse(src); err == nil {\n\t\t\tt.Errorf(\"parse(%q) expected error, got nil\", src)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"jsonic\",\n  \"version\": \"2.28.0\",\n  \"main\": \"dist/jsonic.js\",\n  \"type\": \"commonjs\",\n  \"types\": \"dist/jsonic.d.ts\",\n  \"description\": \"A dynamic JSON parser that isn't strict and can be customized.\",\n  \"homepage\": \"https://github.com/rjrodger/jsonic\",\n  \"keywords\": [\n    \"pattern\",\n    \"matcher\",\n    \"object\",\n    \"property\",\n    \"json\"\n  ],\n  \"author\": \"Richard Rodger (http://richardrodger.com)\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/rjrodger/jsonic.git\"\n  },\n  \"scripts\": {\n    \"test\": \"node --enable-source-maps --test test/**/*.test.js\",\n    \"test-some\": \"node --enable-source-maps --test-name-pattern=\\\"$TEST_PATTERN\\\" --test \\\"test/**/*.test.js\\\"\",\n    \"test-watch\": \"node --test --watch test/**/*.test.js\",\n    \"test-view\": \"open coverage/lcov-report/index.html\",\n    \"watch\": \"tsc --build src test -w\",\n    \"build\": \"tsc --build src test\",\n    \"diagrams\": \"docs-src/.vuepress/diagrams/generate.sh\",\n    \"docs-dev\": \"vuepress dev docs-src\",\n    \"docs-build\": \"vuepress build docs-src\",\n    \"clean\": \"rm -rf node_modules dist yarn.lock package-lock.json\",\n    \"reset\": \"npm run clean && npm i && npm run build && npm test\",\n    \"repo-tag\": \"REPO_VERSION=`node -e \\\"console.log(require('./package').version)\\\"` && echo TAG: v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag v$REPO_VERSION && git push --tags;\",\n    \"repo-publish\": \"npm run clean && npm i && npm run repo-publish-quick\",\n    \"repo-publish-quick\": \"npm run build && npm run test && npm run repo-tag && npm publish --registry https://registry.npmjs.org --access=public\"\n  },\n  \"license\": \"MIT\",\n  \"files\": [\n    \"dist\",\n    \"bin\",\n    \"LICENSE\"\n  ],\n  \"bin\": {\n    \"jsonic\": \"bin/jsonic\",\n    \"jsonic-bnf\": \"bin/jsonic-bnf\"\n  },\n  \"exports\": {\n    \".\": \"./dist/jsonic.js\",\n    \"./debug\": \"./dist/debug.js\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"25.6.0\",\n    \"typescript\": \"6.0.3\"\n  }\n}\n"
  },
  {
    "path": "src/bnf.ts",
    "content": "/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */\n\n/*  bnf.ts\n *  BNF -> jsonic grammar spec converter.\n *\n *  Accepts a small BNF dialect: productions of the form\n *  `<name> ::= rhs`, where `rhs` is an alternation (`|`) of sequences\n *  of terminal literals (`\"foo\"`, `'foo'`) and nonterminal references\n *  (`<name>` or a bare `name`).\n *\n *  The BNF source is itself parsed by a jsonic instance whose grammar\n *  is defined below in `bnfRules`. That grammar is declarative — a\n *  table of `open`/`close` alt specs per rule, with small `bo`/`bc`\n *  state hooks for AST assembly. See `getBnfParser` for how those\n *  rules are installed on a fresh jsonic instance.\n *\n *  The emitter turns each alternative into one or more jsonic rule\n *  alts. A \"single-segment\" alternative (at most one rule reference,\n *  trailing) collapses to a single jsonic alt; any alternative with\n *  two or more ref boundaries is chained through synthetic\n *  continuation rules named `<prodname>$stepN`.\n *\n *  Larger BNF features — repetition, optionality, grouping, left\n *  recursion, precedence — are still out of scope; see\n *  doc/bnf-to-jsonic-feasibility.md for the full plan.\n */\n\nimport type { BnfConvertOptions, GrammarSpec, Rule } from './types'\n\n\ntype BnfElement =\n  | {\n      kind: 'term';\n      literal: string;\n      // ABNF quoted strings are case-insensitive by default; `%s\"…\"`\n      // sets this flag to true. Omitting the flag preserves the\n      // RFC 5234 default so the emitter can lower to a case-fold\n      // match. (Has no effect when the literal contains no ASCII\n      // letters — those match exactly either way.)\n      caseSensitive?: boolean;\n    }\n  | { kind: 'ref'; name: string }\n  | { kind: 'regex'; pattern: string; flags: string }  // internal: for future %x\n  | { kind: 'opt'; inner: BnfElement }     // [ A ]\n  | { kind: 'star'; inner: BnfElement }    // *A\n  | { kind: 'plus'; inner: BnfElement }    // 1*A\n  | { kind: 'rep'; min: number; max: number; inner: BnfElement } // m*nA\n  | { kind: 'group'; alts: BnfSequence[] } // ( A / B )\n\ntype BnfSequence = BnfElement[]\n\ntype BnfProduction = {\n  name: string\n  alts: BnfSequence[]\n  // ABNF `name =/ alt` — flagged during parse; a post-parse merge\n  // step folds each incremental production's alts into the earlier\n  // production with the same name. The flag is gone by the time\n  // the AST reaches the emitter.\n  incremental?: boolean\n  // Set on synthetic productions introduced by the probe-dispatch\n  // rewriter. The emitter emits a phase-retry rule body instead of\n  // compiling `alts` through the normal path.\n  probeDispatch?: ProbeDispatchSpec\n  // Set on synthetic probe-helper productions (`*(vocab)` style,\n  // failure-proof). The emitter emits a one-rule loop over the vocab\n  // token set with an empty-alt fallback. Vocab is stored as a list\n  // of BnfElements (term / regex) — resolved to token names at emit\n  // time, after token allocation.\n  probeHelper?: { vocabElements: BnfElement[] }\n  // How this rule contributes to the output AST:\n  //   - 'user': emit a tagged node `{ rule, src, kids }`.\n  //   - 'core': the RFC 5234 Appendix B.1 rules — flatten into the\n  //     enclosing rule's `src` (char-class terminals shouldn't clutter\n  //     the tree with one node per matched character).\n  //   - 'helper': synthetic sugar / dispatcher / chain rules — also\n  //     flatten. Their `src` and `kids` roll up into the enclosing\n  //     user rule transparently.\n  // Unset is treated as 'user' (default for freshly parsed productions).\n  nodeKind?: 'user' | 'core' | 'helper'\n}\n\n// Configuration attached to a synthesised dispatcher production. The\n// dispatcher is the replacement for an ambiguous `[X D] Y` subsequence\n// in a user rule: on phase 0 it pushes `probeRule` (a failure-proof\n// *vocab loop), peeks `ctx.t[0]` on return, rewinds to the mark, and\n// sets `r.k.phase` to 1 (saw D → push `withBranch`) or 2 (didn't →\n// push `noBranch`). `r:` retries the dispatcher so the committed\n// branch is taken on the next pass.\n//\n// The disambiguator is stored as a BnfElement (term or regex) rather\n// than a token name so the rewriter doesn't need token allocation to\n// have happened. `emitProbeDispatch` resolves it to a name at emit\n// time from the literals / regex maps.\ntype ProbeDispatchSpec = {\n  probeRule: string\n  disambiguator: BnfElement\n  withBranch: string\n  noBranch: string\n}\n\ntype BnfGrammar = {\n  productions: BnfProduction[]\n  // Diagnostics from the probe-dispatch analyser: one entry per\n  // ambiguous `[X D] Y` pattern detected, populated whether or not\n  // the rewrite was actually applied.\n  ambiguities?: AmbiguityReport[]\n}\n\ntype AmbiguityReport = {\n  rule: string\n  altIdx: number\n  optIdx: number\n  reason: string\n  resolved: boolean\n}\n\n\n// Declarative definition of the BNF grammar itself, expressed as\n// jsonic rules. Each rule names its `open`/`close` alt list and, where\n// necessary, a `bo`/`bc` state hook for AST assembly.\n//\n// Stage 8: incremental alternatives via `name =/ alt` now fold\n// into the earlier production with the same name. Quoted strings\n// default to case-insensitive (ABNF semantics), `%s` / `%i` force\n// sensitivity explicitly, numeric values and repetition prefixes\n// work as in previous stages.\n//\n// Token vocabulary:\n//   #DEF   `=`  (rule-definition operator)\n//   #DEFA  `=/` (incremental-alternatives operator)\n//   #ALT   `/`  (alternation)\n//   #STAR  `*`  (repetition separator)\n//   #NUM   decimal repetition count (matched via match.token)\n//   #NV    `%[xdb]NN[(-NN|(.NN)*)]` numeric value (match.token)\n//   #SS    `%s` (case-sensitive string prefix)\n//   #SI    `%i` (case-insensitive string prefix — same as default)\n//   #LP    `(`\n//   #RP    `)`\n//   #OB    `[` (optional-group open)\n//   #CB    `]` (optional-group close)\n//   #TX    bare identifier (jsonic default text token)\n//   #ST    quoted string literal (jsonic default string token)\n//   #ZZ    end-of-source\n//\n// Grammar:\n//   bnf        = production*\n//   production = IDENT ('=' / '=/') alts\n//   alts       = seq ('/' seq)*\n//   seq        = element*\n//   element    = repetition? atom\n//   repetition = NUM '*' NUM / NUM '*' / '*' NUM / '*' / NUM\n//   atom       = IDENT | STRING | ['%s' | '%i'] STRING | NUMVAL\n//              | '(' alts ')' | '[' alts ']'\n//   numval     = '%' ('x' / 'd' / 'b') DIGITS [ '-' DIGITS | ('.' DIGITS)* ]\nconst bnfRules: Record<\n  string,\n  {\n    bo?: (r: Rule) => void\n    bc?: (r: Rule) => void\n    open?: any[]\n    close?: any[]\n  }\n> = {\n  // Top-level: accumulates productions into r.node.\n  bnf: {\n    bo: (r) => { r.node = [] },\n    open: [\n      { s: '#ZZ', g: 'empty' },\n      { p: 'prod' },\n    ],\n    close: [{ s: '#ZZ' }],\n  },\n\n  // One production per invocation; tail-recurses (r:'prod') for the\n  // next. Inherits its parent's node (the productions array) and\n  // appends to it in `bc` once its `alts` child has returned.\n  // Production header is `IDENT =` — a bareword rule name followed\n  // by the `=` definition operator.\n  prod: {\n    open: [\n      // Standalone definition:   name = alts\n      {\n        s: '#TX #DEF',\n        a: (r: Rule) => {\n          r.u.name = r.o[0].val\n          r.u.incremental = false\n        },\n        p: 'alts',\n      },\n      // Incremental alternatives:   name =/ alts\n      {\n        s: '#TX #DEFA',\n        a: (r: Rule) => {\n          r.u.name = r.o[0].val\n          r.u.incremental = true\n        },\n        p: 'alts',\n      },\n    ],\n    close: [\n      // A TX followed by `=` or `=/` means the next production has\n      // begun — back up 2 tokens so a fresh `prod` invocation sees\n      // them.\n      { s: '#TX #DEF', b: 2, r: 'prod' },\n      { s: '#TX #DEFA', b: 2, r: 'prod' },\n      { b: 1 },\n    ],\n    bc: (r) => {\n      if (r.child && r.child.node !== undefined) {\n        const prod: any = { name: r.u.name, alts: r.child.node }\n        if (r.u.incremental) prod.incremental = true\n        r.node.push(prod)\n      }\n    },\n  },\n\n  // A list of alternative sequences separated by `/` (ABNF\n  // alternation). Owns its own array (`bo` resets it) and pushes\n  // each seq result in `bc`.\n  alts: {\n    bo: (r) => { r.node = [] },\n    open: [{ p: 'seq' }],\n    close: [\n      { s: '#ALT', p: 'seq' },\n      { b: 1 },\n    ],\n    bc: (r) => {\n      if (r.child && r.child.node !== undefined) {\n        r.node.push(r.child.node)\n      }\n    },\n  },\n\n  // A (possibly empty) sequence of elements. The 2-token lookahead\n  // `#TX #DEF` detects a following production boundary and bails\n  // out without consuming the tokens; a plain `#TX` at the leading\n  // position (tried later so the longer alt wins) is a rule\n  // reference inside the current sequence.\n  seq: {\n    bo: (r) => { r.node = [] },\n    open: [\n      { s: '#TX #DEF', b: 2, g: 'end' },\n      { s: '#TX #DEFA', b: 2, g: 'end' },\n      { s: '#ALT', b: 1, g: 'end' },\n      { s: '#ZZ', b: 1, g: 'end' },\n      { s: '#RP', b: 1, g: 'end' },\n      { s: '#CB', b: 1, g: 'end' },\n      // Listing element-starter tokens in `s:` here ensures the\n      // tcol-driven matcher considers each one when lexing.\n      { s: '#ST', b: 1, p: 'elem' },\n      { s: '#NV', b: 1, p: 'elem' },\n      { s: '#SS', b: 1, p: 'elem' },\n      { s: '#SI', b: 1, p: 'elem' },\n      { s: '#TX', b: 1, p: 'elem' },\n      { s: '#LP', b: 1, p: 'elem' },\n      { s: '#OB', b: 1, p: 'elem' },\n      { s: '#STAR', b: 1, p: 'elem' },\n      { s: '#NUM', b: 1, p: 'elem' },\n      { p: 'elem' },\n    ],\n    close: [\n      { s: '#TX #DEF', b: 2, g: 'end' },\n      { s: '#TX #DEFA', b: 2, g: 'end' },\n      { s: '#ALT', b: 1, g: 'end' },\n      { s: '#ZZ', b: 1, g: 'end' },\n      { s: '#RP', b: 1, g: 'end' },\n      { s: '#CB', b: 1, g: 'end' },\n      { s: '#ST', b: 1, p: 'elem' },\n      { s: '#NV', b: 1, p: 'elem' },\n      { s: '#SS', b: 1, p: 'elem' },\n      { s: '#SI', b: 1, p: 'elem' },\n      { s: '#TX', b: 1, p: 'elem' },\n      { s: '#LP', b: 1, p: 'elem' },\n      { s: '#OB', b: 1, p: 'elem' },\n      { s: '#STAR', b: 1, p: 'elem' },\n      { s: '#NUM', b: 1, p: 'elem' },\n      { b: 1 },\n    ],\n  },\n\n  // One element: an optional ABNF repetition prefix (`*A`, `1*A`,\n  // `m*nA`, `*nA`, `m*A`, `nA`) followed by an atom. The prefix is\n  // matched up front, stored on `r.u.min`/`r.u.max`; then `atom` is\n  // pushed to parse the actual element body, whose result is wrapped\n  // into an AST node and appended to the parent seq's array in close.\n  elem: {\n    bo: (r) => { r.u.min = 1; r.u.max = 1 },\n    open: [\n      // NUM '*' NUM — bounded repetition, followed by the atom\n      // itself (listed via the ATOM tokenset so every atom-starter\n      // tin — including `#NV` — is in tcol for this position).\n      {\n        s: '#NUM #STAR #NUM #ATOM',\n        b: 1,\n        a: (r: Rule) => {\n          r.u.min = parseInt(r.o[0].src, 10)\n          r.u.max = parseInt(r.o[2].src, 10)\n        },\n        p: 'atom',\n      },\n      // NUM '*' — at-least-NUM repetition followed by an atom.\n      {\n        s: '#NUM #STAR #ATOM',\n        b: 1,\n        a: (r: Rule) => {\n          r.u.min = parseInt(r.o[0].src, 10)\n          r.u.max = Infinity\n        },\n        p: 'atom',\n      },\n      // '*' NUM — at-most-NUM repetition.\n      {\n        s: '#STAR #NUM #ATOM',\n        b: 1,\n        a: (r: Rule) => {\n          r.u.min = 0\n          r.u.max = parseInt(r.o[1].src, 10)\n        },\n        p: 'atom',\n      },\n      // '*' — zero-or-more.\n      {\n        s: '#STAR #ATOM',\n        b: 1,\n        a: (r: Rule) => { r.u.min = 0; r.u.max = Infinity },\n        p: 'atom',\n      },\n      // NUM — exact repetition count.\n      {\n        s: '#NUM #ATOM',\n        b: 1,\n        a: (r: Rule) => {\n          const n = parseInt(r.o[0].src, 10)\n          r.u.min = n\n          r.u.max = n\n        },\n        p: 'atom',\n      },\n      // No prefix — push atom directly (min = max = 1).\n      { p: 'atom' },\n    ],\n    close: [{\n      // Wrap the returned atom (r.child.node) based on r.u.min/max\n      // and append to the parent seq's array.\n      a: (r: Rule) => {\n        const item = r.child.node\n        const { min, max } = r.u\n        if (min === 1 && max === 1) {\n          r.node.push(item)\n        } else if (min === 0 && max === Infinity) {\n          r.node.push({ kind: 'star', inner: item })\n        } else if (min === 1 && max === Infinity) {\n          r.node.push({ kind: 'plus', inner: item })\n        } else if (min === 0 && max === 1) {\n          r.node.push({ kind: 'opt', inner: item })\n        } else {\n          r.node.push({ kind: 'rep', min, max, inner: item })\n        }\n      },\n    }],\n  },\n\n  // The atom body — a bareword ref, quoted-string terminal,\n  // parenthesised group, or bracketed optional. Sets its OWN r.node\n  // to the AST element so the enclosing `elem` rule can read it\n  // from `r.child.node` in its close state.\n  atom: {\n    bo: (r) => { r.node = undefined },\n    open: [\n      // Case-sensitive string:   %s\"foo\"\n      {\n        s: '#SS #ST',\n        a: (r: Rule) => {\n          r.node = {\n            kind: 'term',\n            literal: r.o[1].val,\n            caseSensitive: true,\n          }\n        },\n      },\n      // Case-insensitive string: %i\"foo\" (same as bare \"foo\" below,\n      // but spelled explicitly).\n      {\n        s: '#SI #ST',\n        a: (r: Rule) => {\n          r.node = { kind: 'term', literal: r.o[1].val }\n        },\n      },\n      // Bare quoted string — case-insensitive per ABNF default.\n      {\n        s: '#ST',\n        a: (r: Rule) => {\n          r.node = { kind: 'term', literal: r.o[0].val }\n        },\n      },\n      {\n        s: '#NV',\n        a: (r: Rule) => {\n          r.node = parseNumericValue(r.o[0].src as string)\n        },\n      },\n      {\n        s: '#TX',\n        a: (r: Rule) => {\n          r.node = { kind: 'ref', name: r.o[0].val }\n        },\n      },\n      {\n        s: '#LP',\n        a: (r: Rule) => { r.u.groupKind = 'group' },\n        p: 'alts',\n      },\n      {\n        s: '#OB',\n        a: (r: Rule) => { r.u.groupKind = 'opt' },\n        p: 'alts',\n      },\n    ],\n    close: [\n      {\n        s: '#RP',\n        c: (r: Rule) => r.u.groupKind === 'group',\n        a: (r: Rule) => {\n          r.node = { kind: 'group', alts: r.child.node }\n        },\n      },\n      {\n        s: '#CB',\n        c: (r: Rule) => r.u.groupKind === 'opt',\n        a: (r: Rule) => {\n          r.node = {\n            kind: 'opt',\n            inner: { kind: 'group', alts: r.child.node },\n          }\n        },\n      },\n      // For simple atoms (string/ref), r.node is already set by\n      // open; we want to pop without consuming the next token.\n      // List every token that can legitimately follow an atom so\n      // the lexer's tcol-driven match-matcher emits #NUM, #STAR,\n      // and friends as their proper types here — otherwise the\n      // default number-matcher would lex `1` as #NR and the\n      // enclosing seq.close wouldn't recognise the digit as the\n      // start of a repetition prefix.\n      { s: '#TX', b: 1 },\n      { s: '#ST', b: 1 },\n      { s: '#NV', b: 1 },\n      { s: '#SS', b: 1 },\n      { s: '#SI', b: 1 },\n      { s: '#NUM', b: 1 },\n      { s: '#STAR', b: 1 },\n      { s: '#LP', b: 1 },\n      { s: '#OB', b: 1 },\n      { s: '#RP', b: 1 },\n      { s: '#CB', b: 1 },\n      { s: '#ALT', b: 1 },\n      { s: '#DEF', b: 1 },\n      { s: '#ZZ', b: 1 },\n      { b: 1 },\n    ],\n  },\n}\n\n\n// Lazily built jsonic instance that parses BNF source. Deferred\n// construction avoids a circular-import failure at module load time.\nlet _bnfParser: ((src: string) => BnfProduction[]) | null = null\n\nfunction getBnfParser(): (src: string) => BnfProduction[] {\n  if (_bnfParser) return _bnfParser\n\n  const { Jsonic } = require('./jsonic')\n\n  const j = Jsonic.make({\n    rule: { start: 'bnf' },\n    fixed: {\n      token: {\n        // Clear JSON-oriented defaults we're not using so `:`, `,`\n        // and `{` have no special meaning inside BNF source.\n        '#OS': null,\n        '#CS': null,\n        '#CL': null,\n        '#CA': null,\n        // Re-map `#OB` / `#CB` from JSON's `{` / `}` to ABNF's\n        // `[` / `]` optional-group brackets.\n        '#OB': '[',\n        '#CB': ']',\n        '#DEF': '=',\n        // `=/` — ABNF's incremental-alternatives operator. Longer\n        // than `=`, so jsonic's longest-match-wins fixed matcher\n        // tries it first.\n        '#DEFA': '=/',\n        '#ALT': '/',\n        '#STAR': '*',\n        '#LP': '(',\n        '#RP': ')',\n      },\n    },\n    match: {\n      token: {\n        // ABNF repetition counts: decimal integers.\n        '#NUM': /^[0-9]+/,\n        // ABNF numeric value notation:\n        //   %xNN        single hex code point\n        //   %dNN        single decimal code point\n        //   %bNN        single binary code point\n        //   %xNN-NN     hex range\n        //   %xNN.NN.NN  concatenated hex code points (= string)\n        // Digits are permissive (hex covers the decimal / binary\n        // subsets); `parseNumericValue` re-validates against the\n        // actual base.\n        '#NV': /^%[xdbXDB][0-9a-fA-F]+(?:[-.][0-9a-fA-F]+)*/,\n        // `%s` / `%i` prefixes on a quoted string. The lookahead\n        // requires `\"` so they don't steal the `%` of `%xNN`.\n        '#SS': /^%[sS](?=\")/,\n        '#SI': /^%[iI](?=\")/,\n      },\n    },\n    tokenSet: {\n      // Tokens that can legitimately open an atom. Declaring this\n      // as a set lets elem.open use `#ATOM` inside its `s:` patterns\n      // — that way the tcol at the atom-starter position includes\n      // every matcher tin (notably #NV), so the lexer doesn't fall\n      // through to #TX when the actual atom is `%xNN`.\n      ATOM: ['#ST', '#NV', '#TX', '#LP', '#OB', '#SS', '#SI'],\n    },\n    comment: {\n      // ABNF uses `;` to start a line comment. Override jsonic's\n      // default `hash` definition (which used `#`) and disable the\n      // other comment styles so `//` and `/* */` aren't confused\n      // with the alternation operator.\n      def: {\n        hash: { line: true, start: ';', lex: true, eatline: false },\n        slash: null as any,\n        multi: null as any,\n      },\n    },\n  })\n\n  // Drop the default JSON rules — they would otherwise compete with\n  // ours for the starting token set.\n  const existing = j.rule()\n  for (const name of Object.keys(existing)) {\n    j.rule(name, null)\n  }\n\n  for (const name of Object.keys(bnfRules)) {\n    const spec = bnfRules[name]\n    j.rule(name, (rs: any) => {\n      if (spec.bo) rs.bo(spec.bo)\n      if (spec.bc) rs.bc(spec.bc)\n      if (spec.open) rs.open(spec.open)\n      if (spec.close) rs.close(spec.close)\n    })\n  }\n\n  _bnfParser = (src: string) => j(src) as BnfProduction[]\n  return _bnfParser\n}\n\n\n// Rewrite a grammar so that the only element kinds remaining are\n// `term` and `ref`. Each `X?`, `X*`, `X+` occurrence is replaced by a\n// reference to a newly-generated helper production that expresses the\n// same language in plain BNF.\n// Eliminate left recursion — both direct (P → P α) and indirect\n// (P → Q α, Q → P β) — via Paull's algorithm.\n//\n// Order the productions, and for each A_i walk back over A_1..A_{i-1}\n// inlining any leading reference into A_i's alternatives. Once the\n// only remaining leading self-reference on A_i is direct, rewrite to\n// the iterative form\n//   P → (β_1 | … | β_m) (α_1 | … | α_n)*\n// which jsonic's push-down parser can execute without re-entering P\n// at the same source position.\n//\n// The substitution step can duplicate alternatives, so pathological\n// grammars will enlarge — caller is expected to keep the grammar\n// reasonably small (this is a first-step converter, not a full\n// toolchain).\nfunction eliminateLeftRecursion(grammar: BnfGrammar): BnfGrammar {\n  const originalOrder = grammar.productions.map((p) => p.name)\n\n  // Order productions so that rules referenced at a leading position\n  // are processed before the rules that reference them. Paull's\n  // substitution inlines A_j's alts into A_i for j < i, so putting\n  // dependencies first is what makes nullable-prefixed hidden left\n  // recursion reachable by the substitution step.\n  //\n  // Note: substitution here always runs, even for cycle-free\n  // grammars. The reason is pragmatic rather than theoretical —\n  // populating tcol from multi-token altPrefixes (needed so the\n  // lexer's regex matchers fire with the right tin in nested\n  // contexts) requires the full inlined shape. A future refactor\n  // could compute tcol from the un-substituted grammar and only\n  // apply Paull's to the cyclic SCCs, which would preserve more\n  // named-rule structure in the emitted AST.\n  let prods = topoOrderForPaull(\n    grammar.productions.map((p) => ({\n      name: p.name,\n      alts: p.alts.map((a) => a.slice()),\n      nodeKind: p.nodeKind,\n    })),\n  )\n\n  for (let i = 0; i < prods.length; i++) {\n    // For each earlier production A_j, inline any alternative of\n    // A_i whose leading element is a reference to A_j.\n    for (let j = 0; j < i; j++) {\n      prods[i] = substituteLeadingRef(prods[i], prods[j])\n    }\n    prods[i] = eliminateDirectLeftRec(prods[i])\n  }\n\n  // Restore the caller's declared order, so the start rule still\n  // ends up first (and the user sees their rule names in a\n  // recognisable order when inspecting the spec).\n  const byName = new Map(prods.map((p) => [p.name, p]))\n  const ordered: BnfProduction[] = []\n  for (const name of originalOrder) {\n    const p = byName.get(name)\n    if (p) { ordered.push(p); byName.delete(name) }\n  }\n  // Any generated productions created during substitution (none in\n  // the current implementation) would fall through here.\n  for (const p of byName.values()) ordered.push(p)\n\n  return { productions: ordered }\n}\n\n\n// Tarjan-flavoured SCC scan over the leading-reference graph:\n// returns the names of productions that participate in at least one\n// cycle (self-loop or longer). Used to scope Paull's substitution to\n// only the rules that actually need it.\nfunction findLeadingRefCycleMembers(prods: BnfProduction[]): Set<string> {\n  const byName = new Map(prods.map((p) => [p.name, p]))\n  const leadingRefs = (p: BnfProduction): string[] => {\n    const out: string[] = []\n    for (const alt of p.alts) {\n      if (alt.length === 0) continue\n      const first = alt[0]\n      if (first.kind === 'ref' && byName.has(first.name)) out.push(first.name)\n    }\n    return out\n  }\n\n  // Tarjan's SCC algorithm.\n  let index = 0\n  const stack: string[] = []\n  const onStack = new Set<string>()\n  const indices = new Map<string, number>()\n  const lowlinks = new Map<string, number>()\n  const cyclic = new Set<string>()\n\n  function strongConnect(name: string) {\n    indices.set(name, index)\n    lowlinks.set(name, index)\n    index++\n    stack.push(name)\n    onStack.add(name)\n\n    const prod = byName.get(name)\n    if (prod) {\n      for (const target of leadingRefs(prod)) {\n        if (!indices.has(target)) {\n          strongConnect(target)\n          lowlinks.set(name, Math.min(lowlinks.get(name)!, lowlinks.get(target)!))\n        } else if (onStack.has(target)) {\n          lowlinks.set(name, Math.min(lowlinks.get(name)!, indices.get(target)!))\n        }\n      }\n    }\n\n    if (lowlinks.get(name) === indices.get(name)) {\n      // Pop the SCC. If it has more than one member, or it's a\n      // single member with a self-loop, mark as cyclic.\n      const scc: string[] = []\n      let w: string\n      do {\n        w = stack.pop() as string\n        onStack.delete(w)\n        scc.push(w)\n      } while (w !== name)\n      const isCycle =\n        scc.length > 1 ||\n        (scc.length === 1 && leadingRefs(byName.get(scc[0])!).includes(scc[0]))\n      if (isCycle) for (const n of scc) cyclic.add(n)\n    }\n  }\n\n  for (const p of prods) {\n    if (!indices.has(p.name)) strongConnect(p.name)\n  }\n  return cyclic\n}\n\n\n// Topological order over the \"leading-position reference\" graph:\n// an edge A → B exists when A has at least one alternative whose\n// first element is a reference to B. Cycles are preserved as-is\n// (Paull's handles them via the substitution + direct-LR rewrite).\nfunction topoOrderForPaull(prods: BnfProduction[]): BnfProduction[] {\n  const byName = new Map(prods.map((p) => [p.name, p]))\n  const colour = new Map<string, number>() // 0 unseen, 1 in-progress, 2 done\n  const order: BnfProduction[] = []\n\n  function visit(name: string) {\n    const c = colour.get(name) ?? 0\n    if (c !== 0) return // already seen or on the current path\n    colour.set(name, 1)\n    const p = byName.get(name)\n    if (p) {\n      for (const alt of p.alts) {\n        if (alt.length > 0 && alt[0].kind === 'ref' && byName.has(alt[0].name)) {\n          visit(alt[0].name)\n        }\n      }\n      colour.set(name, 2)\n      order.push(p)\n    } else {\n      colour.set(name, 2)\n    }\n  }\n\n  for (const p of prods) visit(p.name)\n  return order\n}\n\n\n// For every alternative of `target` that begins with a ref to\n// `source`, replace that alt with |source.alts| copies — each one\n// with the leading source-ref expanded to one of source's alts.\nfunction substituteLeadingRef(\n  target: BnfProduction,\n  source: BnfProduction,\n): BnfProduction {\n  const newAlts: BnfSequence[] = []\n  for (const alt of target.alts) {\n    if (\n      alt.length > 0 &&\n      alt[0].kind === 'ref' &&\n      alt[0].name === source.name\n    ) {\n      const tail = alt.slice(1)\n      for (const srcAlt of source.alts) {\n        newAlts.push([...srcAlt, ...tail])\n      }\n    } else {\n      newAlts.push(alt)\n    }\n  }\n  return { name: target.name, alts: newAlts, nodeKind: target.nodeKind }\n}\n\n\n// Rewrite a single production's direct left recursion to its\n// iterative equivalent. Equivalent to the previous version of\n// `eliminateLeftRecursion` but scoped to one production.\nfunction eliminateDirectLeftRec(prod: BnfProduction): BnfProduction {\n  const recursive: BnfSequence[] = []\n  const seeds: BnfSequence[] = []\n  for (const alt of prod.alts) {\n    if (\n      alt.length > 0 &&\n      alt[0].kind === 'ref' &&\n      alt[0].name === prod.name\n    ) {\n      recursive.push(alt.slice(1))\n    } else {\n      seeds.push(alt)\n    }\n  }\n\n  // A trivial recursive alt `[P]` (P ::= P, nothing else) would\n  // derive P from P with no progress — semantically a no-op. Drop\n  // them silently, since nullable-prefix expansion in Paull's can\n  // legitimately produce them and erroring would hide a legal\n  // grammar.\n  const nonTrivialRecursive = recursive.filter((t) => t.length > 0)\n  if (nonTrivialRecursive.length === 0) {\n    // Either no recursion at all, or only trivial self-refs — keep\n    // just the seeds.\n    return { name: prod.name, alts: seeds, nodeKind: prod.nodeKind }\n  }\n  if (seeds.length === 0) {\n    throw new Error(\n      `bnf: rule '${prod.name}' is purely left-recursive ` +\n      `(no seed alternative); cannot eliminate`)\n  }\n\n  const seedElement: BnfElement =\n    seeds.length === 1 && seeds[0].length === 1\n      ? seeds[0][0]\n      : { kind: 'group', alts: seeds }\n\n  const tailInner: BnfElement =\n    nonTrivialRecursive.length === 1 && nonTrivialRecursive[0].length === 1\n      ? nonTrivialRecursive[0][0]\n      : { kind: 'group', alts: nonTrivialRecursive }\n\n  return {\n    name: prod.name,\n    alts: [[seedElement, { kind: 'star', inner: tailInner }]],\n    nodeKind: prod.nodeKind,\n  }\n}\n\n\nfunction desugar(grammar: BnfGrammar): BnfGrammar {\n  const extra: BnfProduction[] = []\n  const used = new Set(grammar.productions.map((p) => p.name))\n\n  function freshName(hint: string): string {\n    // Collision-avoiding name like `_gen1`, `_gen2`, …\n    let i = extra.length\n    let name: string\n    do {\n      i++\n      name = `_gen${i}_${hint}`\n    } while (used.has(name))\n    used.add(name)\n    return name\n  }\n\n  function desugarAlt(alt: BnfSequence): BnfSequence {\n    return alt.map(desugarElement)\n  }\n\n  function desugarElement(el: BnfElement): BnfElement {\n    if (el.kind === 'term' || el.kind === 'ref' || el.kind === 'regex') {\n      return el\n    }\n\n    if (el.kind === 'group') {\n      // Recurse into the group's alts so nested sugar is flattened,\n      // then emit a helper production whose body is those alts.\n      const innerAlts = el.alts.map((a) => desugarAlt(a))\n      const name = freshName('group')\n      extra.push({ name, alts: innerAlts, nodeKind: 'helper' })\n      return { kind: 'ref', name }\n    }\n\n    // `opt`, `star`, `plus` all wrap a single inner element.\n    const inner = desugarElement(el.inner)\n    const hint =\n      inner.kind === 'ref' ? inner.name :\n        inner.kind === 'term' ? 'term' : 'x'\n\n    if (el.kind === 'opt') {\n      // H ::= inner | (empty)\n      const name = freshName('opt_' + hint)\n      extra.push({ name, alts: [[inner], []], nodeKind: 'helper' })\n      return { kind: 'ref', name }\n    }\n\n    if (el.kind === 'star') {\n      // H = inner H / (empty)\n      const name = freshName('star_' + hint)\n      const selfRef: BnfElement = { kind: 'ref', name }\n      extra.push({ name, alts: [[inner, selfRef], []], nodeKind: 'helper' })\n      return { kind: 'ref', name }\n    }\n\n    if (el.kind === 'plus') {\n      // H = inner Tail   where   Tail = inner Tail / (empty)\n      const tailName = freshName('star_' + hint)\n      const plusName = freshName('plus_' + hint)\n      const tailRef: BnfElement = { kind: 'ref', name: tailName }\n      extra.push({\n        name: tailName,\n        alts: [[inner, tailRef], []],\n        nodeKind: 'helper',\n      })\n      extra.push({\n        name: plusName,\n        alts: [[inner, tailRef]],\n        nodeKind: 'helper',\n      })\n      return { kind: 'ref', name: plusName }\n    }\n\n    // ABNF m*n bounded repetition. Desugars to a concatenation of\n    // `min` mandatory copies of the inner element followed by a\n    // tail that accepts up to `(max - min)` more.\n    //   m*n A   =>   A{m}  [A[A[A...[A]]]]   (nested optionals)\n    //   m*  A   =>   A{m}  *A                 (mandatory prefix + star)\n    //   *n  A   =>   [A [A ... [A]]]          (n nested optionals)\n    // The helper's single alt has `min` repetitions of inner, then\n    // either a star-helper for (min, ∞) or `max - min` nested\n    // optionals for a finite range.\n    const { min, max } = el\n    const repName = freshName('rep_' + hint)\n    const repAlt: BnfSequence = []\n    for (let i = 0; i < min; i++) repAlt.push(inner)\n\n    if (max === Infinity) {\n      // Tail: unbounded star of inner.\n      const tailStarName = freshName('star_' + hint)\n      const tailStarRef: BnfElement = { kind: 'ref', name: tailStarName }\n      extra.push({\n        name: tailStarName,\n        alts: [[inner, tailStarRef], []],\n        nodeKind: 'helper',\n      })\n      repAlt.push(tailStarRef)\n    } else {\n      // Nest (max - min) optionals: [A [A [A ...]]].\n      let nested: BnfSequence = []\n      for (let i = 0; i < max - min; i++) {\n        // Wrap current `nested` into an optional and prepend `inner`.\n        if (nested.length === 0) {\n          nested = [{ kind: 'opt', inner: { kind: 'group', alts: [[inner]] } }]\n        } else {\n          nested = [{\n            kind: 'opt',\n            inner: { kind: 'group', alts: [[inner, ...nested]] },\n          }]\n        }\n      }\n      repAlt.push(...nested)\n    }\n\n    extra.push({ name: repName, alts: [desugarAlt(repAlt)], nodeKind: 'helper' })\n    return { kind: 'ref', name: repName }\n  }\n\n  const rewritten: BnfProduction[] = grammar.productions.map((p) => {\n    const out: BnfProduction = {\n      name: p.name,\n      alts: p.alts.map(desugarAlt),\n      nodeKind: p.nodeKind,\n    }\n    // Probe-dispatch flags survive desugar unchanged — the emitter\n    // routes around the standard alt-compilation path for these.\n    if (p.probeDispatch) out.probeDispatch = p.probeDispatch\n    if (p.probeHelper) out.probeHelper = p.probeHelper\n    return out\n  })\n\n  return { productions: [...rewritten, ...extra] }\n}\n\n\n// Error raised when the BNF source itself can't be parsed.  Surfaces\n// line and column from the underlying jsonic error so the caller can\n// report them directly. The original error is kept on `.cause`.\nclass BnfParseError extends Error {\n  readonly line?: number\n  readonly column?: number\n  readonly cause?: unknown\n  constructor(message: string, location?: { line?: number; column?: number }, cause?: unknown) {\n    super(message)\n    this.name = 'BnfParseError'\n    this.line = location?.line\n    this.column = location?.column\n    this.cause = cause\n  }\n}\n\n\n// Parse BNF source into a grammar AST via the jsonic-based parser.\nfunction parseBnf(src: string): BnfGrammar {\n  const parser = getBnfParser()\n  let productions: BnfProduction[]\n  try {\n    productions = parser(src) ?? []\n  } catch (e: any) {\n    // JsonicError carries `lineNumber` / `columnNumber`; fall back to\n    // ad-hoc extraction from the error message otherwise.\n    const line = e?.lineNumber ?? e?.row\n    const column = e?.columnNumber ?? e?.col\n    const loc = (line != null && column != null)\n      ? ` at line ${line}, column ${column}`\n      : ''\n    const raw = e?.message ? String(e.message).split('\\n')[0] : String(e)\n    throw new BnfParseError(\n      `bnf: parse error${loc}: ${raw}`,\n      { line, column },\n      e,\n    )\n  }\n  if (!Array.isArray(productions) || productions.length === 0) {\n    throw new BnfParseError('bnf: no productions found')\n  }\n  const merged = mergeIncrementals(productions)\n  return { productions: withCoreRules(merged) }\n}\n\n\n// RFC 5234 Appendix B.1 core rules. Parsed lazily on first use\n// and spliced into any user grammar that references them but\n// doesn't define them locally.\nconst CORE_RULES_ABNF = `\nALPHA  = %x41-5A / %x61-7A\nBIT    = \"0\" / \"1\"\nCHAR   = %x01-7F\nCR     = %x0D\nLF     = %x0A\nCRLF   = CR LF\nCTL    = %x00-1F / %x7F\nDIGIT  = %x30-39\nDQUOTE = %x22\nHEXDIG = DIGIT / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\"\nHTAB   = %x09\nOCTET  = %x00-FF\nSP     = %x20\nVCHAR  = %x21-7E\nWSP    = SP / HTAB\n`\n\nlet _coreRules: Map<string, BnfProduction> | null = null\n\nfunction getCoreRules(): Map<string, BnfProduction> {\n  if (_coreRules) return _coreRules\n  const parser = getBnfParser()\n  const raw = parser(CORE_RULES_ABNF) as BnfProduction[]\n  // Core rules flatten to `src` in the output AST — they're\n  // character-class bricks, not structural nodes users want to see\n  // one-per-matched-character.\n  for (const p of raw) p.nodeKind = 'core'\n  _coreRules = new Map(raw.map((p) => [p.name, p]))\n  return _coreRules\n}\n\n\nfunction refsIn(alt: BnfSequence, out: Set<string>): void {\n  for (const el of alt) {\n    if (el.kind === 'ref') out.add(el.name)\n    else if (el.kind === 'opt' || el.kind === 'star' ||\n             el.kind === 'plus' || el.kind === 'rep') {\n      refsIn([el.inner], out)\n    } else if (el.kind === 'group') {\n      for (const a of el.alts) refsIn(a, out)\n    }\n  }\n}\n\n\n// Add each RFC 5234 core rule that the user's grammar references\n// but doesn't define locally. Resolution is transitive: if the\n// user mentions HEXDIG, DIGIT is pulled in too. User definitions\n// always win — a local `DIGIT = …` is left untouched.\nfunction withCoreRules(user: BnfProduction[]): BnfProduction[] {\n  const core = getCoreRules()\n  const defined = new Set(user.map((p) => p.name))\n  const needed = new Set<string>()\n\n  const scan = (prods: BnfProduction[]) => {\n    for (const p of prods) {\n      for (const alt of p.alts) refsIn(alt, needed)\n    }\n  }\n\n  scan(user)\n  const out: BnfProduction[] = []\n  // Transitively add core rules, in declaration order.\n  let added = true\n  while (added) {\n    added = false\n    for (const [name, prod] of core) {\n      if (defined.has(name)) continue\n      if (!needed.has(name)) continue\n      defined.add(name)\n      out.push(prod)\n      scan([prod])\n      added = true\n    }\n  }\n  return [...user, ...out]\n}\n\n\n// Fold every `name =/ alt` production into the earlier production\n// with the same name by appending its alternatives. Throws if an\n// incremental references a name that hasn't been defined yet — ABNF\n// requires the base production to appear first.\nfunction mergeIncrementals(prods: BnfProduction[]): BnfProduction[] {\n  const out: BnfProduction[] = []\n  const byName = new Map<string, BnfProduction>()\n  for (const p of prods) {\n    if (p.incremental) {\n      const base = byName.get(p.name)\n      if (!base) {\n        throw new BnfParseError(\n          `bnf: '${p.name} =/ …' has no earlier '${p.name} = …' to extend`,\n        )\n      }\n      base.alts.push(...p.alts)\n      continue\n    }\n    // Strip the (absent) flag on a cleanly-written production so\n    // downstream code never sees it.\n    const clean: BnfProduction = { name: p.name, alts: p.alts }\n    if (p.nodeKind) clean.nodeKind = p.nodeKind\n    out.push(clean)\n    byName.set(p.name, clean)\n  }\n  return out\n}\n\n\n// -- Probe-dispatch analyser + rewriter -----------------------------\n//\n// ABNF has a large family of grammars that aren't LL(k) for any\n// bounded k. The canonical example is RFC 3986's `authority`:\n//\n//   authority = [ userinfo \"@\" ] host [ \":\" port ]\n//   userinfo  = *( unreserved / pct-encoded / sub-delims / \":\" )\n//   host      = IP-literal / IPv4address / reg-name\n//   reg-name  = *( unreserved / pct-encoded / sub-delims )\n//\n// `userinfo` and `reg-name` share a character vocabulary, so a\n// FIRST-set dispatcher can't decide which branch the optional\n// `[ userinfo \"@\" ]` belongs to — the disambiguating `@` can be\n// arbitrarily far from the start.\n//\n// For the common pattern `[X D] Y` — an optional group whose body\n// ends with a terminal D, followed by a sequence Y whose leading\n// terminals overlap with X's — we handle the ambiguity by rewriting\n// the rule to a probe+phase-retry dispatcher:\n//\n//   1. On first entry (phase 0), mark the token position and push a\n//      failure-proof probe rule that greedily consumes every token\n//      in the joint vocabulary of X and Y.\n//   2. When the probe returns, peek ctx.t[0]:\n//        D seen   → phase = 1 (take the `X D Y` branch)\n//        D absent → phase = 2 (take the `Y` branch)\n//      Rewind to the mark and `r:` back into the dispatcher.\n//   3. The dispatcher's open has a `c:`-guarded alt for each phase\n//      that pushes the corresponding committed branch.\n//\n// The primitives used (`r:`, `k:`, `c:`, `ctx.mark`, `ctx.rewind`,\n// `ctx.t`) are the same building blocks rules/parser already exposes\n// — no new jsonic machinery is needed.\n\n\n// Predicate: element is `[ X D ]` where X is one or more elements\n// and D is a terminal literal or a regex terminal.\nfunction isProbeableOpt(el: BnfElement): null | {\n  xSeq: BnfSequence\n  disambiguator: BnfElement\n} {\n  if (el.kind !== 'opt') return null\n  const inner = el.inner\n  if (inner.kind !== 'group') return null\n  if (inner.alts.length !== 1) return null\n  const seq = inner.alts[0]\n  if (seq.length < 2) return null\n  const last = seq[seq.length - 1]\n  if (last.kind !== 'term' && last.kind !== 'regex') return null\n  return { xSeq: seq.slice(0, -1), disambiguator: last }\n}\n\n\n// Union of every terminal reachable by walking an element's subtree,\n// following refs transitively. Cycles are broken by the visited set.\n// Returns terminals as BnfElements so the caller isn't tied to the\n// emitter's token-allocation step.\nfunction collectTerminalVocabElements(\n  el: BnfElement,\n  grammar: BnfGrammar,\n  out: Map<string, BnfElement>,\n  visited: Set<string>,\n): void {\n  if (el.kind === 'term') {\n    const k = termKey(el)\n    if (!out.has(k)) out.set(k, el)\n    return\n  }\n  if (el.kind === 'regex') {\n    const k = regexKey(el)\n    if (!out.has(k)) out.set(k, el)\n    return\n  }\n  if (el.kind === 'ref') {\n    if (visited.has(el.name)) return\n    visited.add(el.name)\n    const prod = grammar.productions.find((p) => p.name === el.name)\n    if (!prod) return\n    for (const alt of prod.alts)\n      for (const sub of alt)\n        collectTerminalVocabElements(sub, grammar, out, visited)\n    return\n  }\n  if (el.kind === 'opt' || el.kind === 'star' || el.kind === 'plus' ||\n      el.kind === 'rep') {\n    collectTerminalVocabElements(el.inner, grammar, out, visited)\n    return\n  }\n  if (el.kind === 'group') {\n    for (const alt of el.alts)\n      for (const sub of alt)\n        collectTerminalVocabElements(sub, grammar, out, visited)\n    return\n  }\n}\n\n\nfunction collectSeqVocabElements(\n  seq: BnfSequence,\n  grammar: BnfGrammar,\n): Map<string, BnfElement> {\n  const out = new Map<string, BnfElement>()\n  const visited = new Set<string>()\n  for (const el of seq)\n    collectTerminalVocabElements(el, grammar, out, visited)\n  return out\n}\n\n\nfunction mapsOverlap<K, V>(a: Map<K, V>, b: Map<K, V>): boolean {\n  for (const x of a.keys()) if (b.has(x)) return true\n  return false\n}\n\n\n// Rewrite every ambiguous `[X D] Y` subsequence in `grammar` into a\n// probe-dispatch pattern. The grammar at this point still has `opt`,\n// `group`, `star`, `plus`, `rep` sugar — intentionally, since that's\n// where the pattern is easy to recognise. Runs BEFORE token\n// allocation; probe metadata stores BnfElements, and the emitter\n// resolves them to token names at emit time.\nfunction rewriteProbeDispatches(grammar: BnfGrammar): BnfGrammar {\n  const reports: AmbiguityReport[] = grammar.ambiguities ?? []\n  const extra: BnfProduction[] = []\n  const used = new Set<string>(grammar.productions.map((p) => p.name))\n\n  function freshName(hint: string): string {\n    let name = hint\n    let i = 1\n    while (used.has(name)) { name = hint + i; i++ }\n    used.add(name)\n    return name\n  }\n\n  const rewritten: BnfProduction[] = []\n\n  for (const prod of grammar.productions) {\n    let newAlts: BnfSequence[] = []\n    let touched = false\n    for (let altIdx = 0; altIdx < prod.alts.length; altIdx++) {\n      const alt = prod.alts[altIdx]\n      let resultAlt: BnfSequence = []\n      for (let i = 0; i < alt.length; i++) {\n        const el = alt[i]\n        const info = isProbeableOpt(el)\n        if (!info) { resultAlt.push(el); continue }\n        const ySeq = alt.slice(i + 1)\n        if (ySeq.length === 0) {\n          // `[X D]` is the last thing in the alt — nothing follows, so\n          // there's nothing to disambiguate against. Standard emit.\n          resultAlt.push(el); continue\n        }\n        const xVocab = collectSeqVocabElements(info.xSeq, grammar)\n        const yVocab = collectSeqVocabElements(ySeq, grammar)\n        if (!mapsOverlap(xVocab, yVocab)) {\n          // The optional's leading tokens don't overlap with the tail's\n          // leading tokens, so the normal FIRST-based dispatcher can\n          // decide. No rewrite needed.\n          resultAlt.push(el); continue\n        }\n\n        // Joint vocab: union of everything the probe might need to\n        // consume. Includes the disambiguator, which we then remove so\n        // the probe stops on it and the peek works.\n        const vocab = new Map<string, BnfElement>([...xVocab, ...yVocab])\n        const d = info.disambiguator\n        const dKey = d.kind === 'term' ? termKey(d)\n          : d.kind === 'regex' ? regexKey(d)\n          : null\n        if (dKey) vocab.delete(dKey)\n\n        const dispatchName = freshName(`${prod.name}$pd${i}`)\n        const probeName = freshName(`${dispatchName}$probe`)\n        const withName = freshName(`${dispatchName}$with`)\n        const noName = freshName(`${dispatchName}$no`)\n\n        // Synthesise the probe helper.\n        extra.push({\n          name: probeName,\n          alts: [],\n          probeHelper: { vocabElements: [...vocab.values()] },\n          nodeKind: 'helper',\n        })\n        // Synthesise the committed branches. `with` = X D Y, `no` = Y.\n        extra.push({\n          name: withName,\n          alts: [[...info.xSeq, info.disambiguator, ...ySeq]],\n          nodeKind: 'helper',\n        })\n        extra.push({\n          name: noName,\n          alts: [ySeq],\n          nodeKind: 'helper',\n        })\n        // Synthesise the dispatcher. The `alts` list is a \"virtual\"\n        // spec — two ref-only alts — that exists solely to feed\n        // computeFirstSets the right FIRST/nullable answers (FIRST\n        // = FIRST(with) ∪ FIRST(no)). The emitter checks\n        // `probeDispatch` first and emits the phase-retry body\n        // instead of compiling `alts`.\n        extra.push({\n          name: dispatchName,\n          alts: [\n            [{ kind: 'ref', name: withName }],\n            [{ kind: 'ref', name: noName }],\n          ],\n          probeDispatch: {\n            probeRule: probeName,\n            disambiguator: info.disambiguator,\n            withBranch: withName,\n            noBranch: noName,\n          },\n          nodeKind: 'helper',\n        })\n\n        reports.push({\n          rule: prod.name, altIdx, optIdx: i,\n          reason: `optional prefix shares vocabulary with tail`,\n          resolved: true,\n        })\n\n        resultAlt.push({ kind: 'ref', name: dispatchName })\n        // Everything that followed the opt is now inside the dispatcher\n        // (withBranch / noBranch), so skip the rest of the alt.\n        i = alt.length\n        touched = true\n      }\n      newAlts.push(resultAlt)\n    }\n    if (touched) {\n      rewritten.push({\n        name: prod.name,\n        alts: newAlts,\n        nodeKind: prod.nodeKind,\n      })\n    } else {\n      rewritten.push(prod)\n    }\n  }\n\n  return {\n    productions: [...rewritten, ...extra],\n    ambiguities: reports,\n  }\n}\n\n\n// Emit a probe helper production. A self-looping rule that matches any\n// one of the vocab tokens and restarts; a final empty-alt fallback\n// ensures the rule NEVER fails — if the current lookahead isn't in the\n// vocab (or we're at #ZZ), the rule pops cleanly. This is the\n// failure-proof property the probe pattern relies on.\nfunction emitProbeHelper(\n  prod: BnfProduction,\n  tag: string,\n  ruleSpec: NonNullable<GrammarSpec['rule']>,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n): void {\n  const elems = prod.probeHelper!.vocabElements\n  const opens: any[] = []\n  for (const el of elems) {\n    const tok = el.kind === 'term'\n      ? literals.get(termKey(el))\n      : el.kind === 'regex' ? regexTokens.get(regexKey(el))\n      : undefined\n    if (tok) opens.push({ s: tok, r: prod.name, g: tag })\n  }\n  // Empty fallback — pops without consuming anything. Must be last.\n  opens.push({ g: tag })\n  ruleSpec[prod.name] = { open: opens }\n}\n\n\n// Emit a probe-dispatch production. Encodes the three-phase retry\n// pattern; uses only standard jsonic primitives (r:, p:, c:, k:,\n// ctx.mark/rewind/t).\nfunction emitProbeDispatch(\n  prod: BnfProduction,\n  tag: string,\n  ruleSpec: NonNullable<GrammarSpec['rule']>,\n  refs: RefRegistry,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n): void {\n  const { probeRule, disambiguator, withBranch, noBranch } =\n    prod.probeDispatch!\n  const disambiguatorToken =\n    disambiguator.kind === 'term'\n      ? literals.get(termKey(disambiguator))\n      : disambiguator.kind === 'regex'\n        ? regexTokens.get(regexKey(disambiguator))\n        : undefined\n  if (!disambiguatorToken) {\n    throw new Error(\n      `bnf: probe-dispatch rule '${prod.name}' has unresolvable ` +\n      `disambiguator (kind=${disambiguator.kind})`)\n  }\n\n  const initMark = refs.register((r: Rule, ctx: any) => {\n    r.k.pd_phase = 0\n    r.k.pd_mark = ctx.mark()\n  })\n\n  const decide = refs.register((r: Rule, ctx: any) => {\n    // ctx.t[0] is the first token the probe didn't consume. The probe\n    // never fails, so this always reflects a real position.\n    const peek = ctx.t[0]\n    ctx.rewind(r.k.pd_mark)\n    const matched = peek && peek.name === disambiguatorToken\n    r.k.pd_phase = matched ? 1 : 2\n  })\n\n  const bubble = refs.register((r: Rule) => {\n    if (r.child && r.child.node !== undefined) r.node = r.child.node\n  })\n\n  ruleSpec[prod.name] = {\n    open: [\n      // Phase 0 — first pass: mark and probe.\n      {\n        c: refs.register((r: Rule) => !r.k.pd_phase),\n        a: initMark,\n        p: probeRule,\n        g: tag,\n      },\n      // Phase 1 — disambiguator was seen: commit to X D Y.\n      {\n        c: refs.register((r: Rule) => r.k.pd_phase === 1),\n        p: withBranch,\n        g: tag,\n      },\n      // Phase 2 — disambiguator was not seen: commit to Y alone.\n      {\n        c: refs.register((r: Rule) => r.k.pd_phase === 2),\n        p: noBranch,\n        g: tag,\n      },\n    ],\n    close: [\n      // Phase 0 close: decide phase based on peek, rewind, retry self.\n      {\n        c: refs.register((r: Rule) => r.k.pd_phase === 0),\n        a: decide,\n        r: prod.name,\n        g: tag,\n      },\n      // Phase 1 / 2 close: lift the committed child's node up.\n      { a: bubble, g: tag },\n    ],\n  }\n}\n\n\n// Convert a BNF grammar AST into a jsonic GrammarSpec.\nfunction emitGrammarSpec(\n  grammar: BnfGrammar,\n  opts?: BnfConvertOptions,\n): GrammarSpec {\n  const start = opts?.start ?? grammar.productions[0].name\n  const tag = opts?.tag ?? 'bnf'\n\n  // Eliminate direct left recursion (P → P α | β) by rewriting to\n  // the equivalent right-recursive form P → β (α)*, then detect\n  // ambiguous `[X D] Y` optional-prefix patterns and rewrite them\n  // into probe-dispatch helpers; finally flatten any EBNF sugar\n  // (`?`, `*`, `+`, grouping) into plain BNF.\n  grammar = eliminateLeftRecursion(grammar)\n  grammar = rewriteProbeDispatches(grammar)\n  grammar = desugar(grammar)\n\n  // Allocate a fixed token for each unique literal, and a match\n  // token for each unique regex terminal. Literals are keyed by\n  // (literal, effective-case-sensitivity) so a `%s\"foo\"` (sensitive)\n  // and a bare `\"foo\"` (insensitive) produce distinct tokens.\n  const literals = new Map<string, string>()        // literal-key -> token name\n  const regexTokens = new Map<string, string>()     // regex key -> token name\n  const usedNames = new Set<string>()\n  const fixedTokens: Record<string, string> = {}\n  const matchTokens: Record<string, RegExp> = {}\n  for (const prod of grammar.productions) {\n    for (const alt of prod.alts) {\n      for (const el of alt) {\n        if (el.kind === 'term') {\n          const key = termKey(el)\n          if (!literals.has(key)) {\n            const name = allocTokenName(el.literal, usedNames)\n            literals.set(key, name)\n            if (isEffectivelyCaseSensitive(el)) {\n              fixedTokens[name] = el.literal\n            } else {\n              // Insensitive literal with at least one letter — emit\n              // as an anchored regex with the `i` flag. Mark the\n              // matcher `eager$` so jsonic's lexer fires it even\n              // when the current rule's tcol doesn't list its tin.\n              const re = new RegExp(\n                '^' + escapeRegExp(el.literal),\n                'i',\n              ) as RegExp & { eager$?: boolean }\n              re.eager$ = true\n              matchTokens[name] = re\n            }\n          }\n        } else if (el.kind === 'regex') {\n          const key = regexKey(el)\n          if (!regexTokens.has(key)) {\n            const name = allocTokenName('rx_' + el.pattern, usedNames)\n            regexTokens.set(key, name)\n            matchTokens[name] = new RegExp('^' + el.pattern, el.flags)\n          }\n        }\n      }\n    }\n    // Probe-helper productions store their vocab as BnfElements —\n    // walk those too so the required tokens get allocated.\n    if (prod.probeHelper) {\n      for (const el of prod.probeHelper.vocabElements) {\n        if (el.kind === 'term') {\n          const key = termKey(el)\n          if (!literals.has(key)) {\n            const name = allocTokenName(el.literal, usedNames)\n            literals.set(key, name)\n            if (isEffectivelyCaseSensitive(el)) {\n              fixedTokens[name] = el.literal\n            } else {\n              const re = new RegExp(\n                '^' + escapeRegExp(el.literal),\n                'i',\n              ) as RegExp & { eager$?: boolean }\n              re.eager$ = true\n              matchTokens[name] = re\n            }\n          }\n        } else if (el.kind === 'regex') {\n          const key = regexKey(el)\n          if (!regexTokens.has(key)) {\n            const name = allocTokenName('rx_' + el.pattern, usedNames)\n            regexTokens.set(key, name)\n            matchTokens[name] = new RegExp('^' + el.pattern, el.flags)\n          }\n        }\n      }\n    }\n  }\n\n  const knownRules = new Set(grammar.productions.map((p) => p.name))\n  const { firstSets, nullable } = computeFirstSets(\n    grammar, literals, regexTokens)\n  const refs = new RefRegistry()\n\n  const ruleSpec: NonNullable<GrammarSpec['rule']> = {}\n  for (const prod of grammar.productions) {\n    if (prod.probeHelper) {\n      emitProbeHelper(prod, tag, ruleSpec, literals, regexTokens)\n      continue\n    }\n    if (prod.probeDispatch) {\n      emitProbeDispatch(prod, tag, ruleSpec, refs, literals, regexTokens)\n      continue\n    }\n    // Standard path: a (possibly single-segment) set of alternatives\n    // compiled to jsonic alts. Simple alts collapse into `open` alts\n    // directly; multi-segment alts emit a chain of aux rules.\n    emitProduction(\n      prod, grammar, literals, regexTokens, knownRules, tag, ruleSpec,\n      firstSets, nullable, refs,\n    )\n  }\n\n  // Wrap the user-visible start rule in a synthetic rule that\n  // explicitly consumes #ZZ. Without this, a user rule that pops\n  // without matching the end-of-source token lets trailing content\n  // slip past jsonic's post-loop endtkn check (the lookahead buffer\n  // outlives the parse loop).\n  const startWrapper = '__start__'\n  ruleSpec[startWrapper] = {\n    open: [{\n      p: start,\n      g: tag,\n    }],\n    close: [{\n      s: '#ZZ',\n      // Return the start rule's AST node directly — the `__start__`\n      // wrapper exists only to ensure end-of-source gets consumed.\n      // The caller of `jsonic(src)` receives the tagged user-rule\n      // node (e.g. `{rule: 'URI', src, kids: [...]}`) unadorned.\n      a: refs.register((r: Rule) => {\n        if (r.child && r.child.node !== undefined) {\n          r.node = r.child.node\n        }\n      }),\n      g: tag,\n    }],\n  }\n\n  const options: any = {\n    fixed: { token: fixedTokens },\n    rule: { start: startWrapper },\n  }\n  if (Object.keys(matchTokens).length > 0) {\n    options.match = { token: matchTokens }\n  }\n\n  const spec: GrammarSpec = {\n    ref: refs.map,\n    options,\n    rule: ruleSpec,\n  }\n\n  return spec\n}\n\n\ntype Segment = {\n  terms: string[]   // token names (e.g. '#HI')\n  ref: string | null // rule name to push after consuming terms\n}\n\n\n// Break an alternative into segments. Each segment is a (possibly\n// empty) run of terminal tokens followed by at most one rule\n// reference. A single-segment alt has at most one ref, located at the\n// very end; everything else has two or more segments.\nfunction segmentize(\n  alt: BnfSequence,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n): Segment[] {\n  const segs: Segment[] = []\n  let current: Segment = { terms: [], ref: null }\n  for (const el of alt) {\n    if (el.kind === 'term') {\n      current.terms.push(literals.get(termKey(el)) as string)\n    } else if (el.kind === 'regex') {\n      const key = regexKey(el)\n      current.terms.push(regexTokens.get(key) as string)\n    } else if (el.kind === 'ref') {\n      current.ref = el.name\n      segs.push(current)\n      current = { terms: [], ref: null }\n    } else {\n      // `opt`, `star`, `plus`, `group` must have been desugared\n      // before reaching the emitter.\n      throw new Error(\n        `bnf: internal — unexpected element kind '${el.kind}' in emitter`)\n    }\n  }\n  if (current.terms.length > 0 || segs.length === 0) {\n    segs.push(current)\n  }\n  return segs\n}\n\n\nfunction regexKey(el: { pattern: string; flags: string }): string {\n  return `/${el.pattern}/${el.flags}`\n}\n\n\nfunction isSingleSegment(alt: BnfSequence): boolean {\n  let sawRef = false\n  for (const el of alt) {\n    if (el.kind === 'ref') {\n      if (sawRef) return false\n      sawRef = true\n    } else if (el.kind === 'term' || el.kind === 'regex') {\n      if (sawRef) return false // terminal after a ref — multi-segment\n    } else {\n      // Desugar should have eliminated sugar kinds.\n      return false\n    }\n  }\n  return true\n}\n\n\nfunction validateRefs(\n  alt: BnfSequence,\n  knownRules: Set<string>,\n  ruleName: string,\n) {\n  for (const el of alt) {\n    if (el.kind === 'ref' && !knownRules.has(el.name)) {\n      throw new Error(\n        `bnf: rule '${ruleName}' references unknown rule '${el.name}'`)\n    }\n  }\n}\n\n\n// Registry used by the emitter to allocate unique `@`-prefixed\n// FuncRef names for inline action functions. The resulting spec is\n// still declarative: every function appears once, keyed by name,\n// under the spec's `ref` map.\nclass RefRegistry {\n  private refs: Record<string, Function> = {}\n  private counter = 0\n  register(fn: Function): `@${string}` {\n    const name = `@bnf_a${this.counter++}` as `@${string}`\n    this.refs[name] = fn\n    return name\n  }\n  get map(): Record<string, Function> {\n    return this.refs\n  }\n}\n\n\n// Output AST node shape. Every rule produces a `{src, kids}` object.\n// User-declared rules additionally carry a `rule` tag so callers can\n// navigate the tree by grammar-rule name. Core rules (ALPHA, DIGIT\n// …) and synthesised helpers (desugar / dispatcher / chain steps)\n// leave `rule` unset — their contribution flattens into the\n// enclosing user rule (src accumulates; kids extend).\ntype AstNode = {\n  rule?: string\n  src: string\n  kids: AstNode[]\n}\n\nfunction mkAstNode(ruleName: string, nodeKind: BnfProduction['nodeKind']): AstNode {\n  return nodeKind === 'user'\n    ? { rule: ruleName, src: '', kids: [] }\n    : { src: '', kids: [] }\n}\n\n\nfunction segmentToAlt(\n  seg: Segment,\n  tag: string,\n  refs: RefRegistry,\n  initNode: boolean,\n  ruleName: string,\n  nodeKind: BnfProduction['nodeKind'],\n): any {\n  const spec: any = { g: tag }\n  if (seg.terms.length > 0) spec.s = seg.terms.join(' ')\n  if (seg.ref) spec.p = seg.ref\n\n  // Default tree-building: accumulate each matched terminal's source\n  // text into `r.node.src`. Head alts also allocate a fresh AST node\n  // so the child doesn't inherit (and then mutate) its parent's.\n  const nterms = seg.terms.length\n  if (nterms > 0 || initNode) {\n    spec.a = refs.register((r: Rule) => {\n      if (initNode) r.node = mkAstNode(ruleName, nodeKind)\n      const n = r.node as AstNode\n      for (let i = 0; i < nterms; i++) n.src += r.o[i].src\n    })\n  }\n  return spec\n}\n\n\n// Close-state action: merge the just-returned child rule's AST node\n// into the current rule's. Tagged children (user rules) get pushed\n// verbatim into `kids`; untagged (helper / core) flatten — their\n// `src` appends and their `kids` extend. Either way `src`\n// concatenates so every ancestor's `.src` reflects everything it\n// matched.\nfunction captureChildRef(\n  refs: RefRegistry,\n  ruleName: string,\n  nodeKind: BnfProduction['nodeKind'],\n): `@${string}` {\n  return refs.register((r: Rule) => {\n    if (r.node == null) r.node = mkAstNode(ruleName, nodeKind)\n    const n = r.node as AstNode\n    const c = r.child && r.child.node as AstNode | undefined\n    if (c == null) return\n    if (typeof c !== 'object' || !('src' in c)) {\n      // Legacy shape — wrap as a leaf kid.\n      n.kids.push(c as any)\n      return\n    }\n    // Defensive: if the child somehow shares this rule's node\n    // object, skip the merge rather than push a self-reference. (A\n    // properly-emitted grammar always allocates fresh child nodes.)\n    if (c === n) return\n    n.src += c.src\n    if (c.rule) n.kids.push(c)\n    else if (Array.isArray(c.kids)) n.kids.push(...c.kids)\n  })\n}\n\n\nfunction emitProduction(\n  prod: BnfProduction,\n  grammar: BnfGrammar,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  knownRules: Set<string>,\n  tag: string,\n  ruleSpec: NonNullable<GrammarSpec['rule']>,\n  firstSets: Map<string, Set<string>>,\n  nullable: Set<string>,\n  refs: RefRegistry,\n) {\n  for (const alt of prod.alts) {\n    validateRefs(alt, knownRules, prod.name)\n  }\n\n  const allSimple = prod.alts.every(isSingleSegment)\n\n  if (allSimple) {\n    // Every alternative collapses to one jsonic alt — emit them\n    // directly into the production's open state. This is a head\n    // rule, so each alt initialises its own node array. Empty alts\n    // are sorted to the end so jsonic's first-match-wins doesn't let\n    // them short-circuit non-empty alternatives.\n    const ordered = [\n      ...prod.alts.filter((alt) => alt.length > 0),\n      ...prod.alts.filter((alt) => alt.length === 0),\n    ]\n\n    // Ref-only alternatives have no terminal to discriminate on, so\n    // jsonic's first-match-wins would silently let them shadow any\n    // later alternative. Guard them with FIRST-set peeks when the\n    // production has more than one alt.\n    const needsPeek = ordered.length > 1\n    const opens: any[] = []\n    for (const alt of ordered) {\n      const segs = segmentize(alt, literals, regexTokens)\n      const seg = segs[0]\n      const isRefOnly = alt.length >= 1 &&\n        alt.every((el) => el.kind === 'ref') &&\n        seg.terms.length === 0 &&\n        seg.ref != null\n\n      const prodKind = prod.nodeKind ?? 'user'\n      if (needsPeek && isRefOnly) {\n        const firstTokens = firstOfAlt(\n          alt, literals, regexTokens, firstSets, nullable)\n        if (firstTokens) {\n          for (const tok of firstTokens) {\n            opens.push({\n              s: tok,\n              b: 1,\n              p: seg.ref,\n              a: refs.register((r: Rule) => {\n                r.node = mkAstNode(prod.name, prodKind)\n              }),\n              g: tag,\n            })\n          }\n          continue\n        }\n      }\n      opens.push(segmentToAlt(seg, tag, refs, true, prod.name, prodKind))\n    }\n\n    const rs: any = { open: opens }\n\n    // If any alt has a push, the close state must capture the\n    // returned child. Add a universal fallback close alt whose\n    // action is a no-op when there was no push.\n    if (prod.alts.some((alt) => alt.some((el) => el.kind === 'ref'))) {\n      rs.close = [{\n        a: captureChildRef(refs, prod.name, prod.nodeKind ?? 'user'),\n        g: tag,\n      }]\n    }\n    ruleSpec[prod.name] = rs\n    return\n  }\n\n  if (prod.alts.length === 1) {\n    // Single-alt, multi-segment: chain rules directly on the\n    // production.\n    emitChain(prod.name, prod.alts[0], literals, regexTokens, tag,\n      ruleSpec, refs, prod.nodeKind ?? 'user')\n    return\n  }\n\n  // Multi-alt with at least one multi-segment alternative: emit a\n  // dispatcher. Each alt becomes its own chained impl rule\n  // (`<prodname>$alt<i>`); the main rule's open peeks the first token\n  // and pushes the matching impl rule. Using `p:` (not `r:`) keeps\n  // the parent's `child` pointer valid so the parent can read the\n  // impl's node in its close-state action.\n  const dispatchOpen: any[] = []\n  let emptyAltSeen = false\n\n  for (let i = 0; i < prod.alts.length; i++) {\n    const alt = prod.alts[i]\n    const implName = `${prod.name}$alt${i}`\n\n    if (alt.length === 0) {\n      // Empty alt acts as fallback — handled after the loop.\n      emptyAltSeen = true\n      continue\n    }\n\n    emitChain(implName, alt, literals, regexTokens, tag, ruleSpec, refs,\n      'helper')\n\n    // Fan out this alt into one dispatch entry per concrete token\n    // sequence it can start with. Up to LOOKAHEAD_K tokens per\n    // prefix is enough for the grammars this converter targets; a\n    // ref with multiple alts produces one prefix per sub-alt so\n    // overlapping FIRST sets between competing alts can still be\n    // separated by their second (or later) token.\n    // The dispatcher itself is a user (or helper) rule — it must\n    // allocate its own AST node on every dispatch alt, otherwise the\n    // node inherited from the parent via makeRule(ctx, rule.node)\n    // would be shared and the dispatcher's captureChildRef would\n    // mutate the parent's tree.\n    const dispatchKind = prod.nodeKind ?? 'user'\n    const initDispatchNode = refs.register((r: Rule) => {\n      r.node = mkAstNode(prod.name, dispatchKind)\n    })\n\n    const LOOKAHEAD_K = 4\n    const prefixes = altPrefixes(\n      alt, grammar, literals, regexTokens, LOOKAHEAD_K)\n    const usable = prefixes.filter((p) => p.length > 0)\n    if (usable.length > 0) {\n      for (const p of usable) {\n        dispatchOpen.push({\n          s: p.join(' '),\n          b: p.length,\n          p: implName,\n          a: initDispatchNode,\n          g: tag,\n        })\n      }\n    } else {\n      const firstTokens = firstOfAlt(\n        alt, literals, regexTokens, firstSets, nullable)\n      if (firstTokens === null) {\n        throw new Error(\n          `bnf: rule '${prod.name}' alternative ${i} is nullable ` +\n          `but is not the only empty alt; FIRST set is ambiguous`)\n      }\n      for (const tok of firstTokens) {\n        dispatchOpen.push({\n          s: tok, b: 1, p: implName, a: initDispatchNode, g: tag,\n        })\n      }\n    }\n  }\n\n  if (emptyAltSeen) {\n    // Fallback: matches any token (or none), pops immediately with\n    // an empty tree. Tagged with the user rule name so a consumer\n    // walking the tree still gets a placeholder node for the empty\n    // alternative.\n    const fallbackKind = prod.nodeKind ?? 'user'\n    dispatchOpen.push({\n      a: refs.register((r: Rule) => {\n        r.node = mkAstNode(prod.name, fallbackKind)\n      }),\n      g: tag,\n    })\n  }\n\n  ruleSpec[prod.name] = {\n    open: dispatchOpen,\n    close: [{\n      // Merge the chosen impl's result up into the dispatcher's node,\n      // tagged with the user rule name (so the enclosing rule sees a\n      // `{rule, src, kids}` child, not the impl chain's transparent\n      // `{src, kids}`).\n      a: captureChildRef(refs, prod.name, prod.nodeKind ?? 'user'),\n      g: tag,\n    }],\n  }\n}\n\n\n// Emit a (possibly single-step) chain of rules for one alt under the\n// given head rule name. Segment 0 goes into `headName`; later\n// segments get synthetic `<headName>$stepN` continuations.\n//\n// `headKind` controls the head rule's AST node shape: 'user' tags\n// the head's node with the rule name; 'helper' leaves it untagged\n// (transparent to the enclosing user rule). Step rules are always\n// helpers — they inherit and accumulate into the head's node via\n// `r:` replacement.\nfunction emitChain(\n  headName: string,\n  alt: BnfSequence,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  tag: string,\n  ruleSpec: NonNullable<GrammarSpec['rule']>,\n  refs: RefRegistry,\n  headKind: BnfProduction['nodeKind'] = 'helper',\n) {\n  const segs = segmentize(alt, literals, regexTokens)\n  const chainName = (i: number) =>\n    i === 0 ? headName : `${headName}$step${i}`\n\n  for (let i = 0; i < segs.length; i++) {\n    const name = chainName(i)\n    const seg = segs[i]\n    const kind = i === 0 ? headKind : 'helper'\n    // Only the head of the chain initialises the node object; later\n    // steps inherit and continue to accumulate into it via `r:`.\n    const open = [segmentToAlt(seg, tag, refs, i === 0, name, kind)]\n    const rs: any = { open }\n\n    const isLast = i === segs.length - 1\n    if (!isLast) {\n      // Non-last step: after the push returns, capture the child's\n      // node and replace with the next step rule.\n      rs.close = [{\n        r: chainName(i + 1),\n        a: captureChildRef(refs, name, kind),\n        g: tag,\n      }]\n    } else if (seg.ref) {\n      // Last step, but it had a push — we still need to capture the\n      // final child before popping.\n      rs.close = [{ a: captureChildRef(refs, name, kind), g: tag }]\n    }\n    ruleSpec[name] = rs\n  }\n}\n\n\n// Compute FIRST(ref) for every production, plus which productions\n// are nullable (can derive the empty string). Iterates to a fixed\n// point. Terminals in FIRST sets are represented by their allocated\n// token names (e.g. `#X`).\nfunction computeFirstSets(\n  grammar: BnfGrammar,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n): { firstSets: Map<string, Set<string>>; nullable: Set<string> } {\n  const firstSets = new Map<string, Set<string>>()\n  const nullable = new Set<string>()\n  for (const p of grammar.productions) firstSets.set(p.name, new Set())\n\n  let changed = true\n  while (changed) {\n    changed = false\n    for (const prod of grammar.productions) {\n      const first = firstSets.get(prod.name) as Set<string>\n      for (const alt of prod.alts) {\n        // Walk the alt, accumulating FIRST until a non-nullable\n        // position is hit.\n        let altNullable = true\n        for (const el of alt) {\n          if (el.kind === 'term' || el.kind === 'regex') {\n            const tok = el.kind === 'term'\n              ? literals.get(termKey(el)) as string\n              : regexTokens.get(regexKey(el)) as string\n            if (!first.has(tok)) { first.add(tok); changed = true }\n            altNullable = false\n            break\n          }\n          if (el.kind === 'ref') {\n            const refFirst = firstSets.get(el.name) ?? new Set<string>()\n            for (const tok of refFirst) {\n              if (!first.has(tok)) { first.add(tok); changed = true }\n            }\n            if (!nullable.has(el.name)) {\n              altNullable = false\n              break\n            }\n            continue\n          }\n          // Desugar should have eliminated other kinds.\n          throw new Error(`bnf: internal — unexpected kind in FIRST: ${el.kind}`)\n        }\n        if (altNullable && !nullable.has(prod.name)) {\n          nullable.add(prod.name)\n          changed = true\n        }\n      }\n    }\n  }\n\n  return { firstSets, nullable }\n}\n\n\n// FIRST set for a specific alternative (not the whole production).\n// Returns null if the alt is nullable — the caller must treat that\n// case separately (typically as a fallback empty alt).\nfunction firstOfAlt(\n  alt: BnfSequence,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  firstSets: Map<string, Set<string>>,\n  nullable: Set<string>,\n): Set<string> | null {\n  const out = new Set<string>()\n  for (const el of alt) {\n    if (el.kind === 'term' || el.kind === 'regex') {\n      const tok = el.kind === 'term'\n        ? literals.get(termKey(el)) as string\n        : regexTokens.get(regexKey(el)) as string\n      out.add(tok)\n      return out\n    }\n    if (el.kind === 'ref') {\n      const rf = firstSets.get(el.name) ?? new Set<string>()\n      for (const tok of rf) out.add(tok)\n      if (!nullable.has(el.name)) return out\n      // else keep walking into the next element\n      continue\n    }\n    throw new Error(`bnf: internal — unexpected kind in firstOfAlt: ${el.kind}`)\n  }\n  // Alt is nullable — no non-empty prefix.\n  return null\n}\n\n\n// Longest deterministic terminal prefix of a rule — the longest\n// sequence of tokens that every alternative of the rule starts\n// with. Refs are followed into their target rule, with a `visited`\n// set guarding cycles. An empty array means there's no confident\n// prefix (the rule either has divergent alts, starts with a multi-\n// alt ref, or hits a cycle), so the caller should fall back to a\n// single-token FIRST-set lookahead instead.\nfunction ruleLiteralPrefix(\n  name: string,\n  grammar: BnfGrammar,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  visited: Set<string>,\n): string[] {\n  if (visited.has(name)) return []\n  const next = new Set(visited); next.add(name)\n  const prod = grammar.productions.find((p) => p.name === name)\n  if (!prod || prod.alts.length === 0) return []\n\n  const prefixes = prod.alts.map((alt) =>\n    altLiteralPrefix(alt, grammar, literals, regexTokens, next))\n  if (prefixes.some((p) => p.length === 0)) return []\n  const minLen = Math.min(...prefixes.map((p) => p.length))\n  const common: string[] = []\n  for (let i = 0; i < minLen; i++) {\n    const tok = prefixes[0][i]\n    if (prefixes.every((p) => p[i] === tok)) common.push(tok)\n    else break\n  }\n  return common\n}\n\n\nfunction altLiteralPrefix(\n  alt: BnfSequence,\n  grammar: BnfGrammar,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  visited: Set<string>,\n): string[] {\n  const out: string[] = []\n  for (const el of alt) {\n    if (el.kind === 'term') {\n      out.push(literals.get(termKey(el)) as string)\n    } else if (el.kind === 'regex') {\n      out.push(regexTokens.get(regexKey(el)) as string)\n    } else if (el.kind === 'ref') {\n      const sub = ruleLiteralPrefix(\n        el.name, grammar, literals, regexTokens, visited)\n      // Take the ref's literal prefix and stop — we can't see past\n      // the ref without more expensive analysis.\n      out.push(...sub)\n      return out\n    } else {\n      return out\n    }\n  }\n  return out\n}\n\n\ntype PrefixPath = { tokens: string[]; done: boolean }\n\n\n// Enumerate concrete token-sequence prefixes an alternative can\n// start with, each at most `maxK` tokens long. Refs with multiple\n// alternatives fan out into one prefix per sub-alternative so the\n// caller can emit a dedicated dispatch alt for each path. When a\n// ref cycles back or exhausts depth, the path is *terminated* at\n// the tokens accumulated so far — the `done` flag is propagated\n// out of nested calls so a truncated sub-prefix is never extended\n// with tokens from elements the outer alt happens to list after the\n// cycled ref.\nfunction altPrefixesRaw(\n  alt: BnfSequence,\n  grammar: BnfGrammar,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  maxK: number,\n  visited: Set<string> = new Set(),\n): PrefixPath[] {\n  let paths: PrefixPath[] = [{ tokens: [], done: false }]\n\n  for (const el of alt) {\n    const next: PrefixPath[] = []\n    for (const p of paths) {\n      if (p.done || p.tokens.length >= maxK) { next.push(p); continue }\n      if (el.kind === 'term') {\n        next.push({\n          tokens: [...p.tokens, literals.get(termKey(el)) as string],\n          done: false,\n        })\n      } else if (el.kind === 'regex') {\n        next.push({\n          tokens: [...p.tokens, regexTokens.get(regexKey(el)) as string],\n          done: false,\n        })\n      } else if (el.kind === 'ref') {\n        if (visited.has(el.name)) {\n          next.push({ tokens: p.tokens, done: true })\n          continue\n        }\n        const childVisited = new Set(visited); childVisited.add(el.name)\n        const target = grammar.productions.find((pr) => pr.name === el.name)\n        if (!target || target.alts.length === 0) {\n          next.push({ tokens: p.tokens, done: true })\n          continue\n        }\n        for (const sub of target.alts) {\n          const subPaths = altPrefixesRaw(\n            sub, grammar, literals, regexTokens,\n            maxK - p.tokens.length, childVisited)\n          for (const sp of subPaths) {\n            next.push({\n              tokens: [...p.tokens, ...sp.tokens],\n              // Propagate `done` so the outer loop won't extend a\n              // cycle-truncated sub-prefix.\n              done: sp.done,\n            })\n          }\n        }\n      } else {\n        // Desugar should have eliminated group/star/etc. at this point.\n        next.push({ tokens: p.tokens, done: true })\n      }\n    }\n    paths = next\n    if (paths.every((p) => p.done || p.tokens.length >= maxK)) break\n  }\n\n  return paths\n}\n\n\nfunction altPrefixes(\n  alt: BnfSequence,\n  grammar: BnfGrammar,\n  literals: Map<string, string>,\n  regexTokens: Map<string, string>,\n  maxK: number,\n): string[][] {\n  const raw = altPrefixesRaw(alt, grammar, literals, regexTokens, maxK)\n  const seen = new Set<string>()\n  const out: string[][] = []\n  for (const p of raw) {\n    const key = p.tokens.join(' ')\n    if (!seen.has(key)) { seen.add(key); out.push(p.tokens) }\n  }\n  return out\n}\n\n\n// A quoted-string literal is effectively case-sensitive either\n// when the user explicitly wrote `%s\"…\"` or when it contains no\n// ASCII letters (there's nothing to fold — `\"+\"` matches `+` in\n// any \"case\").\nfunction isEffectivelyCaseSensitive(el: {\n  literal: string\n  caseSensitive?: boolean\n}): boolean {\n  if (el.caseSensitive === true) return true\n  return !/[A-Za-z]/.test(el.literal)\n}\n\n\n// Map a term element to the key used to look up (or allocate) its\n// emitted token. The key folds together the literal and its\n// effective case-sensitivity so a sensitive and an insensitive\n// occurrence of the same string are distinct tokens.\nfunction termKey(el: { literal: string; caseSensitive?: boolean }): string {\n  return (isEffectivelyCaseSensitive(el) ? 'cs:' : 'ci:') + el.literal\n}\n\n\nfunction escapeRegExp(s: string): string {\n  return s.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&')\n}\n\n\n// Decode an ABNF numeric value (`%xNN`, `%dNN`, `%bNN`, or one of\n// the range/concatenation forms) into a `BnfElement`.\n//\n//   %x61            => single-char term \"a\"\n//   %x66.6f.6f      => concatenated term \"foo\"\n//   %x30-39         => regex character class [\\u0030-\\u0039]\n//\n// Hex is case-insensitive; decimal and binary accept only digits\n// in their respective ranges. Range endpoints must be the same\n// base as the prefix (RFC 5234 doesn't allow mixing).\nfunction parseNumericValue(src: string): BnfElement {\n  const base = src[1].toLowerCase()\n  const radix = base === 'x' ? 16 : base === 'd' ? 10 : 2\n  const body = src.slice(2)\n\n  if (body.includes('-')) {\n    const [loStr, hiStr] = body.split('-')\n    const lo = parseInt(loStr, radix)\n    const hi = parseInt(hiStr, radix)\n    if (lo === hi) {\n      return { kind: 'term', literal: String.fromCharCode(lo) }\n    }\n    const toEsc = (n: number) =>\n      '\\\\u' + n.toString(16).padStart(4, '0')\n    return {\n      kind: 'regex',\n      pattern: '[' + toEsc(lo) + '-' + toEsc(hi) + ']',\n      flags: '',\n    }\n  }\n\n  const parts = body.split('.')\n  const chars = parts.map((n) => String.fromCharCode(parseInt(n, radix)))\n  return { kind: 'term', literal: chars.join('') }\n}\n\n\nfunction allocTokenName(literal: string, used: Set<string>): string {\n  const base = literal\n    .replace(/[^A-Za-z0-9]/g, '_')\n    .toUpperCase()\n    .replace(/^_+|_+$/g, '')\n  const candidate = base.length > 0 ? '#' + base : '#T'\n  if (!used.has(candidate)) {\n    used.add(candidate)\n    return candidate\n  }\n  let i = 1\n  while (used.has(candidate + i)) i++\n  const chosen = candidate + i\n  used.add(chosen)\n  return chosen\n}\n\n\n// Public entry point: take BNF source and return a jsonic GrammarSpec.\nfunction bnf(src: string, opts?: BnfConvertOptions): GrammarSpec {\n  const grammar = parseBnf(src)\n  return emitGrammarSpec(grammar, opts)\n}\n\n\nexport {\n  bnf,\n  parseBnf,\n  emitGrammarSpec,\n  eliminateLeftRecursion,\n  bnfRules,\n  BnfParseError,\n}\n"
  },
  {
    "path": "src/debug.ts",
    "content": "/* Copyright (c) 2021-2023 Richard Rodger, MIT License */\n\n/*  debug.ts\n *  Debug tools\n */\n\nimport type {\n  Context,\n  NormAltSpec,\n  Config,\n  AltMatch,\n  Jsonic,\n  Plugin,\n  RuleSpec,\n  Rule,\n  Lex,\n  Point,\n  LexMatcher,\n  Token,\n} from './jsonic'\n\nimport { S, util, EMPTY } from './jsonic'\n\n\n// TODO: custom stringify for nodes\ntype DebugOptions = {\n  print: boolean\n  trace: Record<string, boolean> & {\n    step: boolean,\n    rule: boolean,\n    lex: boolean,\n    parse: boolean,\n    node: boolean,\n    stack: boolean,\n  }\n}\n\n\nconst DEFAULTS: DebugOptions = {\n  print: true,\n  trace: {\n    step: true,\n    rule: true,\n    lex: true,\n    parse: true,\n    node: true,\n    stack: true,\n  },\n}\n\nconst { entries, tokenize } = util\n\nconst Debug: Plugin = (jsonic: Jsonic, options: DebugOptions) => {\n  options.trace =\n    true === (options.trace as any) ? { ...DEFAULTS.trace } : options.trace\n\n  const { keys, values, entries } = jsonic.util\n\n  jsonic.debug = {\n    describe: function(): string {\n      let cfg = jsonic.internal().config\n      let match = cfg.lex.match\n      let rules = jsonic.rule()\n\n      return [\n        '========= TOKENS ========',\n        Object.entries(cfg.t)\n          .filter((te) => 'string' === typeof te[1])\n          .map((te) => {\n            return (\n              '  ' +\n              te[0] +\n              '\\t' +\n              te[1] +\n              '\\t' +\n              ((s: string) => (s ? '\"' + s + '\"' : ''))(\n                cfg.fixed.ref[te[0] as string] || '',\n              )\n            )\n          })\n          .join('\\n'),\n        '\\n',\n\n        Object.entries(cfg.tokenSet)\n          .map((te) => {\n            return (\n              '    ' +\n              te[0] +\n              '\\t' +\n              Object.keys(cfg.tokenSetTins[te[0]] ?? [])\n            )\n          })\n          .join('\\n'),\n        '\\n',\n\n        ,\n\n        '========= RULES =========',\n        ruleTree(jsonic, keys(rules), rules),\n        '\\n',\n\n        '========= ALTS =========',\n        values(rules)\n          .map(\n            (rs: any) =>\n              '  ' +\n              rs.name +\n              ':\\n' +\n              descAlt(jsonic, rs, 'open') +\n              descAlt(jsonic, rs, 'close'),\n          )\n          .join('\\n\\n'),\n\n        '\\n',\n        '========= LEXER =========',\n        '  ' +\n        (\n          (match &&\n            match.map(\n              (m: any) =>\n                m.order + ': ' + m.matcher + ' (' + m.make.name + ')',\n            )) ||\n          []\n        ).join('\\n  '),\n        '\\n',\n\n        '\\n',\n        '========= PLUGIN =========',\n        '  ' +\n        jsonic\n          .internal()\n          .plugins.map(\n            (p: Plugin) =>\n              p.name +\n              (p.options\n                ? entries(p.options).reduce(\n                  (s: string, e: any[]) =>\n                    (s += '\\n    ' + e[0] + ': ' + JSON.stringify(e[1])),\n                  '',\n                )\n                : ''),\n          )\n          .join('\\n  '),\n        '\\n',\n      ].join('\\n')\n    },\n  }\n\n  const origUse = jsonic.use.bind(jsonic)\n\n  jsonic.use = (...args) => {\n    let self = origUse(...args)\n    if (options.print) {\n      self\n        .internal()\n        .config.debug.get_console()\n        .log('USE:', args[0].name, '\\n\\n', self.debug.describe())\n    }\n    return self\n  }\n\n\n  if (options.trace) {\n    jsonic.options({\n      parse: {\n        prepare: {\n          debug: (_jsonic: Jsonic, ctx: Context, _meta: any) => {\n            const console_log = ctx.cfg.debug.get_console().log\n            console_log('\\n========= TRACE ==========')\n            ctx.log =\n              ctx.log ||\n              ((kind: string, ...rest: any) => {\n                if (LOGKIND[kind] && options.trace[kind]) {\n                  console_log(\n                    LOGKIND[kind](...rest)\n                      .filter((item: any) => 'object' != typeof item)\n                      .map((item: any) =>\n                        'function' == typeof item ? item.name : item,\n                      )\n                      .join('  '),\n                  )\n                }\n              })\n          },\n        },\n      },\n    })\n  }\n}\n\nfunction descAlt(jsonic: Jsonic, rs: RuleSpec, kind: 'open' | 'close') {\n  const { entries } = jsonic.util\n\n  return 0 === rs.def[kind].length\n    ? ''\n    : '    ' +\n    kind.toUpperCase() +\n    ':\\n' +\n    rs.def[kind]\n      .map(\n        (a: any, i: number) =>\n          '      ' +\n          ('' + i).padStart(5, ' ') +\n          ' ' +\n          (\n            '[' +\n            (a.s || [])\n              .map((tin: any) =>\n                null == tin\n                  ? '***INVALID***'\n                  : 'number' === typeof tin\n                    ? jsonic.token[tin]\n                    : Array.isArray(tin) ? '[' + tin.map((t: any) => jsonic.token[t]) + ']'\n                      : ('' + tin),\n              )\n              .join(' ') +\n            '] '\n          ).padEnd(32, ' ') +\n          (a.r ? ' r=' + ('string' === typeof a.r ? a.r : '<F>') : '') +\n          (a.p ? ' p=' + ('string' === typeof a.p ? a.p : '<F>') : '') +\n          (!a.r && !a.p ? '\\t' : '') +\n          '\\t' +\n          (null == a.b ? '' : 'b=' + a.b) +\n          '\\t' +\n          (null == a.n\n            ? ''\n            : 'n=' +\n            entries(a.n).map(([k, v]: [string, any]) => k + ':' + v)) +\n          '\\t' +\n          (null == a.a ? '' : 'A') +\n          (null == a.c ? '' : 'C') +\n          (null == a.h ? '' : 'H') +\n          '\\t' +\n          (null == a.c?.n\n            ? '\\t'\n            : ' CN=' +\n            entries(a.c.n).map(([k, v]: [string, any]) => k + ':' + v)) +\n          (null == a.c?.d ? '' : ' CD=' + a.c.d) +\n          (a.g ? '\\tg=' + a.g : ''),\n      )\n      .join('\\n') +\n    '\\n'\n}\n\nfunction ruleTree(jsonic: Jsonic, rn: string[], rsm: any) {\n  const { values, omap } = jsonic.util\n\n  return rn.reduce(\n    (a: any, n: string) => (\n      (a +=\n        '  ' +\n        n +\n        ':\\n    ' +\n        values(\n          omap(\n            {\n              op: ruleTreeStep(rsm, n, 'open', 'p'),\n              or: ruleTreeStep(rsm, n, 'open', 'r'),\n              cp: ruleTreeStep(rsm, n, 'close', 'p'),\n              cr: ruleTreeStep(rsm, n, 'close', 'r'),\n            },\n            ([n, d]: [string, string]) => [\n              1 < d.length ? n : undefined,\n              n + ': ' + d,\n            ],\n          ),\n        ).join('\\n    ') +\n        '\\n'),\n      a\n    ),\n    '',\n  )\n}\n\nfunction ruleTreeStep(\n  rsm: any,\n  name: string,\n  state: 'open' | 'close',\n  step: 'p' | 'r',\n) {\n  return [\n    ...new Set(\n      rsm[name].def[state]\n        .filter((alt: any) => alt[step])\n        .map((alt: any) => alt[step])\n        .map((step: any) => ('string' === typeof step ? step : '<F>')),\n    ),\n  ].join(' ')\n}\n\nfunction descTokenState(ctx: Context) {\n  return (\n    '[' +\n    (ctx.NOTOKEN === ctx.t0 ? '' : ctx.F(ctx.t0.src)) +\n    (ctx.NOTOKEN === ctx.t1 ? '' : ' ' + ctx.F(ctx.t1.src)) +\n    ']~[' +\n    (ctx.NOTOKEN === ctx.t0 ? '' : tokenize(ctx.t0.tin, ctx.cfg)) +\n    (ctx.NOTOKEN === ctx.t1 ? '' : ' ' + tokenize(ctx.t1.tin, ctx.cfg)) +\n    ']'\n  )\n}\n\nfunction descParseState(ctx: Context, rule: Rule, lex: Lex) {\n  return (\n    ctx.F(ctx.src().substring(lex.pnt.sI, lex.pnt.sI + 16)).padEnd(18, ' ') +\n    ' ' +\n    descTokenState(ctx).padEnd(34, ' ') +\n    ' ' +\n    ('' + rule.d).padStart(4, ' ')\n  )\n}\n\nfunction descRuleState(ctx: Context, rule: Rule) {\n  let en = entries(rule.n)\n  let eu = entries(rule.u)\n  let ek = entries(rule.k)\n\n  return (\n    '' +\n    (0 === en.length\n      ? ''\n      : ' N<' +\n      en\n        .filter((n: any) => n[1])\n        .map((n: any) => n[0] + '=' + n[1])\n        .join(';') +\n      '>') +\n    (0 === eu.length\n      ? ''\n      : ' U<' + eu.map((u: any) => u[0] + '=' + ctx.F(u[1])).join(';') + '>') +\n    (0 === ek.length\n      ? ''\n      : ' K<' + ek.map((k: any) => k[0] + '=' + ctx.F(k[1])).join(';') + '>')\n  )\n}\n\nfunction descAltSeq(alt: NormAltSpec, cfg: Config) {\n  return (\n    '[' +\n    (alt.s || [])\n      .map((tin: any) =>\n        'number' === typeof tin\n          ? tokenize(tin, cfg)\n          : Array.isArray(tin)\n            ? '[' + tin.map((t: any) => tokenize(t, cfg)) + ']'\n            : '',\n      )\n      .join(' ') +\n    '] '\n  )\n}\n\nconst LOG = {\n  RuleState: {\n    o: S.open.toUpperCase(),\n    c: S.close.toUpperCase(),\n  },\n}\n\nconst LOGKIND: any = {\n  step: (...rest: any[]) => rest,\n\n  stack: (ctx: Context, rule: Rule, lex: Lex) => [\n    S.logindent + S.stack,\n    descParseState(ctx, rule, lex),\n\n    // S.indent.repeat(Math.max(rule.d + ('o' === rule.state ? -1 : 1), 0)) +\n    S.indent.repeat(rule.d) +\n    '/' +\n    ctx.rs\n      // .slice(0, ctx.rsI)\n      .slice(0, rule.d)\n      .map((r: Rule) => r.name + '~' + r.i)\n      .join('/'),\n\n    '~',\n\n    '/' +\n    ctx.rs\n      // .slice(0, ctx.rsI)\n      .slice(0, rule.d)\n      .map((r: Rule) => ctx.F(r.node))\n      .join('/'),\n\n    // 'd=' + rule.d,\n    //'rsI=' + ctx.rsI,\n\n    ctx,\n    rule,\n    lex,\n  ],\n\n  rule: (ctx: Context, rule: Rule, lex: Lex) => [\n    rule,\n    ctx,\n    lex,\n\n    S.logindent + S.rule + S.space,\n    descParseState(ctx, rule, lex),\n\n    S.indent.repeat(rule.d) +\n    (rule.name + '~' + rule.i + S.colon + LOG.RuleState[rule.state]).padEnd(\n      16,\n    ),\n\n    (\n      'prev=' +\n      rule.prev.i +\n      ' parent=' +\n      rule.parent.i +\n      ' child=' +\n      rule.child.i\n    ).padEnd(28),\n\n    descRuleState(ctx, rule),\n  ],\n\n  node: (ctx: Context, rule: Rule, lex: Lex, next: Rule) => [\n    rule,\n    ctx,\n    lex,\n    next,\n\n    S.logindent + S.node + S.space,\n    descParseState(ctx, rule, lex),\n\n    S.indent.repeat(rule.d) +\n    ('why=' + next.why + S.space + '<' + ctx.F(rule.node) + '>').padEnd(46),\n\n    descRuleState(ctx, rule),\n  ],\n\n  parse: (\n    ctx: Context,\n    rule: Rule,\n    lex: Lex,\n    match: boolean,\n    cond: boolean,\n    altI: number,\n    alt: NormAltSpec | null,\n    out: AltMatch,\n  ) => {\n    let ns = match && out.n ? entries(out.n) : null\n    let us = match && out.u ? entries(out.u) : null\n    let ks = match && out.k ? entries(out.k) : null\n\n    return [\n      ctx,\n      rule,\n      lex,\n\n      S.logindent + S.parse,\n      descParseState(ctx, rule, lex),\n      S.indent.repeat(rule.d) + (match ? 'alt=' + altI : 'no-alt'),\n\n      match && alt ? descAltSeq(alt, ctx.cfg) : '',\n\n      match && out.g ? 'g:' + out.g + ' ' : '',\n      (match && out.p ? 'p:' + out.p + ' ' : '') +\n      (match && out.r ? 'r:' + out.r + ' ' : '') +\n      (match && out.b ? 'b:' + out.b + ' ' : ''),\n\n      alt && alt.c ? 'c:' + cond : EMPTY,\n      null == ns ? '' : 'n:' + ns.map((p: any) => p[0] + '=' + p[1]).join(';'),\n\n      null == us ? '' : 'u:' + us.map((p: any) => p[0] + '=' + p[1]).join(';'),\n\n      null == ks ? '' : 'k:' + ks.map((p: any) => p[0] + '=' + p[1]).join(';'),\n    ]\n  },\n\n  lex: (\n    ctx: Context,\n    rule: Rule,\n    lex: Lex,\n    pnt: Point,\n    sI: number,\n    match: LexMatcher | undefined,\n    tkn: Token,\n    alt?: NormAltSpec,\n    altI?: number,\n    tI?: number,\n  ) => [\n      S.logindent + S.lex + S.space + S.space,\n      descParseState(ctx, rule, lex),\n      S.indent.repeat(rule.d) +\n      // S.indent.repeat(rule.d) + S.lex, // Log entry prefix.\n\n      // Name of token from tin (token identification numer).\n      tokenize(tkn.tin, ctx.cfg),\n\n      ctx.F(tkn.src), // Format token src for log.\n      pnt.sI, // Current source index.\n      pnt.rI + ':' + pnt.cI, // Row and column.\n      match?.name || '',\n\n      alt\n        ? 'on:alt=' +\n        altI +\n        ';' +\n        alt.g +\n        ';t=' +\n        tI +\n        ';' +\n        descAltSeq(alt, ctx.cfg)\n        : '',\n\n      ctx.F(lex.src.substring(sI, sI + 16)),\n\n      ctx,\n      rule,\n      lex,\n    ],\n}\n\nDebug.defaults = DEFAULTS as DebugOptions\n\nexport { Debug }\n"
  },
  {
    "path": "src/defaults.ts",
    "content": "/* Copyright (c) 2013-2023 Richard Rodger, MIT License */\n\n/*  defaults.ts\n *  Default option values.\n */\n\nimport { Options } from './jsonic'\n\n// Functions that create token matching lexers.\n// The `make*Matcher` functions may optionally initialise\n// and validate Config properties specific to their lexing.\nimport {\n  makeMatchMatcher,\n  makeFixedMatcher,\n  makeSpaceMatcher,\n  makeLineMatcher,\n  makeStringMatcher,\n  makeCommentMatcher,\n  makeNumberMatcher,\n  makeTextMatcher,\n} from './lexer'\n\n\nconst defaults: Options = {\n  // Prevent prototype pollution\n  safe: {\n    key: true,\n  },\n\n  // Default tag - set your own!\n  tag: '-',\n\n  // Fixed token lexing.\n  fixed: {\n    // Recognize fixed tokens in the Lexer.\n    lex: true,\n\n    // Token names.\n    token: {\n      '#OB': '{',\n      '#CB': '}',\n      '#OS': '[',\n      '#CS': ']',\n      '#CL': ':',\n      '#CA': ',',\n    },\n  },\n\n  match: {\n    lex: true,\n    token: {},\n  },\n\n  // Token sets.\n  tokenSet: {\n    IGNORE: ['#SP', '#LN', '#CM'],\n    VAL: ['#TX', '#NR', '#ST', '#VL'],\n    KEY: ['#TX', '#NR', '#ST', '#VL'],\n  },\n\n  // Recognize space characters in the lexer.\n  space: {\n    // Recognize space in the Lexer.\n    lex: true,\n\n    // Space characters are kept to a minimal set.\n    // Add more from https://en.wikipedia.org/wiki/Whitespace_character as needed.\n    chars: ' \\t',\n  },\n\n  // Line lexing.\n  line: {\n    // Recognize lines in the Lexer.\n    lex: true,\n\n    // Line characters.\n    chars: '\\r\\n',\n\n    // Increments row (aka line) counter.\n    rowChars: '\\n',\n\n    // Generate separate lexer tokens for each newline.\n    // Note: '\\r\\n' counts as one newline.\n    single: false,\n  },\n\n  // Text formats.\n  text: {\n    // Recognize text (non-quoted strings) in the Lexer.\n    lex: true,\n  },\n\n  // Control number formats.\n  number: {\n    // Recognize numbers in the Lexer.\n    lex: true,\n\n    // Recognize hex numbers (eg. 10 === 0x0a).\n    hex: true,\n\n    // Recognize octal numbers (eg. 10 === 0o12).\n    oct: true,\n\n    // Recognize ninary numbers (eg. 10 === 0b1010).\n    bin: true,\n\n    // All possible number chars. |+-|0|xob|0-9a-fA-F|.e|+-|0-9a-fA-F|\n    // digital: '-1023456789._xoeEaAbBcCdDfF+',\n\n    // Allow embedded separator. `null` to disable.\n    sep: '_',\n\n    // Exclude number strings matching this RegExp\n    exclude: undefined,\n  },\n\n  // Comment markers.\n  // <mark-char>: true -> single line comments\n  // <mark-start>: <mark-end> -> multiline comments\n  comment: {\n    // Recognize comments in the Lexer.\n    lex: true,\n\n    // TODO: plugin\n    // Balance multiline comments.\n    // balance: true,\n\n    // Comment markers.\n    def: {\n      hash: { line: true, start: '#', lex: true, eatline: false },\n      slash: { line: true, start: '//', lex: true, eatline: false },\n      multi: {\n        line: false,\n        start: '/' + '*',\n        end: '*' + '/',\n        lex: true,\n        eatline: false,\n      },\n    },\n  },\n\n  // String formats.\n  string: {\n    // Recognize strings in the Lexer.\n    lex: true,\n\n    // Quote characters\n    chars: '\\'\"`',\n\n    // Multiline quote chars.\n    multiChars: '`',\n\n    // Escape character.\n    escapeChar: '\\\\',\n\n    // String escape chars.\n    // Denoting char (follows escape char) => actual char.\n    escape: {\n      b: '\\b',\n      f: '\\f',\n      n: '\\n',\n      r: '\\r',\n      t: '\\t',\n      v: '\\v',\n\n      // These preserve standard escapes when allowUnknown=false.\n      '\"': '\"',\n      \"'\": \"'\",\n      '`': '`',\n      '\\\\': '\\\\',\n      '/': '/',\n    },\n\n    // Allow unknown escape characters - they are copied to output: '\\w' -> 'w'.\n    allowUnknown: true,\n\n    // If string lexing fails, instead of error, allow other matchers to try.\n    abandon: false,\n  },\n\n  // Object formats.\n  map: {\n    // TODO: or trigger error?\n    // Later duplicates extend earlier ones, rather than replacing them.\n    extend: true,\n\n    // Custom merge function for duplicates (optional).\n    // TODO: needs function signature\n    merge: undefined,\n\n    // Allow bare colon `:value` in maps, stored as `child$` property.\n    child: false,\n  },\n\n  // Array formats.\n  list: {\n    // Allow arrays to have properties: `[a:9,0,1]`\n    property: true,\n\n    // Parse pairs as object elements: `[a:1]` -> `[{\"a\":1}]`\n    // Takes precedence over list.property when true.\n    pair: false,\n\n    // Parse bare colon as child$ property: `[:1]` -> [] with child$=1\n    // Multiple child values merge.\n    child: false,\n  },\n\n  // Metadata info markers. When enabled, a non-enumerable marker property\n  // is attached to parsed nodes with metadata (implicit flag, meta bag, etc.).\n  info: {\n    // Attach marker to map nodes.\n    map: false,\n    // Attach marker to list nodes.\n    list: false,\n    // Wrap string values as String objects with marker (quote info).\n    text: false,\n    // Property name for the marker.\n    marker: '__info__',\n  },\n\n  // Keyword values.\n  value: {\n    lex: true,\n    def: {\n      true: { val: true },\n      false: { val: false },\n      null: { val: null },\n    },\n  },\n\n  // Additional text ending characters\n  ender: [],\n\n  // Plugin custom options, (namespace by plugin name).\n  plugin: {},\n\n  // Debug settings\n  debug: {\n    // Default console for logging.\n    get_console: () => console,\n\n    // Max length of parse value to print.\n    maxlen: 99,\n\n    // Print internal structures\n    print: {\n      // Print config built from options.\n      config: false,\n\n      // Custom string formatter for src and node values.\n      src: undefined,\n    },\n  },\n\n  // Error messages.\n  error: {\n    unknown: 'unknown error: {code}',\n    unexpected: 'unexpected character(s): {src}',\n    invalid_unicode: 'invalid unicode escape: {src}',\n    invalid_ascii: 'invalid ascii escape: {src}',\n    unprintable: 'unprintable character: {src}',\n    unterminated_string: 'unterminated string: {src}',\n    unterminated_comment: 'unterminated comment: {src}',\n    unknown_rule: 'unknown rule: {rulename}',\n    end_of_source: 'unexpected end of source',\n  },\n\n  errmsg: {\n    name: 'jsonic',\n    suffix: true\n  },\n\n  // Error hints: {error-code: hint-text}.\n  hint: {\n    unknown: `\nSince the error is unknown, this is probably a bug inside jsonic\nitself, or a plugin. Please consider posting a github issue - thanks!\n\nCode: {code}, Details: \n{details}`,\n\n    unexpected: `\nThe character(s) {src} were not expected at this point as they do not\nmatch the expected syntax, even under the relaxed jsonic rules. If it\nis not obviously wrong, the actual syntax error may be elsewhere. Try\ncommenting out larger areas around this point until you get no errors,\nthen remove the comments in small sections until you find the\noffending syntax. NOTE: Also check if any plugins you are using\nexpect different syntax in this case.`,\n\n    invalid_unicode: `\nThe escape sequence {src} does not encode a valid unicode code point\nnumber. You may need to validate your string data manually using test\ncode to see how JavaScript will interpret it. Also consider that your\ndata may have become corrupted, or the escape sequence has not been\ngenerated correctly.`,\n\n    invalid_ascii: `\nThe escape sequence {src} does not encode a valid ASCII character. You\nmay need to validate your string data manually using test code to see\nhow JavaScript will interpret it. Also consider that your data may\nhave become corrupted, or the escape sequence has not been generated\ncorrectly.`,\n\n    unprintable: `\nString values cannot contain unprintable characters (character codes\nbelow 32). The character {src} is unprintable. You may need to remove\nthese characters from your source data. Also check that it has not\nbecome corrupted.`,\n\n    unterminated_string: `\nThis string has no end quote.`,\n\n    unterminated_comment: `\nThis comment is never closed.`,\n\n    unknown_rule: `\nNo rule named $rulename is defined. This is probably an error in the\ngrammar of a plugin.`,\n\n    end_of_source: `\nUnexpected end of source.`,\n  },\n\n  // Lexer\n  lex: {\n    match: {\n      match: { order: 1e6, make: makeMatchMatcher },\n      fixed: { order: 2e6, make: makeFixedMatcher },\n      space: { order: 3e6, make: makeSpaceMatcher },\n      line: { order: 4e6, make: makeLineMatcher },\n      string: { order: 5e6, make: makeStringMatcher },\n      comment: { order: 6e6, make: makeCommentMatcher },\n      number: { order: 7e6, make: makeNumberMatcher },\n      text: { order: 8e6, make: makeTextMatcher },\n    },\n\n    // Empty string is allowed and returns undefined\n    empty: true,\n    emptyResult: undefined,\n  },\n\n  // Parser\n  parse: {\n    // Plugin custom functions to prepare parser context.\n    prepare: {},\n  },\n\n  // Parser rule options.\n  rule: {\n    // Name of the starting rule.\n    start: 'val',\n\n    // Automatically close remaining structures at EOF.\n    finish: true,\n\n    // Multiplier to increase the maximum number of rule occurences.\n    maxmul: 3,\n\n    // Include only those alts with matching group tags (comma sep).\n    // NOTE: applies universally, thus also for subsequent rules.\n    include: '',\n\n    // Exclude alts with matching group tags (comma sep).\n    // NOTE: applies universally, thus also for subsequent rules.\n    exclude: '',\n  },\n\n  // Result value options.\n  result: {\n    // Fail if result matches any of these.\n    fail: [],\n  },\n\n  // Token-rewind options. `history` bounds how many consumed tokens\n  // are retained on ctx.v for ctx.rewind(). The default of 64 keeps\n  // parse-time memory bounded for large inputs; raise it if a\n  // grammar needs to rewind further, or set to Infinity to retain\n  // every consumed token. ctx.rewind(mark) throws if `mark` falls\n  // outside the retained window.\n  rewind: {\n    history: 64,\n  },\n\n  // Configuration options.\n  config: {\n    // Configuration modifiers.\n    modify: {},\n  },\n\n  // Provide a custom parser.\n  parser: {\n    start: undefined,\n  },\n}\n\nexport { defaults }\n"
  },
  {
    "path": "src/error.ts",
    "content": "/* Copyright (c) 2013-2024 Richard Rodger, MIT License */\n\n/*  error.ts\n *  Error handling functions and classes.\n */\n\nimport type {\n  Bag,\n  Config,\n  Context,\n  Rule,\n  Token,\n} from './types'\n\nimport { EMPTY, STRING } from './types'\nimport { makeToken, makePoint } from './lexer'\nimport { assign, deep, entries, keys, escre, tokenize } from './utility'\n\nconst S = {\n  function: 'function',\n  object: 'object',\n  string: 'string',\n  unexpected: 'unexpected',\n  Object: 'Object',\n  Array: 'Array',\n  gap: '  ',\n  no_re_flags: EMPTY,\n}\n\n\n// Jsonic errors with nice formatting.\nclass JsonicError extends SyntaxError {\n  constructor(\n    code: string,\n    details: Bag,\n    token: Token,\n    rule: Rule,\n    ctx: Context,\n  ) {\n    details = deep({}, details)\n    let desc = errdesc(code, details, token, rule, ctx)\n    super(desc.message)\n    assign(this, desc)\n  }\n}\n\n\n// Inject value text into an error message. The value is taken from\n// the `details` parameter to JsonicError. If not defined, the value is\n// determined heuristically from the Token and Context.\nfunction errinject<T extends string | string[] | { [key: string]: string }>(\n  s: T,\n  code: string,\n  details: Bag,\n  token: Token,\n  rule: Rule,\n  ctx: Context,\n): T {\n  let ref: any = {\n    ...(ctx || {}),\n    ...(ctx.cfg || {}),\n    ...(ctx.opts || {}),\n    ...(token || {}),\n    ...(rule || {}),\n    ...(ctx.meta || {}),\n    ...(details || {}),\n    ...{ code, details, token, rule, ctx },\n  }\n  return strinject(s, ref, { indent: '  ' })\n}\n\n// Remove Jsonic internal lines as spurious for caller.\nfunction trimstk(err: Error) {\n  if (err.stack) {\n    err.stack = err.stack\n      .split('\\n')\n      .filter((s) => !s.includes('jsonic/jsonic'))\n      .map((s) => s.replace(/    at /, 'at '))\n      .join('\\n')\n  }\n}\n\n// Extract error site in source text and mark error point. */\nfunction errsite(spec: {\n  src: string\n  sub?: string\n  msg?: string\n  row?: number\n  col?: number\n  pos?: number\n  cline?: string\n}) {\n  let { src, sub, msg, cline, row, col, pos } = spec\n  row = null != row && 0 < row ? row : 1\n  col = null != col && 0 < col ? col : 1\n\n  pos =\n    null != pos && 0 < pos\n      ? pos\n      : null == src\n        ? 0\n        : src\n          .split('\\n')\n          .reduce(\n            (pos, line, i) => (\n              (pos +=\n                i < row - 1 ? line.length + 1 : i === row - 1 ? col : 0),\n              pos\n            ),\n            0,\n          )\n\n  let tsrc = null == sub ? EMPTY : sub\n  let behind = src.substring(Math.max(0, pos - 333), pos).split('\\n')\n  let ahead = src.substring(pos, pos + 333).split('\\n')\n\n  let pad = 2 + (EMPTY + (row + 2)).length\n  let rc = row < 3 ? 1 : row - 2\n  let ln = (s: string) =>\n    (null == cline ? '' : cline) +\n    (EMPTY + rc++).padStart(pad, ' ') +\n    ' | ' +\n    (null == cline ? '' : '\\x1b[0m') +\n    (null == s ? EMPTY : s)\n\n  let blen = behind.length\n\n  let lines = [\n    2 < blen ? ln(behind[blen - 3]) : null,\n    1 < blen ? ln(behind[blen - 2]) : null,\n    ln(behind[blen - 1] + ahead[0]),\n    ' '.repeat(pad) +\n    '   ' +\n    ' '.repeat(col - 1) +\n    (null == cline ? '' : cline) +\n    '^'.repeat(tsrc.length || 1) +\n    ' ' +\n    msg +\n    (null == cline ? '' : '\\x1b[0m'),\n    ln(ahead[1]),\n    ln(ahead[2]),\n  ]\n    .filter((line: any) => null != line)\n    .join('\\n')\n\n  return lines\n}\n\n\nfunction errmsg(spec: {\n  code?: string\n  name?: string\n\n  txts?: {\n    msg?: string\n    hint?: string\n    site?: string\n  }\n\n  smsg?: string\n  src?: string\n  file?: string\n  row?: number\n  col?: number\n  pos?: number\n  site?: string\n  sub?: string\n  prefix?: string | Function\n  suffix?: string | Function\n  color?: { active?: boolean, reset?: string; hi?: string; lo?: string; line?: string }\n}) {\n\n  const color = {\n    active: false,\n    reset: '',\n    hi: '',\n    lo: '',\n    line: '',\n  }\n\n  if (spec.color && spec.color.active) {\n    Object.assign(color, spec.color)\n  }\n\n  const txts = {\n    msg: null,\n    hint: null,\n    site: null,\n    ...(spec.txts || {})\n  }\n\n  let message = [\n    null == spec.prefix\n      ? null\n      : 'function' === typeof spec.prefix\n        ? spec.prefix(color, spec)\n        : '' + spec.prefix,\n\n    (null == spec.code\n      ? ''\n      : color.hi +\n      '[' +\n      (null == spec.name ? '' : spec.name + '/') +\n      spec.code +\n      ']:') +\n    color.reset +\n    ' ' +\n    // (null == spec.msg ? '' : spec.msg),\n    (null == txts.msg ? '' : txts.msg),\n\n    (null != spec.row && null != spec.col) || null != spec.file\n      ? '  ' +\n      color.line +\n      '-->' +\n      color.reset +\n      ' ' +\n      (null == spec.file ? '<no-file>' : spec.file) +\n      (null == spec.row || null == spec.col\n        ? ''\n        : ':' + spec.row + ':' + spec.col)\n      : null,\n\n    null == spec.src\n      ? ''\n      : (null == txts.site ? '' : errsite({\n        src: spec.src,\n        sub: spec.sub,\n        msg: spec.smsg || spec.txts?.msg,\n        cline: color.line,\n        row: spec.row,\n        col: spec.col,\n        pos: spec.pos,\n      })),\n\n    '',\n\n    // null == spec.hint ? null : spec.hint,\n    // txts.hint,\n    (null == txts.hint ? '' : txts.hint),\n\n    null == spec.suffix\n      ? null\n      : 'function' === typeof spec.suffix\n        ? spec.suffix(color, spec)\n        : '' + spec.suffix,\n  ]\n    .filter((n) => null != n)\n    .join('\\n')\n\n  return message\n}\n\n\nfunction errdesc(\n  code: string,\n  details: Bag,\n  token: Token,\n  rule: Rule,\n  ctx: Context,\n): Bag {\n  try {\n    const src = ctx.src()\n    const cfg = ctx.cfg\n    const meta = ctx.meta\n    const txts = errinject(\n      {\n        msg:\n          cfg.error[code] ||\n          (details?.use?.err &&\n            (details.use.err.code || details.use.err.message)) ||\n          cfg.error.unknown,\n\n        hint: (\n          cfg.hint[code] ||\n          details.use?.err?.message ||\n          cfg.hint.unknown ||\n          ''\n        )\n          .trim()\n          .split('\\n')\n          .map((s: string) => '  ' + s)\n          .join('\\n'),\n        site: '',\n      },\n      code,\n      details,\n      token,\n      rule,\n      ctx,\n    )\n\n\n    txts.site = errsite({\n      src,\n      msg: txts.msg,\n      cline: cfg.color.active ? cfg.color.line : '',\n      row: token.rI,\n      col: token.cI,\n      pos: token.sI,\n      sub: token.src,\n\n    })\n\n    const suffix =\n      true === cfg.errmsg.suffix ? (color: any) =>\n        [\n          '',\n          '  ' + color.lo + 'https://jsonic.senecajs.org' + color.reset + '',\n          '  ' +\n          color.lo +\n          '--internal: tag=' +\n          (ctx.opts.tag || '') +\n          '; rule=' +\n          rule.name +\n          '~' +\n          rule.state +\n          '; token=' +\n          tokenize(token.tin, ctx.cfg) +\n          (null == token.why ? '' : '~' + token.why) +\n          '; plugins=' +\n          ctx\n            .plgn()\n            .map((p: any) => p.name)\n            .join(',') +\n          '--' +\n          color.reset,\n        ].join('\\n') :\n        ('string' === typeof cfg.errmsg.suffix || 'function' === typeof cfg.errmsg.suffix) ?\n          cfg.errmsg.suffix :\n          undefined\n\n    let message = errmsg({\n      code,\n      // name: 'jsonic',\n      name: cfg.errmsg.name,\n      txts,\n      src,\n      file: meta ? meta.fileName : undefined,\n      row: token.rI,\n      col: token.cI,\n      pos: token.sI,\n      sub: token.src,\n      color: cfg.color,\n      suffix,\n    })\n\n    let desc: any = {\n      internal: {\n        token,\n        ctx,\n      },\n    }\n\n    desc = {\n      ...Object.create(desc),\n      message,\n      code,\n      details,\n      meta,\n      fileName: meta ? meta.fileName : undefined,\n      lineNumber: token.rI,\n      columnNumber: token.cI,\n      txts: () => txts\n    }\n\n    return desc\n  }\n  catch (e) {\n    // TODO: fix\n    console.log(e)\n    return {}\n  }\n}\n\n\n// Inject value into text by key using \"{key}\" syntax.\nfunction strinject<T extends string | string[] | { [key: string]: string }>(\n  s: T,\n  m: Bag,\n  f?: { indent?: string },\n): T {\n  let st = typeof s\n  let t = Array.isArray(s)\n    ? 'array'\n    : null == s\n      ? 'string'\n      : 'object' === st\n        ? st\n        : 'string'\n  let so =\n    'object' === t\n      ? s\n      : 'array' === t\n        ? (s as string[]).reduce((a: any, n, i) => ((a[i] = n), a), {})\n        : { _: s }\n  let mo = null == m ? {} : m\n\n  Object.entries(so).map(\n    (n: any[]) =>\n    (so[n[0]] =\n      null == n[1]\n        ? ''\n        : ('' + n[1]).replace(\n          /\\{([\\w_0-9.]+)}/g,\n          (match: any, keypath: string) => {\n            let inject = prop(mo, keypath)\n            inject = undefined === inject ? match : inject\n\n            if ('object' === typeof inject) {\n              let cn = inject?.constructor?.name\n              if ('Object' === cn || 'Array' === cn) {\n                inject = JSON.stringify(inject).replace(/([^\"])\"/g, '$1')\n              } else {\n                inject = inject.toString()\n              }\n            } else {\n              inject = '' + inject\n            }\n\n            if (f) {\n              if ('string' === typeof f.indent) {\n                inject = inject.replace(/\\n/g, '\\n' + f.indent)\n              }\n            }\n\n            return inject\n          },\n        )),\n  )\n\n  return ('string' === t ? so._ : 'array' === t ? Object.values(so) : so) as T\n}\n\nfunction prop(obj: any, path: string, val?: any): any {\n  let root = obj\n  try {\n    let parts = path.split('.')\n    let pn: any\n    for (let pI = 0; pI < parts.length; pI++) {\n      pn = parts[pI]\n      if ('__proto__' === pn) {\n        throw new Error(pn)\n      }\n      if (pI < parts.length - 1) {\n        obj = obj[pn] = obj[pn] || {}\n      }\n    }\n    if (undefined !== val) {\n      if ('__proto__' === pn) {\n        throw new Error(pn)\n      }\n      obj[pn] = val\n    }\n    return obj[pn]\n  } catch (e: any) {\n    throw new Error(\n      'Cannot ' +\n      (undefined === val ? 'get' : 'set') +\n      ' path ' +\n      path +\n      ' on object: ' +\n      str(root) +\n      (undefined === val ? '' : ' to value: ' + str(val, 22)),\n    )\n  }\n}\n\nfunction str(o: any, len: number = 44) {\n  let s\n  try {\n    s = 'object' === typeof o ? JSON.stringify(o) : '' + o\n  } catch (e: any) {\n    s = '' + o\n  }\n  return snip(len < s.length ? s.substring(0, len - 3) + '...' : s, len)\n}\n\nfunction snip(s: any, len: number = 5) {\n  return undefined === s\n    ? ''\n    : ('' + s).substring(0, len).replace(/[\\r\\n\\t]/g, '.')\n}\n\nexport {\n  JsonicError,\n  errdesc,\n  errinject,\n  errsite,\n  errmsg,\n  trimstk,\n  strinject,\n  prop,\n}\n"
  },
  {
    "path": "src/grammar.ts",
    "content": "/* Copyright (c) 2013-2024 Richard Rodger, MIT License */\n\n/*  grammar.ts\n *  Grammar definition.\n *\n *  First, a pure JSON grammar is defined. Then it is extended to provide the\n *  Jsonic format.\n */\n\nimport { Jsonic, Rule, RuleSpec, Context, Parser, FuncRef } from './jsonic'\n\nconst defprop = Object.defineProperty\n\nfunction mark(node: any, marker: string, data: any): void {\n  if (node != null && typeof node === 'object') {\n    defprop(node, marker, { value: data, writable: true })\n  }\n}\n\nfunction grammar(jsonic: Jsonic) {\n  const { deep } = jsonic.util\n\n  const {\n    // Fixed tokens\n    // OB, // Open Brace `{`\n    // CB, // Close Brace `}`\n    // OS, // Open Square `[`\n    // CS, // Close Square `]`\n    // CL, // Colon `:`\n    CA, // Comma `,`\n\n    // Complex tokens\n    TX, // Text (unquoted character sequence)\n    ST, // String (quoted character sequence)\n\n    // Control tokens\n    ZZ, // End-of-source\n  } = jsonic.token\n\n  const {\n    VAL, // All tokens that make up values\n    // KEY, // All tokens that make up keys\n  } = jsonic.tokenSet\n\n  const fnm: Record<FuncRef, Function> = {\n    '@finish': (_rule: Rule, ctx: Context) => {\n      if (!ctx.cfg.rule.finish) {\n        // TODO: pass missing end char for replacement in error message\n        ctx.t0.err = 'end_of_source'\n        return ctx.t0\n      }\n    },\n\n    // TODO: define a way to \"export\" rule actions or other functions so that\n    // other plugins can use them.\n    '@pairkey': (r: Rule) => {\n      // Get key string value from first matching token of `Open` state.\n      const key_token = r.o0\n      const key =\n        ST === key_token.tin || TX === key_token.tin\n          ? key_token.val // Was text\n          : key_token.src // Was number, use original text\n\n      r.u.key = key\n    },\n  }\n\n\n  // Plain JSON\n  // ----------\n\n  jsonic.grammar({\n    ref: {\n      '@finish': (_rule: Rule, ctx: Context) => {\n        if (!ctx.cfg.rule.finish) {\n          // TODO: pass missing end char for replacement in error message\n          ctx.t0.err = 'end_of_source'\n          return ctx.t0\n        }\n      },\n\n      // TODO: define a way to \"export\" rule actions or other functions so that\n      // other plugins can use them.\n      '@pairkey': (r: Rule) => {\n        // Get key string value from first matching token of `Open` state.\n        const key_token = r.o0\n        const key =\n          ST === key_token.tin || TX === key_token.tin\n            ? key_token.val // Was text\n            : key_token.src // Was number, use original text\n\n        r.u.key = key\n      },\n\n      '@val-bo': (rule: Rule) => (rule.node = undefined),\n      '@val-bc': (r: Rule, ctx: Context) => {\n        // NOTE: val can be undefined when there is no value at all\n        // (eg. empty string, thus no matched opening token)\n        r.node =\n          // If there's no node,\n          undefined === r.node\n            ? // ... or no child node (child map or list),\n            undefined === r.child.node\n              ? // ... or no matched tokens,\n              0 === r.os\n                ? // ... then the node has no value\n                undefined\n                : // .. otherwise use the token value\n                (() => {\n                  let val = r.o0.resolveVal(r, ctx)\n                  if (ctx.cfg.info.text &&\n                    typeof val === 'string' &&\n                    (r.o0.tin === ctx.cfg.t.ST || r.o0.tin === ctx.cfg.t.TX)) {\n                    let quote = r.o0.tin === ctx.cfg.t.ST && r.o0.src.length > 0\n                      ? r.o0.src[0] : ''\n                    let sv = new String(val)\n                    mark(sv, ctx.cfg.info.marker, { quote })\n                    val = sv as any\n                  }\n                  return val\n                })()\n              : r.child.node\n            : r.node\n      },\n\n      '@map-bo': (r: Rule, ctx: Context) => {\n        // Create a new empty map.\n        r.node = Object.create(null)\n        if (ctx.cfg.info.map) {\n          mark(r.node, ctx.cfg.info.marker, { implicit: false, meta: {} })\n        }\n      },\n\n      '@list-bo': (r: Rule, ctx: Context) => {\n        // Create a new empty list.\n        r.node = []\n        if (ctx.cfg.info.list) {\n          mark(r.node, ctx.cfg.info.marker, { implicit: false, meta: {} })\n        }\n      },\n\n      '@pair-bc': (r: Rule, ctx: Context) => {\n        if (r.u.pair) {\n          // Drop keys that match the info marker to preserve metadata.\n          if (ctx.cfg.info.map && r.u.key === ctx.cfg.info.marker) {\n            return\n          }\n          // Store previous value (if any, for extensions).\n          r.u.prev = r.node[r.u.key]\n          r.node[r.u.key] = r.child.node\n        }\n      },\n\n      '@elem-bc': (r: Rule) => {\n        if (true !== r.u.done && undefined !== r.child.node) {\n          r.node.push(r.child.node)\n        }\n      },\n    },\n\n\n    rule: {\n      val: {\n\n        // Opening token alternates.\n        open: [\n          // A map: `{ ...`\n          { s: '#OB', p: 'map', b: 1, g: 'map,json' },\n\n          // A list: `[ ...`\n          { s: '#OS', p: 'list', b: 1, g: 'list,json' },\n\n          // A plain value: `x` `\"x\"` `1` `true` ....\n          { s: '#VAL', g: 'val,json' },\n        ],\n\n        // Closing token alternates.\n        close: [\n          // End of input.\n          { s: '#ZZ', g: 'end,json' },\n\n          // There's more JSON.\n          { b: 1, g: 'more,json' },\n        ]\n      },\n\n\n      map: {\n        open: [\n          // An empty map: {}.\n          { s: '#OB #CB', b: 1, n: { pk: 0 }, g: 'map,json' },\n\n          // Start matching map key-value pairs: a:1.\n          // Reset counter n.pk as new map (for extensions).\n          { s: '#OB', p: 'pair', n: { pk: 0 }, g: 'map,json,pair' },\n        ],\n        close: [\n          // End of map.\n          { s: '#CB', g: 'end,json' },\n        ],\n      },\n\n\n      list: {\n        open: [\n          // An empty list: [].\n          { s: '#OS #CS', b: 1, g: 'list,json' },\n\n          // Start matching list elements: 1,2.\n          { s: '#OS', p: 'elem', g: 'list,elem,json' },\n        ],\n        close: [\n          // End of map.\n          { s: '#CS', g: 'end,json' },\n        ]\n      },\n\n\n      // sets key:val on node\n      pair: {\n        open: [\n          // Match key-colon start of pair. Marker `pair=true` allows flexibility.\n          {\n            s: '#KEY #CL',\n            p: 'val',\n            u: { pair: true },\n            a: '@pairkey',\n            g: 'map,pair,key,json',\n          },\n        ],\n\n        close: [\n          // Comma means a new pair at same pair-key level.\n          { s: '#CA', r: 'pair', g: 'map,pair,json' },\n\n          // End of map.\n          { s: '#CB', b: 1, g: 'map,pair,json' },\n        ]\n      },\n\n\n      // push onto node\n      elem: {\n        open: [\n          // List elements are values.\n          { p: 'val', g: 'list,elem,val,json' },\n        ],\n\n        close: [\n          // Next element.\n          { s: '#CA', r: 'elem', g: 'list,elem,json' },\n\n          // End of list.\n          { s: '#CS', b: 1, g: 'list,elem,json' },\n        ],\n      },\n\n\n    },\n  })\n\n\n  /*\n  jsonic.rule('val', (rs: RuleSpec) => {\n  rs\n   \n  .fnref({\n    '@val-bo': (rule: Rule) => (rule.node = undefined),\n    '@val-bc': (r: Rule, ctx: Context) => {\n      // NOTE: val can be undefined when there is no value at all\n      // (eg. empty string, thus no matched opening token)\n      r.node =\n        // If there's no node,\n        undefined === r.node\n          ? // ... or no child node (child map or list),\n          undefined === r.child.node\n            ? // ... or no matched tokens,\n            0 === r.os\n              ? // ... then the node has no value\n              undefined\n              : // .. otherwise use the token value\n              r.o0.resolveVal(r, ctx)\n            : r.child.node\n          : r.node\n    }\n  })\n   \n  // Clear the current node as this a new value.\n  // .bo((rule: Rule) => (rule.node = undefined))\n  // .bo('@val-bo')\n   \n  // Opening token alternates.\n  .open([\n  // A map: `{ ...`\n  { s: '#OB', p: 'map', b: 1, g: 'map,json' },\n   \n  // A list: `[ ...`\n  { s: '#OS', p: 'list', b: 1, g: 'list,json' },\n   \n  // A plain value: `x` `\"x\"` `1` `true` ....\n  { s: '#VAL', g: 'val,json' },\n  ])\n   \n  // Closing token alternates.\n  .close([\n  // End of input.\n  { s: '#ZZ', g: 'end,json' },\n   \n  // There's more JSON.\n  { b: 1, g: 'more,json' },\n  ])\n   \n  // .bc('@val-bc')\n   \n  })\n   \n   \n   \n  jsonic.rule('map', (rs: RuleSpec) => {\n    rs\n      .fnref({\n        '@map-bo': (r: Rule) => {\n          // Create a new empty map.\n          r.node = Object.create(null)\n        }\n      })\n      // .bo('@bo')\n      .open([\n        // An empty map: {}.\n        { s: '#OB #CB', b: 1, n: { pk: 0 }, g: 'map,json' },\n   \n        // Start matching map key-value pairs: a:1.\n        // Reset counter n.pk as new map (for extensions).\n        { s: '#OB', p: 'pair', n: { pk: 0 }, g: 'map,json,pair' },\n      ])\n      .close([\n        // End of map.\n        { s: '#CB', g: 'end,json' },\n      ])\n  })\n   \n  jsonic.rule('list', (rs: RuleSpec) => {\n    rs\n      .fnref({\n        '@list-bo': (r: Rule) => {\n          // Create a new empty list.\n          r.node = []\n        }\n      })\n      // .bo('@bo')\n      .open([\n        // An empty list: [].\n        { s: '#OS #CS', b: 1, g: 'list,json' },\n  \n        // Start matching list elements: 1,2.\n        { s: '#OS', p: 'elem', g: 'list,elem,json' },\n      ])\n      .close([\n        // End of map.\n        { s: '#CS', g: 'end,json' },\n      ])\n  })\n\n\n\n\n  // sets key:val on node\n  jsonic.rule('pair', (rs: RuleSpec) => {\n    rs\n      .fnref({\n        ...fnm,\n        '@pair-bc': (r: Rule, _ctx: Context) => {\n          if (r.u.pair) {\n            // Store previous value (if any, for extensions).\n            r.u.prev = r.node[r.u.key]\n            r.node[r.u.key] = r.child.node\n          }\n        }\n      })\n\n      .open([\n        // Match key-colon start of pair. Marker `pair=true` allows flexibility.\n        {\n          s: '#KEY #CL',\n          p: 'val',\n          u: { pair: true },\n          a: '@pairkey',\n          g: 'map,pair,key,json',\n        },\n      ])\n      // .bc('@bc')\n      .close([\n        // Comma means a new pair at same pair-key level.\n        { s: '#CA', r: 'pair', g: 'map,pair,json' },\n\n        // End of map.\n        { s: '#CB', b: 1, g: 'map,pair,json' },\n      ])\n  })\n\n  // push onto node\n  jsonic.rule('elem', (rs: RuleSpec) => {\n    rs\n      .fnref({\n        ...fnm,\n        '@elem-bc': (r: Rule) => {\n          if (true !== r.u.done && undefined !== r.child.node) {\n            r.node.push(r.child.node)\n          }\n        }\n      })\n      .open([\n        // List elements are values.\n        { p: 'val', g: 'list,elem,val,json' },\n      ])\n      // .bc('@bc')\n      .close([\n        // Next element.\n        { s: '#CA', r: 'elem', g: 'list,elem,json' },\n\n        // End of list.\n        { s: '#CS', b: 1, g: 'list,elem,json' },\n      ])\n  })\n\n  */\n\n  // Jsonic syntax extensions.\n  // NOTE: undefined values are still removed, as JSON does not have \"undefined\", only null.\n\n  // Counters.\n  // * pk: depth of the pair-key path\n  // * dmap: depth of maps\n\n  function pairval(r: Rule, ctx: Context) {\n    let key = r.u.key\n    let val = r.child.node\n    const prev = r.u.prev\n\n    // Convert undefined to null when there was no pair value\n    val = undefined === val ? null : val\n\n    // Do not set unsafe keys on Arrays (Objects are created without a prototype)\n    if (r.u.list && ctx.cfg.safe.key) {\n      if ('__proto__' === key || 'constructor' === key) {\n        return\n      }\n    }\n\n    // Drop keys that match the info marker to preserve metadata.\n    if (ctx.cfg.info.map && key === ctx.cfg.info.marker) {\n      return\n    }\n\n    val = null == prev\n      ? val\n      : ctx.cfg.map.merge\n        ? ctx.cfg.map.merge(prev, val, r, ctx)\n        : ctx.cfg.map.extend\n          ? deep(prev, val)\n          : val\n\n    r.node[key] = val\n  }\n\n\n\n  jsonic.grammar({\n    ref: {\n      '@val-close-error': (r: Rule, c: Context) => (0 === r.d ? c.t0 : undefined),\n    },\n\n    rule: {\n      val: {\n        open: {\n          alts: [\n            // A pair key: `a: ...`\n            // Implicit map at top level.\n            {\n              s: '#KEY #CL',\n              c: { d: 0 },\n              p: 'map',\n              b: 2,\n              g: 'pair,jsonic,top',\n            },\n\n            // A pair dive: `a:b: ...`\n            // Increment counter n.pk to indicate pair-key depth (for extensions).\n            // a:9 -> pk=undef, a:b:9 -> pk=1, a:b:c:9 -> pk=2, etc\n            {\n              s: '#KEY #CL',\n              p: 'map',\n              b: 2,\n              n: { pk: 1 },\n              g: 'pair,jsonic',\n            },\n\n            // A plain value: `x` `\"x\"` `1` `true` ....\n            { s: '#VAL', g: 'val,json' },\n\n            // Implicit ends `{a:}` -> {\"a\":null}, `[a:]` -> [{\"a\":null}]\n            {\n              s: ['#CB #CS'],\n              b: 1,\n              c: { d: { $gt: 0 } },\n              g: 'val,imp,null,jsonic',\n            },\n\n            // Implicit list at top level: a,b.\n            {\n              s: '#CA',\n              c: { d: 0 },\n              p: 'list',\n              b: 1,\n              g: 'list,imp,jsonic',\n            },\n\n            // Value is implicitly null when empty before commas.\n            { s: '#CA', b: 1, g: 'list,val,imp,null,jsonic' },\n\n            { s: '#ZZ', g: 'jsonic' },\n\n          ],\n          inject: { append: true, delete: [2] },\n        },\n\n        close: {\n          alts: [\n\n            // Explicitly close map or list: `}`, `]`\n            {\n              s: ['#CB #CS'],\n              b: 1,\n              g: 'val,json,close',\n              e: '@val-close-error', // (r, c) => (0 === r.d ? c.t0 : undefined),\n            },\n\n            // Implicit list (comma sep) only allowed at top level: `1,2`.\n            {\n              s: '#CA',\n              c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n              r: 'list',\n              u: { implist: true },\n              g: 'list,val,imp,comma,jsonic',\n            },\n\n            // Implicit list (space sep) only allowed at top level: `1 2`.\n            {\n              c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n              r: 'list',\n              u: { implist: true },\n              g: 'list,val,imp,space,jsonic',\n              b: 1,\n            },\n\n            { s: '#ZZ', g: 'jsonic' },\n\n          ],\n          inject: {\n            append: true,\n\n            // Move \"There's more JSON\" to end.\n            move: [1, -1],\n          }\n        }\n      }\n    }\n  })\n\n\n  /*\n    jsonic.rule('val', (rs: RuleSpec) => {\n      rs\n        .open(\n          [\n            // A pair key: `a: ...`\n            // Implicit map at top level.\n            {\n              s: '#KEY #CL',\n              c: { d: 0 },\n              p: 'map',\n              b: 2,\n              g: 'pair,jsonic,top',\n            },\n   \n            // A pair dive: `a:b: ...`\n            // Increment counter n.pk to indicate pair-key depth (for extensions).\n            // a:9 -> pk=undef, a:b:9 -> pk=1, a:b:c:9 -> pk=2, etc\n            {\n              s: '#KEY #CL',\n              p: 'map',\n              b: 2,\n              n: { pk: 1 },\n              g: 'pair,jsonic',\n            },\n   \n            // A plain value: `x` `\"x\"` `1` `true` ....\n            { s: [VAL], g: 'val,json' },\n   \n            // Implicit ends `{a:}` -> {\"a\":null}, `[a:]` -> [{\"a\":null}]\n            {\n              s: ['#CB #CS'],\n              b: 1,\n              c: { d: { $gt: 0 } },\n              g: 'val,imp,null,jsonic',\n            },\n   \n            // Implicit list at top level: a,b.\n            {\n              s: '#CA',\n              c: { d: 0 },\n              p: 'list',\n              b: 1,\n              g: 'list,imp,jsonic',\n            },\n   \n            // Value is implicitly null when empty before commas.\n            { s: '#CA', b: 1, g: 'list,val,imp,null,jsonic' },\n   \n            { s: '#ZZ', g: 'jsonic' },\n          ],\n          { append: true, delete: [2] },\n        )\n        .close(\n          [\n            // Explicitly close map or list: `}`, `]`\n            {\n              s: ['#CB #CS'],\n              b: 1,\n              g: 'val,json,close',\n              e: (r, c) => (0 === r.d ? c.t0 : undefined),\n            },\n   \n            // Implicit list (comma sep) only allowed at top level: `1,2`.\n            {\n              s: '#CA',\n              c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n              r: 'list',\n              u: { implist: true },\n              g: 'list,val,imp,comma,jsonic',\n            },\n   \n            // Implicit list (space sep) only allowed at top level: `1 2`.\n            {\n              c: { 'n.dlist': { $lte: 0 }, 'n.dmap': { $lte: 0 } },\n              r: 'list',\n              u: { implist: true },\n              g: 'list,val,imp,space,jsonic',\n              b: 1,\n            },\n   \n            { s: '#ZZ', g: 'jsonic' },\n          ],\n          {\n            append: true,\n   \n            // Move \"There's more JSON\" to end.\n            move: [1, -1],\n          },\n        )\n    })\n  */\n\n  jsonic.rule('map', (rs: RuleSpec) => {\n    rs\n      .fnref({\n        ...fnm\n      })\n      .bo((r: Rule) => {\n        // Increment depth of maps.\n        r.n.dmap = 1 + (r.n.dmap ? r.n.dmap : 0)\n      })\n      .open([\n        // Auto-close; fail if rule.finish option is false.\n        { s: '#OB #ZZ', b: 1, e: '@finish', g: 'end,jsonic' },\n      ])\n      .open(\n        [\n          // Pair from implicit map.\n          { s: '#KEY #CL', p: 'pair', b: 2, g: 'pair,list,val,imp,jsonic' },\n        ],\n        { append: true },\n      )\n      .close(\n        [\n          // Normal end of map, no path dive.\n          {\n            s: '#CB',\n            c: { 'n.pk': { $lte: 0 } },\n            g: 'end,json',\n          },\n\n          // Not yet at end of path dive, keep ascending.\n          { s: '#CB', b: 1, g: 'path,jsonic' },\n\n          // End of implicit path\n          { s: ['#CA #CS #VAL'], b: 1, g: 'end,path,jsonic' },\n\n          // Auto-close; fail if rule.finish option is false.\n          { s: '#ZZ', e: '@finish', g: 'end,jsonic' },\n        ],\n        { append: true, delete: [0] },\n      )\n      .bc((r: Rule, ctx: Context) => {\n        let m = ctx.cfg.info.marker\n        if (ctx.cfg.info.map && r.node?.[m]) {\n          r.node[m].implicit = !(r.o0 && r.o0.tin === ctx.cfg.t.OB)\n        }\n      })\n  })\n\n  jsonic.rule('list', (rs: RuleSpec) => {\n    rs\n      .fnref({\n        ...fnm,\n        '@list-bo': (r: Rule) => {\n          // Increment depth of lists.\n          r.n.dlist = 1 + (r.n.dlist ? r.n.dlist : 0)\n\n          if (r.prev.u.implist) {\n            r.node.push(r.prev.node)\n            r.prev.node = r.node\n          }\n        }\n      })\n      // .bo('@bo')\n      .open({\n        c: { 'prev.u.implist': { $eq: true } },\n        p: 'elem',\n      })\n      .open(\n        [\n          // Initial comma [, will insert null as [null,\n          { s: '#CA', p: 'elem', b: 1, g: 'list,elem,val,imp,jsonic' },\n\n          // Another element.\n          { p: 'elem', g: 'list,elem,jsonic' },\n        ],\n        { append: true },\n      )\n      .close(\n        [\n          // Fail if rule.finish option is false.\n          { s: '#ZZ', e: '@finish', g: 'end,jsonic' },\n        ],\n        { append: true },\n      )\n      .bc((r: Rule, ctx: Context) => {\n        let m = ctx.cfg.info.marker\n        if (ctx.cfg.info.list && r.node?.[m]) {\n          r.node[m].implicit = !(r.o0 && r.o0.tin === ctx.cfg.t.OS)\n        }\n      })\n  })\n\n  // sets key:val on node\n  jsonic.rule('pair', (rs: RuleSpec, p: Parser) => {\n    rs\n      .fnref({\n        ...fnm,\n        '@pair-bc': (r: Rule, ctx: Context) => {\n          if (r.u.pair) {\n            pairval(r, ctx)\n          }\n\n          if (true === r.u.child) {\n            let val = r.child.node\n            val = undefined === val ? null : val\n            let prev = r.node['child$']\n\n            if (undefined === prev) {\n              r.node['child$'] = val\n            } else {\n              r.node['child$'] =\n                ctx.cfg.map.merge\n                  ? ctx.cfg.map.merge(prev, val, r, ctx)\n                  : ctx.cfg.map.extend\n                    ? deep(prev, val)\n                    : val\n            }\n          }\n        }\n      })\n\n      .open(\n        [\n          // Ignore initial comma: {,a:1.\n          { s: '#CA', g: 'map,pair,comma,jsonic' },\n\n          // map.child: bare colon `:value` stores value on child$ property.\n          p.cfg.map.child && {\n            s: '#CL',\n            p: 'val',\n            u: { done: true, child: true },\n            g: 'map,pair,child,jsonic',\n          },\n        ],\n        { append: true },\n      )\n\n      // NOTE: JSON pair.bc runs first, then this bc may override value.\n      // .bc('@bc')\n      .close(\n        [\n          // End of map, reset implicit depth counter so that\n          // a:b:c:1,d:2 -> {a:{b:{c:1}},d:2}\n          {\n            s: '#CB',\n            c: { 'n.pk': { $lte: 0 } },\n            b: 1,\n            g: 'map,pair,json',\n          },\n\n          // Ignore trailing comma at end of map.\n          {\n            s: '#CA #CB',\n            c: { 'n.pk': { $lte: 0 } },\n            b: 1,\n            g: 'map,pair,comma,jsonic',\n          },\n\n          { s: [CA, ZZ], g: 'end,jsonic' },\n\n          // Comma means a new pair at same pair-key level.\n          {\n            s: '#CA',\n            c: { 'n.pk': { $lte: 0 } },\n            r: 'pair',\n            g: 'map,pair,json',\n          },\n\n          // TODO: try CA VAL ? works anywhere?\n          // Comma means a new pair if implicit top level map.\n          {\n            s: '#CA',\n            c: { 'n.dmap': { $lte: 1 } },\n            r: 'pair',\n            g: 'map,pair,jsonic',\n          },\n\n          // TODO: try VAL CL ? works anywhere?\n          // Value means a new pair if implicit top level map.\n          {\n            s: '#KEY',\n            c: { 'n.dmap': { $lte: 1 } },\n            r: 'pair',\n            b: 1,\n            g: 'map,pair,imp,jsonic',\n          },\n\n          // End of implicit path (eg. a:b:1), keep closing until pk=0.\n          {\n            s: ['#CB #CA #CS #KEY'],\n            c: { 'n.pk': { $gt: 0 } },\n            b: 1,\n            g: 'map,pair,imp,path,jsonic',\n          },\n\n          // Can't close a map with `]`\n          { s: '#CS', e: (r: Rule) => r.c0, g: 'end,jsonic' },\n\n          // Fail if auto-close option is false.\n          { s: '#ZZ', e: '@finish', g: 'map,pair,json' },\n\n          // Who needs commas anyway?\n          {\n            r: 'pair',\n            b: 1,\n            g: 'map,pair,imp,jsonic',\n          },\n        ],\n        { append: true, delete: [0, 1] },\n      )\n  })\n\n  // push onto node\n  jsonic.rule('elem', (rs: RuleSpec, p: Parser) => {\n    rs\n      .fnref({\n        ...fnm,\n        '@elem-bc': (r: Rule, ctx: Context) => {\n          if (true === r.u.pair) {\n            if (ctx.cfg.list.pair) {\n              // list.pair: push pair as object element into the list\n              let key = r.u.key\n              let val = r.child.node\n              val = undefined === val ? null : val\n              let pairObj = Object.create(null)\n              pairObj[key] = val\n              r.node.push(pairObj)\n            } else {\n              r.u.prev = r.node[r.u.key]\n              pairval(r, ctx)\n            }\n          }\n\n          if (true === r.u.child) {\n            let val = r.child.node\n            val = undefined === val ? null : val\n            let prev = r.node['child$']\n\n            if (undefined === prev) {\n              r.node['child$'] = val\n            } else {\n              r.node['child$'] =\n                ctx.cfg.map.merge\n                  ? ctx.cfg.map.merge(prev, val, r, ctx)\n                  : ctx.cfg.map.extend\n                    ? deep(prev, val)\n                    : val\n            }\n          }\n        }\n      })\n\n      .open([\n        // Empty commas insert null elements.\n        // Note that close consumes a comma, so b:2 works.\n        {\n          s: '#CA #CA',\n          b: 2,\n          u: { done: true },\n          a: (r: Rule) => r.node.push(null),\n          g: 'list,elem,imp,null,jsonic',\n        },\n\n        {\n          s: '#CA',\n          u: { done: true },\n          a: (r: Rule) => r.node.push(null),\n          g: 'list,elem,imp,null,jsonic',\n        },\n\n        {\n          s: '#KEY #CL',\n          e: (p.cfg.list.property || p.cfg.list.pair) ? undefined :\n            (_r: Rule, ctx: Context) => ctx.t0,\n          p: 'val',\n          n: { pk: 1, dmap: 1 },\n          u: { done: true, pair: true, list: true },\n          a: '@pairkey',\n          g: 'elem,pair,jsonic',\n        },\n\n        // list.child: bare colon `:value` stores value on child$ property.\n        p.cfg.list.child && {\n          s: '#CL',\n          p: 'val',\n          u: { done: true, child: true, list: true },\n          g: 'elem,child,jsonic',\n        },\n      ])\n      // .bc('@bc')\n      .close(\n        [\n          // Ignore trailing comma.\n          { s: ['#CA', '#CS #ZZ'], b: 1, g: 'list,elem,comma,jsonic' },\n\n          // Next element.\n          { s: '#CA', r: 'elem', g: 'list,elem,json' },\n\n          // End of list.\n          { s: '#CS', b: 1, g: 'list,elem,json' },\n\n          // Fail if auto-close option is false.\n          { s: '#ZZ', e: '@finish', g: 'list,elem,json' },\n\n          // Can't close a list with `}`\n          { s: '#CB', e: (r: Rule) => r.c0, g: 'end,jsonic' },\n\n          // Who needs commas anyway?\n          { r: 'elem', b: 1, g: 'list,elem,imp,jsonic' },\n        ],\n        { delete: [-1, -2] },\n      )\n  })\n}\n\n\nfunction makeJSON(jsonic: any) {\n  let justJSON = jsonic.make({\n    grammar$: false,\n    text: { lex: false },\n    number: {\n      hex: false,\n      oct: false,\n      bin: false,\n      sep: null,\n      exclude: /^00+/,\n    },\n    string: {\n      chars: '\"',\n      multiChars: '',\n      allowUnknown: false,\n      escape: { v: null },\n    },\n    comment: { lex: false },\n    map: { extend: false },\n    lex: { empty: false },\n    rule: { finish: false, include: 'json' },\n    result: { fail: [undefined, NaN] },\n    tokenSet: {\n      KEY: ['#ST', null, null, null],\n    },\n  })\n\n  grammar(justJSON)\n\n  return justJSON\n}\n\n\nexport { grammar, makeJSON }\n"
  },
  {
    "path": "src/jsonic-bnf-cli.ts",
    "content": "/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */\n\n/*  jsonic-bnf-cli.ts\n *  CLI wrapper for the BNF -> jsonic grammar spec converter.\n */\n\nimport Fs from 'node:fs'\n\nimport { bnf } from './bnf'\nimport { Jsonic } from './jsonic'\n\n\nexport async function run(argv: string[], console: Console) {\n  const args = {\n    help: false,\n    stdin: false,\n    files: [] as string[],\n    inline: [] as string[],\n    start: undefined as string | undefined,\n    tag: undefined as string | undefined,\n    space: 2,\n    // When set, convert and install the grammar, parse each sample,\n    // and report the tree (or an error) instead of the spec.\n    parse: [] as string[],\n    parseFiles: [] as string[],\n  }\n\n  for (let aI = 2; aI < argv.length; aI++) {\n    const arg = argv[aI]\n    if ('-' === arg) {\n      args.stdin = true\n    } else if ('--help' === arg || '-h' === arg) {\n      args.help = true\n    } else if ('--file' === arg || '-f' === arg) {\n      args.files.push(argv[++aI])\n    } else if ('--start' === arg || '-s' === arg) {\n      args.start = argv[++aI]\n    } else if ('--tag' === arg || '-t' === arg) {\n      args.tag = argv[++aI]\n    } else if ('--compact' === arg || '-c' === arg) {\n      args.space = 0\n    } else if ('--parse' === arg || '-P' === arg) {\n      args.parse.push(argv[++aI])\n    } else if ('--parse-file' === arg) {\n      args.parseFiles.push(argv[++aI])\n    } else if (arg && !arg.startsWith('-')) {\n      args.inline.push(arg)\n    }\n  }\n\n  if (args.help) {\n    return help(console)\n  }\n\n  let src = ''\n  for (const fp of args.files) {\n    if ('string' === typeof fp && '' !== fp) {\n      src += Fs.readFileSync(fp).toString() + '\\n'\n    }\n  }\n  for (const inline of args.inline) {\n    src += inline + '\\n'\n  }\n\n  if ('' === src.trim() || args.stdin) {\n    src += await readStdin(console)\n  }\n\n  const spec = bnf(src, { start: args.start, tag: args.tag })\n\n  // Parse-mode: validate the grammar against one or more sample\n  // inputs and print their parse trees. Exits 1 if any sample fails.\n  if (args.parse.length > 0 || args.parseFiles.length > 0) {\n    const samples: { label: string; input: string }[] = []\n    for (const fp of args.parseFiles) {\n      samples.push({\n        label: fp,\n        input: Fs.readFileSync(fp).toString(),\n      })\n    }\n    for (const inp of args.parse) {\n      samples.push({ label: inp, input: inp })\n    }\n\n    const j = Jsonic.make()\n    j.grammar(spec)\n\n    let failed = 0\n    for (const { label, input } of samples) {\n      try {\n        const tree = j(input)\n        console.log(\n          `ok: ${JSON.stringify(label)} -> ` +\n          JSON.stringify(tree, null, args.space || undefined))\n      } catch (e: any) {\n        failed++\n        const msg = (e?.message || String(e)).split('\\n')[0]\n        console.error(`fail: ${JSON.stringify(label)}: ${msg}`)\n      }\n    }\n    if (failed > 0) {\n      process.exitCode = 1\n    }\n    return\n  }\n\n  console.log(JSON.stringify(spec, null, args.space || undefined))\n}\n\n\nasync function readStdin(console: Console): Promise<string> {\n  if ('string' === typeof (console as any).test$) {\n    return (console as any).test$\n  }\n  if (process.stdin.isTTY) return ''\n  let s = ''\n  process.stdin.setEncoding('utf8')\n  for await (const p of process.stdin) s += p\n  return s\n}\n\n\nfunction help(console: Console) {\n  console.log(`\njsonic-bnf: convert a BNF grammar into a jsonic grammar spec.\n\nUsage: jsonic-bnf <args> [<bnf-source>]*\n\nArguments:\n  -                      Read BNF source from stdin.\n  --file <path>          Read BNF source from <path> (repeatable).\n  -f <path>\n\n  --start <name>         Set the start rule (defaults to the first\n  -s <name>                production).\n\n  --tag <name>           Group tag applied to every emitted alt.\n  -t <name>                Defaults to \\`bnf\\`.\n\n  --compact              Emit single-line JSON (default indent is 2).\n  -c\n\n  --parse <input>        Parse <input> against the generated grammar\n  -P <input>               and print its parse tree. Repeatable.\n                           Exits non-zero if any sample fails.\n\n  --parse-file <path>    Parse the contents of <path> against the\n                           generated grammar (repeatable).\n\n  --help                 Print this help message.\n  -h\n\nExamples:\n  > jsonic-bnf '<greet> ::= \"hi\" | \"hello\"'\n  > jsonic-bnf -f grammar.bnf\n  > echo '<g> ::= \"a\"' | jsonic-bnf -\n  > jsonic-bnf -f grammar.bnf --parse 'hi'\n`)\n}\n"
  },
  {
    "path": "src/jsonic-cli.ts",
    "content": "/* Copyright (c) 2020-2024 Richard Rodger, Oliver Sturm, and other contributors, MIT License */\n\nimport Fs from 'node:fs'\n\nimport { Jsonic, Plugin, Bag, util } from './jsonic'\n\nimport { Debug } from './debug'\n\nexport async function run(argv: string[], console: Console) {\n  const args = {\n    help: false,\n    stdin: false,\n    sources: [] as string[],\n    files: [] as string[],\n    options: [] as string[],\n    meta: [] as string[],\n    plugins: [] as string[],\n  }\n\n  let plugins: { [name: string]: Plugin } = {}\n\n  let accept_args = true\n  for (let aI = 2; aI < argv.length; aI++) {\n    let arg = argv[aI]\n\n    if (accept_args && arg.startsWith('-')) {\n      if ('-' === arg) {\n        args.stdin = true\n      } //\n      else if ('--' === arg) {\n        accept_args = false\n      } //\n      else if ('--file' === arg || '-f' === arg) {\n        args.files.push(argv[++aI])\n      } //\n      else if ('--option' === arg || '-o' === arg) {\n        args.options.push(argv[++aI])\n      } //\n      else if ('--meta' === arg || '-m' === arg) {\n        args.meta.push(argv[++aI])\n      } //\n      else if ('--debug' === arg || '-d' === arg) {\n        plugins.debug = Debug\n        args.meta.push('log=-1')\n      } //\n      else if ('--help' === arg || '-h' === arg) {\n        args.help = true\n      } //\n      else if ('--plugin' === arg || '-p' === arg) {\n        args.plugins.push(argv[++aI])\n      } //\n      else if ('--nice' === arg || '-n' === arg) {\n        args.options.push('JSON.space=2')\n      } //\n      else {\n        args.sources.push(arg)\n      }\n    } //\n    else {\n      args.sources.push(arg)\n    }\n  }\n\n  if (args.help) {\n    return help(console)\n  }\n\n  let options: any = handle_props(args.options)\n  let meta: any = handle_props(args.meta)\n  plugins = { ...plugins, ...handle_plugins(args.plugins) }\n\n  options.debug = options.debug || {}\n  options.debug.get_console = () => console\n\n  let jsonic = Jsonic.make(options)\n\n  for (let pn in plugins) {\n    jsonic.use(plugins[pn], options.plugin?.[pn] || {})\n  }\n\n  if (null != plugins.debug) {\n    console.log(jsonic.debug.describe() + '\\n=== PARSE ===')\n  }\n\n  let data = { val: null }\n\n  for (let fp of args.files) {\n    if ('string' === typeof fp && '' !== fp) {\n      util.deep(data, { val: jsonic(Fs.readFileSync(fp).toString(), meta) })\n    }\n  }\n\n  if (0 === args.sources.length || args.stdin) {\n    let stdin = await read_stdin(console)\n    util.deep(data, { val: jsonic(stdin, meta) })\n  }\n\n  for (let src of args.sources) {\n    util.deep(data, { val: jsonic(src, meta) })\n  }\n\n  options.JSON =\n    null == options.JSON || 'object' !== typeof options.JSON ? {} : options.JSON\n  let replacer = Jsonic(options.JSON.replacer)\n  let space = Jsonic(options.JSON.space)\n\n  replacer = Array.isArray(replacer)\n    ? replacer\n    : null == replacer\n      ? null\n      : [replacer]\n\n  let json = JSON.stringify(data.val, replacer, space)\n\n  console.log(json)\n}\n\nasync function read_stdin(console: Console) {\n  if ('string' === typeof (console as any).test$) {\n    return (console as any).test$\n  }\n\n  if (process.stdin.isTTY) return ''\n\n  let s = ''\n  process.stdin.setEncoding('utf8')\n  for await (const p of process.stdin) s += p\n  return s\n}\n\n// NOTE: uses vanilla Jsonic to parse arg vals, so you can set complex\n// properties.  This will break if core Jsonic is broken.\nfunction handle_props(propvals: string[]): Bag {\n  let out = {}\n\n  for (let propval of propvals) {\n    let pv = propval.split(/=/)\n    if ('' !== pv[0] && '' !== pv[1]) {\n      let val = Jsonic(pv[1])\n      util.prop(out, pv[0], val)\n    }\n  }\n  return out\n}\n\nfunction handle_plugins(plugins: string[]): Bag {\n  let out: any = {}\n  for (let name of plugins) {\n    try {\n      out[name] = require(name)\n    } catch (e) {\n      let err = e\n\n      // Might be @jsonic plugin\n      if (!name.startsWith('@')) {\n        try {\n          out[name] = require('@jsonic/' + name)\n        } catch (e) {\n          throw err // NOTE: throws original error\n        }\n      } else {\n        throw err\n      }\n    }\n\n    // Handle some variations in the way the plugin function is exported.\n    if ('function' !== typeof out[name]) {\n      let refname = ((name as any).match(/([^.\\\\\\/]+)($|\\.[^.]+$)/) || [])[1]\n      refname = null != refname ? refname.toLowerCase() : refname\n\n      // See test plugin test/p1.js\n      if ('function' == typeof out[name].default) {\n        out[name] = out[name].default\n      } //\n      else if (\n        null != refname &&\n        'function' == typeof out[name][camel(refname) as string]\n      ) {\n        out[name] = out[name][camel(refname) as string]\n      }\n\n      // See test plugin test/p2.js\n      else if (\n        null != refname &&\n        'function' == typeof out[name][refname as any]\n      ) {\n        out[refname] = out[name][refname]\n        delete out[name]\n      } //\n      else {\n        throw new Error('Plugin is not a function: ' + name)\n      }\n    }\n  }\n\n  return out\n}\n\nfunction camel(s: string) {\n  return (\n    s[0].toUpperCase() +\n    s\n      .substring(1)\n      .replace(/-(\\w)/g, (m) => m[1][0].toUpperCase() + m[1].substring(1))\n  )\n}\n\nfunction help(console: Console) {\n  let s = `\nA JSON parser that isn't strict.\n\nUsage: jsonic <args> [<source-text>]*\n\nwhere \n  <source-text> is the source text to be parsed into JSON.\n    If omitted, the source text is read from STDIN. If multiple source texts\n    are provided, they will be merged in precedence (from highest) \n    right to left, STDIN, <file>.\n\n  <args> are the command arguments:\n\n    -                      Alias for STDIN.\n\n    --file <file>          Load and parse <file>.\n    -f <file>\n\n    --option <name=value>  Set option <name> to <value>, where <name> \n    -o <name=value>          can be a dotted path (see example below).\n\n    --nice                 Print JSON indented over multiple lines.\n    -n\n\n    --meta <meta=value>    Set parse meta data <name> to <value>, where <name> \n    -m <meta=value>          can be a dotted path (see option example).\n\n    --plugin <require>     Load a plugin, where <require> is the plugin module\n    -p <require>             reference (name or path).\n\n    --debug                Print abbreviated lex and parse logs for debugging,\n    -d                       alias of \\`--meta log = -1\\`.\n\n    --help                 Print this help message.\n    -h\n\nOutput:\n  Output is generated by the built-in JSON.stringify method. The \\`replacer\\`\n    and \\`space\\` arguments can be specified using \\`-o JSON.replacer=...\\` and\n    \\`-o JSON.space=...\\` respectively.\n\n\nPlugins \n  The built-in plugins (found in the ./plugin folder of the distribution) can be \n  specified using the abbreviated references:\n    directive, multisource, csv, toml, ...\n\n  Plugin options can be specified using: \\`-o plugin.<name>.<option>=<value>\\`.\n  See the example below.\n\n\nExamples:\n\n# Basic usage\n> jsonic a:1\n{\"a\":1} \n\n> jsonic -n a:1\n{\n  \"a\": 1\n}\n\n\n# Merging arguments\n> jsonic a:b:1 a:c:2\n{\"a\":{\"b\":1,\"c\":2}}\n\n\n# Output options\n> jsonic a:b:1 a:c:2 --option JSON.space=2\n{\n  \"a\": {\n    \"b\": 1,\n    \"c\": 2\n  }\n}\n\n\n# Piping\n> echo a:1 | jsonic\n{\"a\":1} \n\n\n# Using plugins (e.g. npm install @jsonic/csv)\n> jsonic -p csv  -o plugin.csv.record.separators=^ \"a,b^1,2\"\n[{\"a\":\"1\",\"b\":\"2\"}]\n\n\n# Full debug tracing\n> jsonic -d -o plugin.debug.trace=true a:1\n... lots of debug info, including token-by-token trace ...\n{\"a\":1}\n\n\nSee also: http://jsonic.senecajs.org\n`\n\n  console.log(s)\n}\n"
  },
  {
    "path": "src/jsonic.ts",
    "content": "/* Copyright (c) 2013-2023 Richard Rodger, MIT License */\n\n/*  jsonic.ts\n *  Entry point and API.\n */\n\nimport type {\n  AltAction,\n  AltCond,\n  AltError,\n  AltMatch,\n  AltModifier,\n  AltSpec,\n  Bag,\n  Config,\n  Context,\n  Counters,\n  FuncRef,\n  JsonicAPI,\n  JsonicParse,\n  Lex,\n  LexCheck,\n  LexMatcher,\n  LexSub,\n  MakeLexMatcher,\n  NormAltSpec,\n  Options,\n  Parser,\n  Plugin,\n  Point,\n  Rule,\n  RuleDefiner,\n  RuleSpec,\n  RuleSpecMap,\n  RuleState,\n  RuleSub,\n  StateAction,\n  Tin,\n  Token,\n  GrammarSpec,\n  GrammarSetting,\n} from './types'\n\nimport { OPEN, CLOSE, BEFORE, AFTER, EMPTY, SKIP } from './types'\n\nimport {\n  S,\n  assign,\n  badlex,\n  deep,\n  defprop,\n  makelog,\n  mesc,\n  regexp,\n  tokenize,\n  findTokenSet,\n  srcfmt,\n  clone,\n  charset,\n  configure,\n  escre,\n  parserwrap,\n  str,\n  clean,\n\n  resolveFuncRefs,\n\n  // Exported with jsonic.util\n  omap,\n  entries,\n  values,\n  keys,\n} from './utility'\n\nimport {\n  JsonicError,\n  errdesc,\n  errinject,\n  errsite,\n  errmsg,\n  trimstk,\n  strinject,\n  prop,\n} from './error'\n\nimport { defaults } from './defaults'\n\nimport {\n  makePoint,\n  makeToken,\n  makeLex,\n  makeFixedMatcher,\n  makeSpaceMatcher,\n  makeLineMatcher,\n  makeStringMatcher,\n  makeCommentMatcher,\n  makeNumberMatcher,\n  makeTextMatcher,\n} from './lexer'\n\nimport { makeRule, makeRuleSpec, makeParser } from './parser'\n\nimport { grammar, makeJSON } from './grammar'\n\nimport { bnf as bnfConvert } from './bnf'\nimport type { BnfConvertOptions } from './types'\n\n// TODO: remove - too much for an API!\nconst util = {\n  tokenize,\n  srcfmt,\n  clone,\n  charset,\n  trimstk,\n  makelog,\n  badlex,\n  errsite,\n  errinject,\n  errdesc,\n  configure,\n  parserwrap,\n  mesc,\n  escre,\n  regexp,\n  prop,\n  str,\n  clean,\n  errmsg,\n  strinject,\n\n  // TODO: validated to include in util API:\n  deep,\n  omap,\n  keys,\n  values,\n  entries,\n}\n\n// The full library type.\n// NOTE: redeclared here so it can be exported as a type and instance.\ntype Jsonic = JsonicParse & // A function that parses.\n  JsonicAPI & { [prop: string]: any } // A utility with API methods. // Extensible by plugin decoration.\n\nfunction make(param_options?: Bag | string, parent?: Jsonic): Jsonic {\n  let injectFullAPI = true\n  if ('jsonic' === param_options) {\n    injectFullAPI = false\n  } else if ('json' === param_options) {\n    return makeJSON(root)\n  }\n\n  param_options = 'string' === typeof param_options ? {} : param_options\n\n  let internal: {\n    parser: Parser\n    config: Config\n    plugins: Plugin[]\n    sub: {\n      lex?: LexSub[]\n      rule?: RuleSub[]\n    }\n    mark: number\n  } = {\n    parser: null as unknown as Parser,\n    config: null as unknown as Config,\n    plugins: [],\n    sub: {\n      lex: undefined,\n      rule: undefined,\n    },\n    mark: Math.random(),\n  }\n\n  // Merge options.\n  let merged_options = deep(\n    {},\n    parent\n      ? { ...parent.options }\n      : false === (param_options as Bag)?.defaults$\n        ? {}\n        : defaults,\n    param_options ? param_options : {},\n  )\n\n  // Create primary parsing function\n  let jsonic: any = function Jsonic(\n    src: any,\n    meta?: any,\n    parent_ctx?: any,\n  ): any {\n    if (S.string === typeof src) {\n      let internal = jsonic.internal()\n      let parser = optionsMethod.parser?.start\n        ? parserwrap(optionsMethod.parser)\n        : internal.parser\n      return parser.start(src, jsonic, meta, parent_ctx)\n    }\n\n    return src\n  }\n\n  // This lets you access options as direct properties,\n  // and set them as a function call.\n  // `change_options` can be a Bag object or a jsonic-format string that\n  // is parsed into a Bag before applying.\n  let optionsMethod: any = (change_options?: Bag | string) => {\n    if (null != change_options) {\n      if (S.string === typeof change_options) {\n        const parsed = make()(change_options as string)\n        change_options =\n          null != parsed && S.object === typeof parsed\n            ? (parsed as Bag)\n            : undefined\n      }\n      if (null != change_options && S.object === typeof change_options) {\n        deep(merged_options, change_options)\n        configure(jsonic, internal.config, merged_options)\n        let parser: Parser = jsonic.internal().parser\n        internal.parser = parser.clone(merged_options, internal.config, jsonic)\n      }\n    }\n    return { ...jsonic.options }\n  }\n\n  // Define the API\n  let api: JsonicAPI = {\n    token: ((ref: string | Tin) =>\n      internal.config.fixed.token[ref] ??\n      tokenize(ref, internal.config, jsonic)) as unknown as JsonicAPI['token'],\n\n    tokenSet: ((ref: string | Tin) =>\n      findTokenSet(ref, internal.config)) as unknown as JsonicAPI['tokenSet'],\n\n    fixed: ((ref: string | Tin) =>\n      internal.config.fixed.ref[ref]) as unknown as JsonicAPI['fixed'],\n\n    options: deep(optionsMethod, merged_options),\n\n    config: () => deep(internal.config),\n\n    parse: jsonic,\n\n    // TODO: how to handle null plugin?\n    use: function use(plugin: Plugin, plugin_options?: Bag): Jsonic {\n      if (S.function !== typeof plugin) {\n        throw new Error(\n          'Jsonic.use: the first argument must be a function ' +\n          'defining a plugin. See https://jsonic.senecajs.org/plugin',\n        )\n      }\n\n      // Plugin name keys in options.plugin are the lower-cased plugin function name.\n      const plugin_name = plugin.name.toLowerCase()\n      const full_plugin_options = deep(\n        {},\n        plugin.defaults || {},\n        plugin_options || {},\n      )\n\n      jsonic.options({\n        plugin: {\n          [plugin_name]: full_plugin_options,\n        },\n      })\n      let merged_plugin_options = jsonic.options.plugin[plugin_name]\n      jsonic.internal().plugins.push(plugin)\n      plugin.options = merged_plugin_options\n\n      return plugin(jsonic, merged_plugin_options) || jsonic\n    },\n\n    rule: (name?: string, define?: RuleDefiner | null) => {\n      return (jsonic.internal().parser as Parser).rule(name, define) || jsonic\n    },\n\n    make: (options?: Options | string) => {\n      return make(options, jsonic)\n    },\n\n    empty: (options?: Options) =>\n      make({\n        defaults$: false,\n        standard$: false,\n        grammar$: false,\n        ...(options || {}),\n      }),\n\n    id:\n      'Jsonic/' +\n      Date.now() +\n      '/' +\n      ('' + Math.random()).substring(2, 8).padEnd(6, '0') +\n      (null == optionsMethod.tag ? '' : '/' + optionsMethod.tag),\n\n    toString: () => {\n      return api.id\n    },\n\n    sub: (spec: { lex?: LexSub; rule?: RuleSub }) => {\n      if (spec.lex) {\n        internal.sub.lex = internal.sub.lex || []\n        internal.sub.lex.push(spec.lex)\n      }\n      if (spec.rule) {\n        internal.sub.rule = internal.sub.rule || []\n        internal.sub.rule.push(spec.rule)\n      }\n      return jsonic\n    },\n\n    util,\n\n\n    grammar: (gs: GrammarSpec | string, setting?: GrammarSetting) => {\n      if ('string' === typeof gs) {\n        const parsed = make()(gs)\n        if (null == parsed || 'object' !== typeof parsed) {\n          return\n        }\n        gs = parsed as GrammarSpec\n      }\n\n      // Normalize the optional setting's rule.alt.g value to a string[] once.\n      const altG = setting?.rule?.alt?.g\n      const altGArr: string[] | null =\n        null == altG\n          ? null\n          : Array.isArray(altG)\n            ? [...altG]\n            : String(altG).split(/\\s*,\\s*/).filter((s) => s.length > 0)\n\n      // Append altGArr tags to each alt's g field without mutating the input alt.\n      const applyG = (alts: any): any => {\n        if (null == altGArr || 0 === altGArr.length || !Array.isArray(alts)) {\n          return alts\n        }\n        return alts.map((a: any) => {\n          if (null == a || 'object' !== typeof a) return a\n          const existing: string[] =\n            null == a.g\n              ? []\n              : Array.isArray(a.g)\n                ? [...a.g]\n                : String(a.g).split(/\\s*,\\s*/).filter((s) => s.length > 0)\n          return { ...a, g: [...existing, ...altGArr] }\n        })\n      }\n\n      if (gs.options) {\n        const resolved = resolveFuncRefs(gs.options, gs.ref)\n        ji.options(resolved)\n      }\n\n      if (gs.rule) {\n        for (const rulename of Object.keys(gs.rule)) {\n          const rulespec = gs.rule[rulename]\n          ji.rule(rulename, (rs: RuleSpec) => {\n\n            if (gs.ref) {\n              rs.fnref(gs.ref)\n            }\n\n            if (rulespec.open) {\n              const isarr = Array.isArray(rulespec.open)\n              const alts = isarr ? rulespec.open : (rulespec.open as any).alts\n              const inject = isarr ? {} : (rulespec.open as any).inject\n              rs.open(applyG(alts), inject)\n            }\n\n            if (rulespec.close) {\n              const isarr = Array.isArray(rulespec.close)\n              const alts = isarr ? rulespec.close : (rulespec.close as any).alts\n              const inject = isarr ? {} : (rulespec.close as any).inject\n              rs.close(applyG(alts), inject)\n            }\n\n          })\n        }\n      }\n    },\n\n\n    // Convert a BNF grammar string into a jsonic GrammarSpec and install\n    // it on this instance. Returns the generated spec so callers can\n    // inspect, serialise or diff it. Use `bnf.toSpec(src, opts)` to\n    // build the spec without installing it.\n    bnf: (() => {\n      const impl = (src: string, opts?: BnfConvertOptions) => {\n        const spec = bnfConvert(src, opts)\n        ji.grammar(spec)\n        return spec\n      }\n      impl.toSpec = (src: string, opts?: BnfConvertOptions) =>\n        bnfConvert(src, opts)\n      return impl\n    })(),\n\n  }\n\n  // Has to be done indirectly as we are in a fuction named `make`.\n  defprop(api.make, S.name, { value: S.make })\n\n  let ji = jsonic\n  if (injectFullAPI) {\n    // Add API methods to the core utility function.\n    assign(jsonic, api)\n  } else {\n    assign(jsonic, {\n      empty: api.empty,\n      parse: api.parse,\n      sub: api.sub,\n      id: api.id,\n      toString: api.toString,\n    })\n    ji = assign(Object.create(jsonic), api)\n  }\n\n  // Hide internals where you can still find them.\n  defprop(jsonic, 'internal', { value: () => internal })\n\n  if (parent) {\n    // Transfer extra parent properties (preserves plugin decorations, etc).\n    for (let k in parent) {\n      if (undefined === jsonic[k]) {\n        jsonic[k] = parent[k]\n      }\n    }\n\n    jsonic.parent = parent\n\n    let parent_internal = parent.internal()\n    internal.config = deep({}, parent_internal.config)\n\n    configure(jsonic, internal.config, merged_options)\n    assign(jsonic.token, internal.config.t)\n\n    internal.plugins = [...parent_internal.plugins]\n    internal.parser = parent_internal.parser.clone(\n      merged_options,\n      internal.config,\n      ji,\n    )\n  }\n  else {\n    let rootWithAPI = { ...jsonic, ...api }\n    internal.config = configure(rootWithAPI, undefined, merged_options)\n    internal.plugins = []\n    internal.parser = makeParser(merged_options, internal.config, ji)\n\n    if (false !== merged_options.grammar$) {\n      grammar(rootWithAPI)\n    }\n  }\n\n  return jsonic\n}\n\n\nlet root: any = undefined\n\n// The global root Jsonic instance parsing rules cannot be modified.\n// use Jsonic.make() to create a modifiable instance.\nlet Jsonic: Jsonic = (root = make('jsonic'))\n\n// Provide deconstruction export names\nroot.Jsonic = root\nroot.JsonicError = JsonicError\nroot.makeLex = makeLex\nroot.makeParser = makeParser\nroot.makeToken = makeToken\nroot.makePoint = makePoint\nroot.makeRule = makeRule\nroot.makeRuleSpec = makeRuleSpec\nroot.makeFixedMatcher = makeFixedMatcher\nroot.makeSpaceMatcher = makeSpaceMatcher\nroot.makeLineMatcher = makeLineMatcher\nroot.makeStringMatcher = makeStringMatcher\nroot.makeCommentMatcher = makeCommentMatcher\nroot.makeNumberMatcher = makeNumberMatcher\nroot.makeTextMatcher = makeTextMatcher\nroot.OPEN = OPEN\nroot.CLOSE = CLOSE\nroot.BEFORE = BEFORE\nroot.AFTER = AFTER\nroot.EMPTY = EMPTY\nroot.SKIP = SKIP\n\nroot.util = util\nroot.make = make\nroot.S = S\n\n// Export most of the types for use by plugins.\nexport type {\n  AltAction,\n  AltCond,\n  AltError,\n  AltMatch,\n  AltModifier,\n  AltSpec,\n  Bag,\n  Config,\n  Context,\n  Counters,\n  FuncRef,\n  Lex,\n  LexCheck,\n  LexMatcher,\n  MakeLexMatcher,\n  NormAltSpec,\n  Options,\n  Plugin,\n  Point,\n  Rule,\n  RuleDefiner,\n  RuleSpec,\n  RuleSpecMap,\n  RuleState,\n  StateAction,\n  Tin,\n  Token,\n}\n\nexport {\n  // Jsonic is both a type and a value.\n  Jsonic as Jsonic,\n  JsonicError,\n  Parser,\n  util,\n  make,\n  makeToken,\n  makePoint,\n  makeRule,\n  makeRuleSpec,\n  makeLex,\n  makeParser,\n  makeFixedMatcher,\n  makeSpaceMatcher,\n  makeLineMatcher,\n  makeStringMatcher,\n  makeCommentMatcher,\n  makeNumberMatcher,\n  makeTextMatcher,\n  OPEN,\n  CLOSE,\n  BEFORE,\n  AFTER,\n  EMPTY,\n  SKIP,\n  S,\n  root,\n}\n\nexport default Jsonic\n\nif ('undefined' !== typeof module) {\n  module.exports = Jsonic\n}\n"
  },
  {
    "path": "src/lexer.ts",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger, MIT License */\n\n/*  lexer.ts\n *  Lexer implementation, converts source text into tokens for the parser.\n */\n\nimport type {\n  Tin,\n  Token,\n  Point,\n  Lex,\n  Rule,\n  Config,\n  Context,\n  LexMatcher,\n  MakeLexMatcher,\n  Bag,\n  NormAltSpec,\n} from './types'\n\nimport { EMPTY, INSPECT } from './types'\n\nimport type { Options } from './jsonic'\n\nimport {\n  S,\n  charset,\n  clean,\n  deep,\n  escre,\n  keys,\n  omap,\n  regexp,\n  snip,\n  tokenize,\n  entries,\n  values,\n} from './utility'\n\nclass PointImpl implements Point {\n  len = -1\n  sI = 0\n  rI = 1\n  cI = 1\n  token: Token[] = []\n  end?: Token\n\n  constructor(len: number, sI?: number, rI?: number, cI?: number) {\n    this.len = len\n    if (null != sI) {\n      this.sI = sI\n    }\n    if (null != rI) {\n      this.rI = rI\n    }\n    if (null != cI) {\n      this.cI = cI\n    }\n  }\n\n  toString() {\n    return (\n      'Point[' +\n      [this.sI + '/' + this.len, this.rI, this.cI] +\n      (0 < this.token.length ? ' ' + this.token : '') +\n      ']'\n    )\n  }\n\n  [INSPECT]() {\n    return this.toString()\n  }\n}\n\nconst makePoint = (...params: ConstructorParameters<typeof PointImpl>) =>\n  new PointImpl(...params)\n\n// Tokens from the lexer.\nclass TokenImpl implements Token {\n  isToken = true\n  name = EMPTY\n  tin = -1\n  val = undefined\n  src = EMPTY\n  sI = -1\n  rI = -1\n  cI = -1\n  len = -1\n  use?: Bag\n  err?: string\n  why?: string\n\n  constructor(\n    name: string,\n    tin: Tin,\n    val: any,\n    src: string,\n    pnt: Point,\n    use?: any,\n    why?: string,\n  ) {\n    this.name = name\n    this.tin = tin\n    this.src = src\n    this.val = val\n    this.sI = pnt.sI\n    this.rI = pnt.rI\n    this.cI = pnt.cI\n    this.use = use\n    this.why = why\n\n    this.len = null == src ? 0 : src.length\n  }\n\n  resolveVal(rule: Rule, ctx: Context): any {\n    let out =\n      'function' === typeof this.val ? (this.val as any)(rule, ctx) : this.val\n    return out\n  }\n\n  bad(err: string, details?: any): Token {\n    this.err = err\n    if (null != details) {\n      this.use = deep(this.use || {}, details)\n    }\n    return this\n  }\n\n  toString() {\n    return (\n      'Token[' +\n      this.name +\n      '=' +\n      this.tin +\n      ' ' +\n      snip(this.src) +\n      (undefined === this.val || '#ST' === this.name || '#TX' === this.name\n        ? ''\n        : '=' + snip(this.val)) +\n      ' ' +\n      [this.sI, this.rI, this.cI] +\n      (null == this.use\n        ? ''\n        : ' ' + snip('' + JSON.stringify(this.use).replace(/\"/g, ''), 22)) +\n      (null == this.err ? '' : ' ' + this.err) +\n      (null == this.why ? '' : ' ' + snip('' + this.why, 22)) +\n      ']'\n    )\n  }\n\n  [INSPECT]() {\n    return this.toString()\n  }\n}\n\nconst makeToken = (...params: ConstructorParameters<typeof TokenImpl>) =>\n  new TokenImpl(...params)\n\nconst makeNoToken = () => makeToken('', -1, undefined, EMPTY, makePoint(-1))\n\nlet makeFixedMatcher: MakeLexMatcher = (cfg: Config, _opts: Options) => {\n  let fixed = regexp(null, '^(', cfg.rePart.fixed, ')')\n\n  return function fixedMatcher(lex: Lex) {\n    let mcfg = cfg.fixed\n    if (!mcfg.lex) return undefined\n\n    if (cfg.fixed.check) {\n      let check = cfg.fixed.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let pnt = lex.pnt\n    let fwd = lex.fwd\n\n    let m = fwd.match(fixed)\n    if (m) {\n      let msrc = m[1]\n      let mlen = msrc.length\n      if (0 < mlen) {\n        let tkn: Token | undefined = undefined\n\n        let tin = mcfg.token[msrc]\n        if (null != tin) {\n          tkn = lex.token(tin, undefined, msrc, pnt)\n\n          pnt.sI += mlen\n          pnt.cI += mlen\n        }\n\n        return tkn\n      }\n    }\n  }\n}\n\nlet makeMatchMatcher: MakeLexMatcher = (cfg: Config, _opts: Options) => {\n  // Pre-sort both matcher lists at configure time so lexing iterates in\n  // a deterministic order regardless of how the config object was built.\n  // Value matchers: sort by user-supplied name (ascending).\n  // Token matchers: sort by attached tin$ (ascending), set in utility.ts.\n  let valueMatchers = entries(cfg.match.value)\n    .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n    .map(([, spec]) => spec)\n  let tokenMatchers = values(cfg.match.token).sort(\n    (a: any, b: any) => (a.tin$ || 0) - (b.tin$ || 0),\n  )\n\n  // Don't add a matcher if there's nothing to do.\n  if (0 === valueMatchers.length && 0 === tokenMatchers.length) {\n    return null\n  }\n\n  return function matchMatcher(lex: Lex, rule: Rule, tI: number = 0) {\n    let mcfg = cfg.match\n    if (!mcfg.lex) return undefined\n\n    if (cfg.match.check) {\n      let check = cfg.match.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let pnt = lex.pnt\n    let fwd = lex.fwd\n\n    let oc = 'o' === rule.state ? 0 : 1\n\n    for (let valueMatcher of valueMatchers) {\n      if (valueMatcher.match instanceof RegExp) {\n        // TODO: only match VL if present in rule\n\n        let m = fwd.match(valueMatcher.match)\n\n        if (m) {\n          let msrc = m[0]\n          let mlen = msrc.length\n          if (0 < mlen) {\n            let tkn: Token | undefined = undefined\n\n            let val = valueMatcher.val ? valueMatcher.val(m) : msrc\n            tkn = lex.token('#VL', val, msrc, pnt)\n\n            pnt.sI += mlen\n            pnt.cI += mlen\n\n            return tkn\n          }\n        }\n      } else {\n        let tkn: any = valueMatcher.match(lex, rule)\n        if (null != tkn) {\n          return tkn\n        }\n      }\n    }\n\n    for (let tokenMatcher of tokenMatchers) {\n      // Only match Token if present in Rule sequence.\n      // Exception: an `eager$` flag on the matcher opts out of\n      // tcol gating — the matcher fires whenever its regex matches\n      // and the downstream parser rejects tokens it doesn't expect\n      // at the current position. This is what ABNF's\n      // case-insensitive literals need: the lexer has to emit the\n      // literal's own tin even when the current rule's tcol is\n      // narrower, so the next rule up the stack can see the token\n      // as its proper type rather than falling through to #TX.\n\n      if (\n        (tokenMatcher as any).tin$ &&\n        !(tokenMatcher as any).eager$ &&\n        !rule.spec.def.tcol[oc][tI].includes((tokenMatcher as any).tin$)\n      ) {\n        continue\n      }\n\n      if (tokenMatcher instanceof RegExp) {\n        let m = fwd.match(tokenMatcher)\n\n        if (m) {\n          let msrc = m[0]\n          let mlen = msrc.length\n          if (0 < mlen) {\n            let tkn: Token | undefined = undefined\n\n            let tin = (tokenMatcher as any).tin$\n            tkn = lex.token(tin, msrc, msrc, pnt)\n\n            pnt.sI += mlen\n            pnt.cI += mlen\n\n            return tkn\n          }\n        }\n      } else {\n        let tkn: any = tokenMatcher(lex, rule)\n        if (null != tkn) {\n          return tkn\n        }\n      }\n    }\n  }\n}\n\n// NOTE 1: matchers return arbitrary tokens and describe lexing using\n// code, rather than a grammar. Thus, for example, some matchers below\n// will check (using subMatchFixed) if their source text actually represents\n// a fixed value.\n\n// NOTE 2: matchers can place a second token onto the Point tokens,\n// supporting two token lookahead.\n\ntype CommentDef = Config['comment']['def'] extends { [_: string]: infer T }\n  ? T\n  : never\n\nlet makeCommentMatcher: MakeLexMatcher = (cfg: Config, opts: Options) => {\n  let oc = opts.comment\n\n  cfg.comment = {\n    lex: oc ? !!oc.lex : false,\n    def: (oc?.def ? entries(oc.def) : []).reduce(\n      (def: any, [name, om]: [string, any]) => {\n        // Set comment marker to null to remove\n        if (null == om || false === om) {\n          return def\n        }\n\n        let { suffixes, suffixFn } = normalizeCommentSuffix(om.suffix)\n\n        let cm: CommentDef = {\n          name,\n          start: om.start as string,\n          end: om.end,\n          line: !!om.line,\n          lex: !!om.lex,\n          eatline: !!om.eatline,\n          suffixes,\n          suffixFn,\n        }\n\n        def[name] = cm\n        return def\n      },\n      {} as any,\n    ),\n  }\n\n  // Pre-sort by start length (longest first) so that a longer marker\n  // shadows any shorter marker it contains (e.g. '##' wins over '#'),\n  // regardless of the insertion order of cfg.comment.def. Ties break by\n  // name for deterministic iteration across runtimes.\n  let byStartLenDesc = (a: CommentDef, b: CommentDef) =>\n    b.start.length - a.start.length ||\n    (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)\n  let lineComments = cfg.comment.lex\n    ? values(cfg.comment.def).filter((c) => c.lex && c.line).sort(byStartLenDesc)\n    : []\n  let blockComments = cfg.comment.lex\n    ? values(cfg.comment.def).filter((c) => c.lex && !c.line).sort(byStartLenDesc)\n    : []\n\n  return function matchComment(lex: Lex, _rule: Rule) {\n    let mcfg = cfg.comment\n    if (!mcfg.lex) return undefined\n\n    if (cfg.comment.check) {\n      let check = cfg.comment.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let pnt = lex.pnt\n    let fwd = lex.fwd\n\n    let rI = pnt.rI\n    let cI = pnt.cI\n\n    // Single line comment.\n\n    for (let mc of lineComments) {\n      if (fwd.startsWith(mc.start)) {\n        let fwdlen = fwd.length\n        let fI = mc.start.length\n        cI += mc.start.length\n        let suffixLen = 0\n        while (fI < fwdlen && !cfg.line.chars[fwd[fI]]) {\n          let n = commentSuffixMatch(fwd, fI, mc.suffixes)\n          if (n > 0) { suffixLen = n; break }\n          n = commentSuffixFnMatch(lex, fI, mc.suffixFn)\n          if (n > 0) { suffixLen = n; break }\n          cI++\n          fI++\n        }\n\n        if (suffixLen > 0) {\n          // Consume the suffix as the tail of the comment body.\n          fI += suffixLen\n          cI += suffixLen\n        }\n        else if (mc.eatline) {\n          // Only absorb trailing line chars when termination came from\n          // a line char (not from a suffix match).\n          while (fI < fwdlen && cfg.line.chars[fwd[fI]]) {\n            if (cfg.line.rowChars[fwd[fI]]) {\n              rI++\n            }\n            fI++\n          }\n        }\n\n        let csrc = fwd.substring(0, fI)\n        let tkn = lex.token('#CM', undefined, csrc, pnt)\n\n        pnt.sI += csrc.length\n        pnt.cI = cI\n        pnt.rI = rI\n\n        return tkn\n      }\n    }\n\n    // Multiline comment.\n\n    for (let mc of blockComments) {\n      if (fwd.startsWith(mc.start)) {\n        let fwdlen = fwd.length\n        let fI = mc.start.length\n        let end = mc.end as string\n        cI += mc.start.length\n        let suffixLen = 0\n        while (fI < fwdlen && !fwd.startsWith(end, fI)) {\n          let n = commentSuffixMatch(fwd, fI, mc.suffixes)\n          if (n > 0) { suffixLen = n; break }\n          n = commentSuffixFnMatch(lex, fI, mc.suffixFn)\n          if (n > 0) { suffixLen = n; break }\n          if (cfg.line.rowChars[fwd[fI]]) {\n            rI++\n            cI = 0\n          }\n\n          cI++\n          fI++\n        }\n\n        if (suffixLen > 0) {\n          // Advance through the consumed suffix, tracking newlines.\n          for (let k = 0; k < suffixLen; k++) {\n            if (cfg.line.rowChars[fwd[fI + k]]) {\n              rI++\n              cI = 0\n            }\n            cI++\n          }\n          let csrc = fwd.substring(0, fI + suffixLen)\n          let tkn = lex.token('#CM', undefined, csrc, pnt)\n          pnt.sI += csrc.length\n          pnt.rI = rI\n          pnt.cI = cI\n          return tkn\n        }\n\n        if (fwd.startsWith(end, fI)) {\n          cI += end.length\n\n          if (mc.eatline) {\n            while (fI < fwdlen && cfg.line.chars[fwd[fI]]) {\n              if (cfg.line.rowChars[fwd[fI]]) {\n                rI++\n              }\n              fI++\n            }\n          }\n\n          let csrc = fwd.substring(0, fI + end.length)\n          let tkn = lex.token('#CM', undefined, csrc, pnt)\n\n          pnt.sI += csrc.length\n          pnt.rI = rI\n          pnt.cI = cI\n\n          return tkn\n        } else {\n          return lex.bad(\n            S.unterminated_comment,\n            pnt.sI,\n            pnt.sI + 9 * mc.start.length,\n          )\n        }\n      }\n    }\n  }\n}\n\n// normalizeCommentSuffix splits the polymorphic suffix option value\n// (string | string[] | LexMatcher) into a length-sorted string list and\n// an optional LexMatcher probe. Empty/absent input yields empty outputs.\nfunction normalizeCommentSuffix(raw: any):\n  { suffixes: string[] | undefined, suffixFn: LexMatcher | undefined } {\n  if (null == raw) {\n    return { suffixes: undefined, suffixFn: undefined }\n  }\n  if ('function' === typeof raw) {\n    return { suffixes: undefined, suffixFn: raw as LexMatcher }\n  }\n  let list: string[] = []\n  if ('string' === typeof raw) {\n    if ('' !== raw) list.push(raw)\n  } else if (Array.isArray(raw)) {\n    for (let s of raw) {\n      if ('string' === typeof s && '' !== s) list.push(s)\n    }\n  }\n  if (list.length > 1) {\n    list.sort((a, b) => b.length - a.length || (a < b ? -1 : a > b ? 1 : 0))\n  }\n  return {\n    suffixes: 0 === list.length ? undefined : list,\n    suffixFn: undefined,\n  }\n}\n\n// commentSuffixMatch returns the length of the best suffix match at\n// fwd[fI:] or 0 if none matches. Suffixes are pre-sorted longest-first,\n// so the first match is the best match.\nfunction commentSuffixMatch(fwd: string, fI: number, suffixes?: string[]): number {\n  if (!suffixes || 0 === suffixes.length) return 0\n  for (let s of suffixes) {\n    if (fwd.substring(fI, fI + s.length) === s) return s.length\n  }\n  return 0\n}\n\n// commentSuffixFnMatch probes the LexMatcher-form suffix terminator at\n// offset fI. Returns the length of the returned token's src (to be\n// consumed) or 0 if no termination. The lex point is snapshotted and\n// restored so a misbehaving matcher can't advance the stream itself.\nfunction commentSuffixFnMatch(lex: Lex, fI: number, fn?: LexMatcher): number {\n  if (!fn) return 0\n  let pnt = lex.pnt\n  let savedSI = pnt.sI\n  let savedRI = pnt.rI\n  let savedCI = pnt.cI\n  pnt.sI = savedSI + fI\n  let tkn: any\n  try {\n    tkn = fn(lex, undefined as any)\n  } finally {\n    pnt.sI = savedSI\n    pnt.rI = savedRI\n    pnt.cI = savedCI\n  }\n  if (null == tkn) return 0\n  return ('string' === typeof tkn.src) ? tkn.src.length : 0\n}\n\n// Match text, checking for literal values, optionally followed by a fixed token.\n// Text strings are terminated by end markers.\nlet makeTextMatcher: MakeLexMatcher = (cfg: Config, opts: Options) => {\n  let ender = regexp(cfg.line.lex ? null : 's', '^(.*?)', ...cfg.rePart.ender)\n\n  return function textMatcher(lex: Lex) {\n    if (cfg.text.check) {\n      let check = cfg.text.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let mcfg = cfg.text\n    let pnt = lex.pnt\n    let fwd = lex.fwd\n    let def = cfg.value.def\n    let defre = cfg.value.defre\n\n    let m = fwd.match(ender)\n\n    if (m) {\n      let msrc = m[1]\n      let tsrc = m[2]\n\n      let out: Token | undefined = undefined\n\n      if (null != msrc) {\n        let mlen = msrc.length\n        if (0 < mlen) {\n          // Check for values first.\n          let vs = undefined\n          if (cfg.value.lex) {\n            // Fixed values (e.g true, false, null).\n            if (undefined !== (vs = def[msrc])) {\n              out = lex.token('#VL', vs.val, msrc, pnt)\n              pnt.sI += mlen\n              pnt.cI += mlen\n            }\n\n            // Regexp processed values.\n            else {\n              // defre is a name-sorted array (see cfg.value.defre build in\n              // utility.ts) — iteration order is deterministic.\n              for (let vspec of defre) {\n                if (vspec.match) {\n                  // If consume, assume regexp starts with ^.\n                  let res = vspec.match.exec(vspec.consume ? fwd : msrc)\n\n                  // Must match entire text.\n                  if (res && (vspec.consume || res[0].length === msrc.length)) {\n                    let remsrc = res[0]\n\n                    if (null == vspec.val) {\n                      out = lex.token('#VL', remsrc, remsrc, pnt)\n                    } else {\n                      let val = vspec.val(res)\n                      out = lex.token('#VL', val, remsrc, pnt)\n                    }\n\n                    pnt.sI += remsrc.length\n                    pnt.cI += remsrc.length\n                  }\n                }\n              }\n            }\n          }\n\n          // Not a value, so plain text.\n          // NOTEL if !text.lex then only values are matched.\n          if (null == out && mcfg.lex) {\n            out = lex.token('#TX', msrc, msrc, pnt)\n            pnt.sI += mlen\n            pnt.cI += mlen\n          }\n        }\n      }\n\n      // A following fixed token can only match if there was already a\n      // valid text or value match.\n      if (out) {\n        out = subMatchFixed(lex, out, tsrc)\n      }\n\n      if (out && 0 < cfg.text.modify.length) {\n        const modify = cfg.text.modify\n        for (let mI = 0; mI < modify.length; mI++) {\n          out.val = modify[mI](out.val, lex, cfg, opts)\n        }\n      }\n\n      return out\n    }\n  }\n}\n\nlet makeNumberMatcher: MakeLexMatcher = (cfg: Config, _opts: Options) => {\n  let mcfg = cfg.number\n\n  let ender = regexp(\n    null,\n    [\n      '^([-+]?(0(',\n      [\n        mcfg.hex ? 'x[0-9a-fA-F_]+' : null,\n        mcfg.oct ? 'o[0-7_]+' : null,\n        mcfg.bin ? 'b[01_]+' : null,\n      ]\n        .filter((s) => null != s)\n        .join('|'),\n      // ')|[.0-9]+([0-9_]*[0-9])?)',\n      ')|\\\\.?[0-9]+([0-9_]*[0-9])?)',\n      '(\\\\.[0-9]?([0-9_]*[0-9])?)?',\n      '([eE][-+]?[0-9]+([0-9_]*[0-9])?)?',\n    ]\n      .join('')\n      .replace(/_/g, mcfg.sep ? escre(mcfg.sepChar as string) : ''),\n    ')',\n    ...cfg.rePart.ender,\n  )\n\n  let numberSep = mcfg.sep\n    ? regexp('g', escre(mcfg.sepChar as string))\n    : undefined\n\n  return function matchNumber(lex: Lex) {\n    mcfg = cfg.number\n    if (!mcfg.lex) return undefined\n\n    if (cfg.number.check) {\n      let check = cfg.number.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let pnt = lex.pnt\n    let fwd = lex.fwd\n    let valdef = cfg.value.def\n\n    let m = fwd.match(ender)\n    if (m) {\n      let msrc = m[1]\n      let tsrc = m[9] // NOTE: count parens in numberEnder!\n\n      let out: Token | undefined = undefined\n      let included = true\n\n      if (\n        null != msrc &&\n        (included = !cfg.number.exclude || !msrc.match(cfg.number.exclude))\n      ) {\n        let mlen = msrc.length\n        if (0 < mlen) {\n          let vs = undefined\n          if (cfg.value.lex && undefined !== (vs = valdef[msrc])) {\n            out = lex.token('#VL', vs.val, msrc, pnt)\n          } else {\n            let nstr = numberSep ? msrc.replace(numberSep, '') : msrc\n            let num = +nstr\n\n            // Special case: +- prefix of 0x... format\n            if (isNaN(num)) {\n              let first = nstr[0]\n              if ('-' === first || '+' === first) {\n                num = ('-' === first ? -1 : 1) * +nstr.substring(1)\n              }\n            }\n\n            if (!isNaN(num)) {\n              out = lex.token('#NR', num, msrc, pnt)\n              pnt.sI += mlen\n              pnt.cI += mlen\n            }\n            // Else let later matchers try.\n          }\n        }\n      }\n\n      if (included) {\n        out = subMatchFixed(lex, out, tsrc)\n      }\n\n      return out\n    }\n  }\n}\n\nlet makeStringMatcher: MakeLexMatcher = (cfg: Config, opts: Options) => {\n  // TODO: does `clean` make sense here?\n\n  let os = opts.string || {}\n  cfg.string = cfg.string || ({} as any)\n\n  // TODO: compose with earlier config - do this in other makeFooMatchers?\n  cfg.string = deep(cfg.string, {\n    lex: !!os?.lex,\n    quoteMap: charset(os.chars),\n    multiChars: charset(os.multiChars),\n    escMap: { ...os.escape },\n    escChar: os.escapeChar,\n    escCharCode:\n      null == os.escapeChar ? undefined : os.escapeChar.charCodeAt(0),\n    allowUnknown: !!os.allowUnknown,\n    replaceCodeMap: omap(clean({ ...os.replace }), ([c, r]) => [\n      c.charCodeAt(0),\n      r,\n    ]),\n    hasReplace: false,\n    abandon: !!os.abandon,\n  })\n\n  cfg.string.escMap = clean(cfg.string.escMap)\n  cfg.string.hasReplace = 0 < keys(cfg.string.replaceCodeMap).length\n\n  return function stringMatcher(lex: Lex) {\n    let mcfg = cfg.string\n    if (!mcfg.lex) return undefined\n\n    if (cfg.string.check) {\n      let check = cfg.string.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let {\n      quoteMap,\n      escMap,\n      escChar,\n      escCharCode,\n      multiChars,\n      allowUnknown,\n      replaceCodeMap,\n      hasReplace,\n    } = mcfg\n\n    let { pnt, src } = lex\n    let { sI, rI, cI } = pnt\n    let srclen = src.length\n\n    if (quoteMap[src[sI]]) {\n      const q = src[sI] // Quote character\n      const qI = sI\n      const qrI = rI\n      const isMultiLine = multiChars[q]\n      ++sI\n      ++cI\n\n      let s: string[] = []\n      let rs: string | undefined\n\n      for (sI; sI < srclen; sI++) {\n        cI++\n        let c = src[sI]\n        rs = undefined\n\n        // Quote char.\n        if (q === c) {\n          sI++\n          break // String finished.\n        }\n\n        // Escape char.\n        else if (escChar === c) {\n          sI++\n          cI++\n\n          let es = escMap[src[sI]]\n\n          if (null != es) {\n            s.push(es)\n          }\n\n          // ASCII escape \\x**\n          else if ('x' === src[sI]) {\n            sI++\n            let cc = parseInt(src.substring(sI, sI + 2), 16)\n\n            if (isNaN(cc)) {\n              if (mcfg.abandon) {\n                return undefined\n              }\n              sI = sI - 2\n              cI -= 2\n              pnt.sI = sI\n              pnt.cI = cI\n              return lex.bad(S.invalid_ascii, sI, sI + 4)\n            }\n\n            let us = String.fromCharCode(cc)\n\n            s.push(us)\n            sI += 1 // Loop increments sI.\n            cI += 2\n          }\n\n          // Unicode escape \\u**** and \\u{*****}.\n          else if ('u' === src[sI]) {\n            sI++\n            let ux = '{' === src[sI] ? (sI++, 1) : 0\n            let ulen = ux ? 6 : 4\n\n            let cc = parseInt(src.substring(sI, sI + ulen), 16)\n\n            if (isNaN(cc)) {\n              if (mcfg.abandon) {\n                return undefined\n              }\n              sI = sI - 2 - ux\n              cI -= 2\n\n              pnt.sI = sI\n              pnt.cI = cI\n              return lex.bad(S.invalid_unicode, sI, sI + ulen + 2 + 2 * ux)\n            }\n\n            let us = String.fromCodePoint(cc)\n\n            s.push(us)\n            sI += ulen - 1 + ux // Loop increments sI.\n            cI += ulen + ux\n          } else if (allowUnknown) {\n            s.push(src[sI])\n          } else {\n            if (mcfg.abandon) {\n              return undefined\n            }\n            pnt.sI = sI\n            pnt.cI = cI - 1\n            return lex.bad(S.unexpected, sI, sI + 1)\n          }\n        } else if (\n          hasReplace &&\n          undefined !== (rs = replaceCodeMap[src.charCodeAt(sI)])\n        ) {\n          s.push(rs)\n          cI++\n        }\n\n        // Body part of string.\n        else {\n          let bI = sI\n\n          // TODO: move to cfgx\n          let qc = q.charCodeAt(0)\n          let cc = src.charCodeAt(sI)\n\n          while (\n            (!hasReplace || undefined === (rs = replaceCodeMap[cc])) &&\n            sI < srclen &&\n            32 <= cc &&\n            qc !== cc &&\n            escCharCode !== cc\n          ) {\n            cc = src.charCodeAt(++sI)\n            cI++\n          }\n          cI--\n\n          if (undefined === rs && cc < 32) {\n            // TODO: move up - allow c < 32 to be a line char\n            if (isMultiLine && cfg.line.chars[src[sI]]) {\n              if (cfg.line.rowChars[src[sI]]) {\n                pnt.rI = ++rI\n              }\n\n              cI = 1\n              s.push(src.substring(bI, sI + 1))\n            } else {\n              if (mcfg.abandon) {\n                return undefined\n              }\n              pnt.sI = sI\n              pnt.cI = cI\n              return lex.bad(S.unprintable, sI, sI + 1)\n            }\n          } else {\n            s.push(src.substring(bI, sI))\n            sI--\n          }\n        }\n      }\n\n      if (src[sI - 1] !== q || pnt.sI === sI - 1) {\n        if (mcfg.abandon) {\n          return undefined\n        }\n        pnt.rI = qrI\n        return lex.bad(S.unterminated_string, qI, sI)\n      }\n\n      const tkn = lex.token(\n        '#ST',\n        s.join(EMPTY),\n        src.substring(pnt.sI, sI),\n        pnt,\n      )\n\n      pnt.sI = sI\n      pnt.rI = rI\n      pnt.cI = cI\n      return tkn\n    }\n  }\n}\n\n// Line ending matcher.\nlet makeLineMatcher: MakeLexMatcher = (cfg: Config, _opts: Options) => {\n  return function matchLine(lex: Lex) {\n    if (!cfg.line.lex) return undefined\n\n    if (cfg.line.check) {\n      let check = cfg.line.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let { chars, rowChars } = cfg.line\n    let { pnt, src } = lex\n    let { sI, rI } = pnt\n\n    let single = cfg.line.single\n    let counts: Record<string, number> | undefined = undefined\n\n    if (single) {\n      counts = {}\n    }\n\n    while (chars[src[sI]]) {\n      if (counts) {\n        counts[src[sI]] = (counts[src[sI]] || 0) + 1\n\n        if (single) {\n          if (1 < counts[src[sI]]) {\n            break\n          }\n        }\n      }\n      rI += rowChars[src[sI]] ? 1 : 0\n      sI++\n    }\n\n    if (pnt.sI < sI) {\n      let msrc = src.substring(pnt.sI, sI)\n      const tkn = lex.token('#LN', undefined, msrc, pnt)\n      pnt.sI += msrc.length\n      pnt.rI = rI\n      pnt.cI = 1\n\n      return tkn\n    }\n  }\n}\n\n// Space matcher.\nlet makeSpaceMatcher: MakeLexMatcher = (cfg: Config, _opts: Options) => {\n  return function spaceMatcher(lex: Lex) {\n    if (!cfg.space.lex) return undefined\n\n    if (cfg.space.check) {\n      let check = cfg.space.check(lex)\n      if (check && check.done) {\n        return check.token\n      }\n    }\n\n    let { chars } = cfg.space\n    let { pnt, src } = lex\n    let { sI, cI } = pnt\n\n    while (chars[src[sI]]) {\n      sI++\n      cI++\n    }\n\n    if (pnt.sI < sI) {\n      let msrc = src.substring(pnt.sI, sI)\n      const tkn = lex.token('#SP', undefined, msrc, pnt)\n      pnt.sI += msrc.length\n      pnt.cI = cI\n      return tkn\n    }\n  }\n}\n\nfunction subMatchFixed(\n  lex: Lex,\n  first: Token | undefined,\n  tsrc: string | undefined,\n): Token | undefined {\n  let pnt = lex.pnt\n  let out = first\n\n  if (lex.cfg.fixed.lex && null != tsrc) {\n    let tknlen = tsrc.length\n    if (0 < tknlen) {\n      let tkn: Token | undefined = undefined\n\n      let tin = lex.cfg.fixed.token[tsrc]\n      if (null != tin) {\n        tkn = lex.token(tin, undefined, tsrc, pnt)\n      }\n\n      if (null != tkn) {\n        pnt.sI += tkn.src.length\n        pnt.cI += tkn.src.length\n\n        if (null == first) {\n          out = tkn\n        } else {\n          pnt.token.push(tkn)\n        }\n      }\n    }\n  }\n  return out\n}\n\nclass LexImpl implements Lex {\n  src = EMPTY\n  ctx = {} as Context\n  cfg = {} as Config\n  pnt = makePoint(-1)\n  fwd = EMPTY as string\n\n  refwd(): string {\n    this.fwd = this.src.substring(this.pnt.sI) as string\n    return this.fwd\n  }\n\n  constructor(ctx: Context) {\n    this.ctx = ctx\n    this.src = ctx.src()\n    this.cfg = ctx.cfg\n    this.pnt = makePoint(this.src.length)\n  }\n\n  token(\n    ref: Tin | string,\n    val: any,\n    src: string,\n    pnt?: Point,\n    use?: any,\n    why?: string,\n  ): Token {\n    let tin: Tin\n    let name: string\n    if ('string' === typeof ref) {\n      name = ref\n      tin = tokenize(name, this.cfg)\n    } else {\n      tin = ref\n      name = tokenize(ref, this.cfg)\n    }\n\n    let tkn = makeToken(name, tin, val, src, pnt || this.pnt, use, why)\n\n    return tkn\n  }\n\n  next(rule: Rule, alt?: NormAltSpec, altI?: number, tI?: number): Token {\n    let tkn: Token | undefined\n    let pnt = this.pnt\n    let sI = pnt.sI\n    let match: LexMatcher | undefined = undefined\n\n    if (pnt.end) {\n      tkn = pnt.end\n    } else if (0 < pnt.token.length) {\n      tkn = pnt.token.shift() as Token\n    } else if (pnt.len <= pnt.sI) {\n      pnt.end = this.token('#ZZ', undefined, '', pnt)\n\n      tkn = pnt.end\n    } else {\n      this.fwd = this.src.substring(pnt.sI) as string\n      try {\n        for (let mat of this.cfg.lex.match) {\n          if ((tkn = mat(this, rule, tI))) {\n            match = mat\n            break\n          }\n        }\n      } catch (err: any) {\n        tkn =\n          tkn ||\n          this.token(\n            '#BD',\n            undefined,\n            this.src[pnt.sI],\n            pnt,\n            { err },\n            err.code || S.unexpected,\n          )\n      }\n\n      tkn =\n        tkn ||\n        this.token(\n          '#BD',\n          undefined,\n          this.src[pnt.sI],\n          pnt,\n          undefined,\n          S.unexpected,\n        )\n    }\n\n    this.ctx.log &&\n      this.ctx.log(\n        S.lex,\n        this.ctx,\n        rule,\n        this,\n        pnt,\n        sI,\n        match,\n        tkn,\n        alt,\n        altI,\n        tI,\n      )\n\n    // this.ctx.log &&\n    //   log_lex(this.ctx, rule, this, pnt, sI, match, tkn, alt, altI, tI)\n\n    if (this.ctx.sub.lex) {\n      this.ctx.sub.lex.map((sub) => sub(tkn as Token, rule, this.ctx))\n    }\n\n    return tkn\n  }\n\n  tokenize<R extends string | Tin, T extends R extends Tin ? string : Tin>(\n    ref: R,\n  ): T {\n    return tokenize(ref, this.cfg)\n  }\n\n  bad(why: string, pstart: number, pend: number) {\n    return this.token(\n      '#BD',\n      undefined,\n      0 <= pstart && pstart <= pend\n        ? this.src.substring(pstart, pend)\n        : this.src[this.pnt.sI],\n      undefined,\n      undefined,\n      why,\n    )\n  }\n}\n\nconst makeLex = (...params: ConstructorParameters<typeof LexImpl>) =>\n  new LexImpl(...params)\n\nexport {\n  makeNoToken,\n  makeLex,\n  makePoint,\n  makeToken,\n  makeMatchMatcher,\n  makeFixedMatcher,\n  makeSpaceMatcher,\n  makeLineMatcher,\n  makeStringMatcher,\n  makeCommentMatcher,\n  makeNumberMatcher,\n  makeTextMatcher,\n}\n"
  },
  {
    "path": "src/mfix.js",
    "content": "/* Copyright (c) 2021 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Uncomments a special fix that removes the need for `.default` when\n// requiring plain Node.js code.\n\nconst Fs = require('fs')\n\n// Inject as part of build step (see package.json).\nlet src = Fs.readFileSync(__dirname+'/../dist/jsonic.js').toString()\nsrc = src.replace(/\\/\\/-NODE-MODULE-FIX/, '')\nFs.writeFileSync(__dirname+'/../dist/jsonic.js', src)\n"
  },
  {
    "path": "src/parser.ts",
    "content": "/* Copyright (c) 2013-2026 Richard Rodger, MIT License */\n\n/*  parser.ts\n *  Parser implementation, converts the lexer tokens into parsed data.\n */\n\nimport type {\n  Config,\n  Context,\n  Options,\n  ParsePrepare,\n  Parser,\n  Rule,\n  RuleDefiner,\n  RuleSpec,\n  RuleSpecMap,\n  Jsonic,\n} from './types'\n\nimport { EMPTY } from './types'\n\nimport {\n  S,\n  badlex,\n  deep,\n  filterRules,\n  keys,\n  srcfmt,\n  tokenize,\n  // log_stack,\n  values,\n} from './utility'\n\nimport { JsonicError } from './error'\n\nimport { makeNoToken, makeLex, makePoint, makeToken } from './lexer'\n\nimport { makeRule, makeNoRule, makeRuleSpec } from './rules'\n\n\n// Install ES getter/setter accessors on a Context so the legacy\n// `ctx.t0` / `ctx.t1` names proxy to `ctx.t[0]` / `ctx.t[1]`. Reading\n// an unfetched slot yields NOTOKEN; writing seeds the array slot.\nfunction defineLookaheadAliases(ctx: any, notoken: any) {\n  // Skip if already installed (a plain own-data property would be\n  // overwritten; an accessor matching ours is left alone).\n  const existing = Object.getOwnPropertyDescriptor(ctx, 't0')\n  if (existing && existing.get) return\n\n  Object.defineProperties(ctx, {\n    t0: {\n      configurable: true,\n      enumerable: true,\n      get() { return this.t[0] ?? notoken },\n      set(v: any) { this.t[0] = v },\n    },\n    t1: {\n      configurable: true,\n      enumerable: true,\n      get() { return this.t[1] ?? notoken },\n      set(v: any) { this.t[1] = v },\n    },\n    // v1 / v2 used to be plain data slots; keep them as accessors on\n    // the growing `v` stack so existing grammar code reads the same\n    // \"most-recently consumed\" tokens.\n    v1: {\n      configurable: true,\n      enumerable: true,\n      get() { return this.v[this.v.length - 1] ?? notoken },\n      set(t: any) {\n        if (0 < this.v.length) this.v[this.v.length - 1] = t\n        else this.v.push(t)\n      },\n    },\n    v2: {\n      configurable: true,\n      enumerable: true,\n      get() { return this.v[this.v.length - 2] ?? notoken },\n      set(t: any) {\n        const L = this.v.length\n        if (1 < L) this.v[L - 2] = t\n        else if (1 === L) this.v.unshift(t)\n        else this.v.push(t)\n      },\n    },\n  })\n}\n\n\n// Rewind primitives. Attached to `ctx` at parse start so rule\n// actions can reach them via their `ctx` argument. `mark()` returns\n// an opaque absolute counter (ctx.vAbs); `rewind(mark)` replays the\n// tokens consumed since that mark by unshifting them back onto the\n// active lexer's pending-token queue, so subsequent `lex.next()`\n// calls re-serve them in forward order.\n//\n// Marks are absolute rather than array-relative so the ring-buffer\n// cap (options.rewind.history) can evict old tokens from the front\n// of ctx.v without invalidating mark values held by in-flight rule\n// actions. A rewind whose target has been evicted throws — the\n// caller's retained-history budget was too small for the grammar.\nfunction attachRewind(ctx: any): void {\n  ctx.mark = function (): number {\n    return this.vAbs\n  }\n  ctx.rewind = function (mark: number): void {\n    const k = this.vAbs - mark\n    if (k <= 0) return\n    if (k > this.v.length) {\n      throw new Error(\n        `jsonic: ctx.rewind target ${mark} is outside the retained ` +\n        `history window (oldest mark available is ${this.vAbs - this.v.length}, ` +\n        `current is ${this.vAbs}); increase options.rewind.history.`,\n      )\n    }\n    const queue: any[] = this.lex.pnt.token\n    const NOTOKEN = this.NOTOKEN\n    // The lookahead buffer (ctx.t) holds tokens the lexer has already\n    // produced past the current consumed position but that haven't\n    // been committed to ctx.v yet. They advanced the lexer's sI — so\n    // if we just invalidated the buffer, those source chars would be\n    // lost. Preserve them by splicing into the front of the pending\n    // queue in the order the lexer produced them, BEHIND the rewound\n    // consumed tokens that come next.\n    const pendingLookahead: any[] = []\n    for (let i = 0; i < this.t.length; i++) {\n      const tkn = this.t[i]\n      if (tkn && tkn !== NOTOKEN) pendingLookahead.push(tkn)\n      this.t[i] = NOTOKEN\n    }\n    // Un-shift pre-lexed lookahead (oldest-first order at the queue\n    // head), so the next lex.next() serves them in the same order they\n    // were originally produced.\n    for (let i = pendingLookahead.length - 1; i >= 0; i--) {\n      queue.unshift(pendingLookahead[i])\n    }\n    // Then unshift the rewound consumed tokens — they go in FRONT of\n    // the lookahead, so the next lex.next() serves the oldest rewound\n    // consumed token first, then the rest in order.\n    for (let i = 0; i < k; i++) {\n      // Pop newest-first, unshift in that order — the first unshift\n      // lands the newest at the queue's head; the next unshift slides\n      // older tokens in front of it, so the queue reads oldest-first.\n      queue.unshift(this.v.pop())\n    }\n    this.vAbs -= k\n    // Clear the lexer's cached end-of-source token so lex.next serves\n    // from the newly-replenished queue rather than short-circuiting\n    // to #ZZ. (Once the lexer has produced the end token it pins it\n    // to pnt.end; the rewound tokens would otherwise be unreachable.)\n    this.lex.pnt.end = undefined\n  }\n}\n\n\nclass ParserImpl implements Parser {\n  options: Options\n  cfg: Config\n  rsm: RuleSpecMap = {}\n  ji: Jsonic\n\n  constructor(options: Options, cfg: Config, j: Jsonic) {\n    this.options = options\n    this.cfg = cfg\n    this.ji = j\n  }\n\n  // TODO: ensure chains properly, both for create and extend rule\n  // Multi-functional get/set for rules.\n  rule(\n    name?: string,\n    define?: RuleDefiner | null,\n  ): RuleSpec | RuleSpecMap | undefined {\n    // If no name, get all the rules.\n    if (null == name) {\n      return this.rsm\n    }\n\n    // Else get a rule by name.\n    let rs: RuleSpec = this.rsm[name]\n\n    // Else delete a specific rule by name.\n    if (null === define) {\n      delete this.rsm[name]\n    }\n\n    // Else add or redefine a rule by name.\n    else if (undefined !== define) {\n      rs = this.rsm[name] = this.rsm[name] || makeRuleSpec(this.ji, this.cfg, {})\n      rs.name = name\n      rs = this.rsm[name] = define(this.rsm[name], this) || this.rsm[name]\n\n      // Ensures jsonic.rule can chain\n      return undefined\n    }\n\n    return rs\n  }\n\n  start(src: string, jsonic: any, meta?: any, parent_ctx?: any): any {\n    let root: Rule\n\n    let endtkn = makeToken(\n      '#ZZ',\n      tokenize('#ZZ', this.cfg),\n      undefined,\n      EMPTY,\n      makePoint(-1),\n    )\n\n    let notoken = makeNoToken()\n\n    let ctx: Context = {\n      uI: 0,\n      opts: this.options,\n      cfg: this.cfg,\n      meta: meta || {},\n      src: () => src, // Avoid printing src\n      root: () => root,\n      plgn: () => jsonic.internal().plugins,\n      inst: () => jsonic,\n      rule: {} as Rule,\n      sub: jsonic.internal().sub,\n      xs: -1,\n      // Consumed-token history. Legacy v1 / v2 accessors (installed by\n      // defineLookaheadAliases) read the top of this stack. ctx.vAbs\n      // is the absolute count of pushed-and-not-rewound tokens since\n      // parse start; ctx.mark() returns it so ring-buffer eviction of\n      // old entries doesn't invalidate outstanding marks.\n      v: [],\n      vAbs: 0,\n      // Lookahead buffer. Seeded with two NOTOKEN slots; grows as alts\n      // request deeper positions via ctx.t[i].\n      t: [notoken, notoken],\n      tC: -2, // Prepare count for lookahead (two slots preseeded above).\n      kI: -1,\n      rs: [],\n      rsI: 0,\n      rsm: this.rsm,\n      log: undefined,\n      F: srcfmt(this.cfg),\n      u: {},\n      NOTOKEN: notoken,\n      NORULE: {} as Rule,\n    } as unknown as Context\n\n    // Legacy accessors: ctx.t0 / ctx.t1 proxy to ctx.t[0] / ctx.t[1].\n    defineLookaheadAliases(ctx, notoken)\n\n    ctx = deep(ctx, parent_ctx)\n\n    // `deep` may return a fresh object (when parent_ctx is a plain\n    // object); re-install the accessors on the result so they survive.\n    defineLookaheadAliases(ctx, notoken)\n\n    let norule = makeNoRule(this.ji, ctx)\n    ctx.NORULE = norule\n    ctx.rule = norule\n\n    // makelog(ctx, meta)\n    if (meta && S.function === typeof meta.log) {\n      ctx.log = meta.log\n    }\n\n    this.cfg.parse.prepare.forEach((prep: ParsePrepare) =>\n      prep(jsonic, ctx, meta),\n    )\n\n    // Special case - avoids extra per-token tests in main parser rules.\n    if ('' === src) {\n      if (this.cfg.lex.empty) {\n        return this.cfg.lex.emptyResult\n      } else {\n        throw new JsonicError(S.unexpected, { src }, ctx.t0, norule, ctx)\n      }\n    }\n\n    let lex = badlex(makeLex(ctx), tokenize('#BD', this.cfg), ctx)\n\n    // Stash lex on ctx so ctx.rewind can push tokens back onto the\n    // lexer's pending-token queue. Also attach the rewind primitives.\n    ;(ctx as any).lex = lex\n    attachRewind(ctx)\n\n    let startspec = this.rsm[this.cfg.rule.start]\n\n    if (null == startspec) {\n      return undefined\n    }\n\n    let rule = makeRule(startspec, ctx)\n\n    root = rule\n\n    // Maximum rule iterations (prevents infinite loops). Allow for\n    // rule open and close, and for each rule on each char to be\n    // virtual (like map, list), and double for safety margin (allows\n    // lots of backtracking), and apply a multipler option as a get-out-of-jail.\n    let maxr =\n      2 * keys(this.rsm).length * lex.src.length * 2 * ctx.cfg.rule.maxmul\n\n    // Process rules on tokens\n    let kI = 0\n\n    // This loop is the heart of the engine. Keep processing rule\n    // occurrences until there's none left.\n    while (norule !== rule && kI < maxr) {\n      ctx.kI = kI\n      ctx.rule = rule\n\n      ctx.log && ctx.log(S.step, ctx.kI + ':')\n\n      if (ctx.sub.rule) {\n        ctx.sub.rule.map((sub) => sub(rule, ctx))\n      }\n\n      rule = rule.process(ctx, lex)\n\n      ctx.log && ctx.log(S.stack, ctx, rule, lex)\n\n      kI++\n    }\n\n    // TODO: option to allow trailing content\n    if (endtkn.tin !== lex.next(rule).tin) {\n      throw new JsonicError(S.unexpected, {}, ctx.t0, norule, ctx)\n    }\n\n    // NOTE: by returning root, we get implicit closing of maps and lists.\n    const result = ctx.root().node\n\n    if (this.cfg.result.fail.includes(result)) {\n      throw new JsonicError(S.unexpected, {}, ctx.t0, norule, ctx)\n    }\n\n    return result\n  }\n\n  clone(options: Options, config: Config, j: Jsonic) {\n    let parser = new ParserImpl(options, config, j)\n\n    // Inherit rules from parent, filtered by config.rule\n    parser.rsm = Object.keys(this.rsm).reduce(\n      (a, rn) => ((a[rn] = filterRules(this.rsm[rn], this.cfg)), a),\n      {} as any,\n    )\n\n    parser.norm()\n\n    return parser\n  }\n\n  norm() {\n    values(this.rsm).map((rs: RuleSpec) => rs.norm())\n  }\n}\n\nconst makeParser = (...params: ConstructorParameters<typeof ParserImpl>) =>\n  new ParserImpl(...params)\n\nexport { makeRule, makeRuleSpec, makeParser }\n"
  },
  {
    "path": "src/rules.ts",
    "content": "/* Copyright (c) 2013-2023 Richard Rodger, MIT License */\n\n/*  rules.ts\n *  Parser rules.\n */\n\nimport type {\n  AltAction,\n  AltCond,\n  AltMatch,\n  AltModifier,\n  AltSpec,\n  Bag,\n  Config,\n  Context,\n  Counters,\n  FuncRef,\n  FuncRefMap,\n  Jsonic,\n  Lex,\n  ListMods,\n  NormAltCond,\n  NormAltSpec,\n  Rule,\n  RuleSpec,\n  RuleState,\n  RuleStep,\n  StateAction,\n  Tin,\n  Token,\n} from './types'\n\nimport { OPEN, CLOSE, BEFORE, AFTER, EMPTY, STRING } from './types'\n\nimport {\n  S,\n  defprop,\n  filterRules,\n  getpath,\n  isarr,\n  modlist,\n  tokenize,\n} from './utility'\n\nimport { JsonicError } from './error'\n\nclass RuleImpl implements Rule {\n  i = -1\n  name = EMPTY\n  node = null\n  state = OPEN\n  n = Object.create(null)\n  d = -1\n  u = Object.create(null)\n  k = Object.create(null)\n  bo = false\n  ao = false\n  bc = false\n  ac = false\n\n  oN = 0\n  cN = 0\n\n  spec: RuleSpec\n  child: Rule\n  parent: Rule\n  prev: Rule\n  next: Rule\n\n  // Canonical storage for matched tokens at each lookahead position.\n  o: Token[]\n  c: Token[]\n\n  // Per-rule NOTOKEN reference (from ctx), used by legacy accessors.\n  // Optional so the structural type of RuleImpl stays compatible with\n  // the Rule interface (which does not declare this field).\n  _NOTOKEN?: Token\n\n  need = 0\n\n  constructor(spec: RuleSpec, ctx: Context, node?: any) {\n    this.i = ctx.uI++ // Rule ids are unique only to the parse run.\n    this.name = spec.name\n    this.spec = spec\n\n    this.child = ctx.NORULE\n    this.parent = ctx.NORULE\n    this.prev = ctx.NORULE\n    this.next = ctx.NORULE\n\n    this._NOTOKEN = ctx.NOTOKEN\n    this.o = []\n    this.c = []\n\n    this.node = node\n    this.d = ctx.rsI\n    this.bo = null != spec.def.bo\n    this.ao = null != spec.def.ao\n    this.bc = null != spec.def.bc\n    this.ac = null != spec.def.ac\n  }\n\n  // Legacy aliases for o[0], o[1], c[0], c[1] and the count fields.\n  // Maintained so existing grammar/plugin code that reads r.o0/r.o1/r.os\n  // (and r.c0/r.c1/r.cs) continues to work unchanged.\n  get o0(): Token { return this.o[0] ?? (this._NOTOKEN as Token) }\n  set o0(v: Token) { this.o[0] = v }\n  get o1(): Token { return this.o[1] ?? (this._NOTOKEN as Token) }\n  set o1(v: Token) { this.o[1] = v }\n  get c0(): Token { return this.c[0] ?? (this._NOTOKEN as Token) }\n  set c0(v: Token) { this.c[0] = v }\n  get c1(): Token { return this.c[1] ?? (this._NOTOKEN as Token) }\n  set c1(v: Token) { this.c[1] = v }\n  get os(): number { return this.oN }\n  set os(v: number) { this.oN = v }\n  get cs(): number { return this.cN }\n  set cs(v: number) { this.cN = v }\n\n  process(ctx: Context, lex: Lex): Rule {\n    let rule = this.spec.process(this, ctx, lex, this.state)\n    return rule\n  }\n\n  eq(counter: string, limit: number = 0): boolean {\n    let value = this.n[counter]\n    return null == value || value === limit\n  }\n\n  lt(counter: string, limit: number = 0): boolean {\n    let value = this.n[counter]\n    return null == value || value < limit\n  }\n\n  gt(counter: string, limit: number = 0): boolean {\n    let value = this.n[counter]\n    return null == value || value > limit\n  }\n\n  lte(counter: string, limit: number = 0): boolean {\n    let value = this.n[counter]\n    return null == value || value <= limit\n  }\n\n  gte(counter: string, limit: number = 0): boolean {\n    let value = this.n[counter]\n    return null == value || value >= limit\n  }\n\n  toString() {\n    return '[Rule ' + this.name + '~' + this.i + ']'\n  }\n}\n\nconst makeRule = (...params: ConstructorParameters<typeof RuleImpl>) =>\n  new RuleImpl(...params)\n\nconst makeNoRule = (j: Jsonic, ctx: Context) => makeRule(makeRuleSpec(j, ctx.cfg, {}), ctx)\n\n// Parse-alternate match (built from current tokens and AltSpec).\nclass AltMatchImpl implements AltMatch {\n  p = EMPTY // Push rule (by name).\n  r = EMPTY // Replace rule (by name).\n  b = 0 // Move token position backward.\n  c?: AltCond // Custom alt match condition.\n  n?: Counters // increment named counters.\n  a?: AltAction // Match actions.\n  h?: AltModifier // Modify alternate match.\n  u?: Bag // Custom props to add to Rule.use.\n  k?: Bag // Custom props to add to Rule.keep and keep via push and replace.\n  g?: string[] // Named group tags (allows plugins to find alts).\n  e?: Token // Errored on this token.\n}\n\nconst makeAltMatch = (...params: ConstructorParameters<typeof AltMatchImpl>) =>\n  new AltMatchImpl(...params)\n\nconst PALT: AltMatch = makeAltMatch() // Only one alt object is created.\nconst EMPTY_ALT = makeAltMatch()\n\nclass RuleSpecImpl implements RuleSpec {\n  name = EMPTY // Set by Parser.rule\n  def = {\n    open: [] as AltSpec[],\n    close: [] as AltSpec[],\n    bo: [] as StateAction[],\n    bc: [] as StateAction[],\n    ao: [] as StateAction[],\n    ac: [] as StateAction[],\n    tcol: [] as Tin[][][],\n    fnref: {} as FuncRefMap<Function>,\n  }\n  cfg: Config\n  ji: Jsonic\n\n\n  constructor(j: Jsonic, cfg: Config, def: any) {\n    this.ji = j\n    this.cfg = cfg\n    this.def = Object.assign(this.def, def)\n\n    // Null Alt entries are allowed and ignored as a convenience.\n    this.def.open = (this.def.open || []).filter((alt: AltSpec) => null != alt)\n    this.def.close = (this.def.close || []).filter(\n      (alt: AltSpec) => null != alt,\n    )\n\n    for (let alt of this.def.open) {\n      normalt(alt, OPEN, this)\n    }\n\n    for (let alt of this.def.close) {\n      normalt(alt, CLOSE, this)\n    }\n\n    const anames = ['bo', 'ao', 'bc', 'ac']\n    for (let an of anames) {\n      for (let sa of ((this.def as any)[an] ?? [])) {\n        if ('object' === typeof sa) {\n          let sadef = sa as any\n          (this as any)[an](sadef.append, sadef.action)\n        }\n      }\n    }\n  }\n\n  // Convenience access to token Tins\n  tin<R extends string | Tin, T extends R extends Tin ? string : Tin>(\n    ref: R,\n  ): T {\n    return tokenize(ref, this.cfg)\n  }\n\n\n  fnref(frm: Record<FuncRef, Function>): RuleSpec {\n    Object.assign(this.def.fnref, frm)\n\n    // Auto-install reserved `@<rulename>-<phase>` handlers as state\n    // actions. Dedupe by function identity per phase: registering the\n    // same function twice (directly or via a later fnref() call that\n    // also passes it) installs only one action, but distinct functions\n    // for the same phase all install. This accommodates grammars that\n    // layer handlers (e.g. core sets a basic @list-bo, then a richer\n    // one replaces/augments it) while preventing the old bug where\n    // iterating the accumulated fnref map re-installed every\n    // previously-registered handler on every call.\n    const rn = this.name\n    const fr: any = this.def.fnref\n    const installed: Map<string, WeakSet<Function>> =\n      ((this.def as any).fnrefInstalled =\n        (this.def as any).fnrefInstalled || new Map())\n\n    const reserved = [`@${rn}-bo`, `@${rn}-ao`, `@${rn}-bc`, `@${rn}-ac`]\n    for (let base of reserved) {\n      let phaseSet = installed.get(base)\n      if (!phaseSet) installed.set(base, phaseSet = new WeakSet())\n\n      const aname = base.replace(/^[^-]+-/, '')\n      const prependFn = fr[base + '/prepend']\n      const appendFn = fr[base + '/append'] ?? fr[base]\n\n      if (prependFn && !phaseSet.has(prependFn)) {\n        phaseSet.add(prependFn)\n          ; (this as any)[aname](false, prependFn)\n      }\n      if (appendFn && !phaseSet.has(appendFn)) {\n        phaseSet.add(appendFn)\n          ; (this as any)[aname](true, appendFn)\n      }\n    }\n\n    return this\n  }\n\n\n  add(rs: RuleState, a: AltSpec | AltSpec[], mods?: ListMods): RuleSpec {\n    let inject = mods?.append ? 'push' : 'unshift'\n    let aa = ((isarr(a) ? a : [a]) as AltSpec[])\n      .filter((alt: AltSpec) => null != alt && 'object' === typeof alt)\n      .map((a) => normalt(a, rs, this))\n    let altState: 'open' | 'close' = 'o' === rs ? 'open' : 'close'\n    let alts: any = this.def[altState]\n\n    alts[inject](...aa)\n\n    alts = this.def[altState] = modlist(alts, mods)\n\n    filterRules(this, this.cfg)\n\n    this.norm()\n\n    return this\n  }\n\n\n  open(a: AltSpec | AltSpec[], mods?: ListMods): RuleSpec {\n    return this.add('o', a, mods)\n  }\n\n  close(a: AltSpec | AltSpec[], mods?: ListMods): RuleSpec {\n    return this.add('c', a, mods)\n  }\n\n  action(\n    append: boolean,\n    step: RuleStep,\n    state: RuleState,\n    action: StateAction,\n  ): RuleSpec {\n    let actions = (this.def as any)[step + state]\n    if (append) {\n      actions.push(action)\n    } else {\n      actions.unshift(action)\n    }\n    return this\n  }\n\n  bo(append: StateAction | boolean | FuncRef, action?: StateAction): RuleSpec {\n    return this.action(\n      action ? !!append : true,\n      BEFORE,\n      OPEN,\n      'string' === typeof append ? this.def.fnref[append as FuncRef] as StateAction :\n        (action ?? (append as StateAction)),\n    )\n  }\n\n  ao(append: StateAction | boolean, action?: StateAction): RuleSpec {\n    return this.action(\n      action ? !!append : true,\n      AFTER,\n      OPEN,\n      'string' === typeof append ? this.def.fnref[append as FuncRef] as StateAction :\n        (action ?? (append as StateAction)),\n    )\n  }\n\n  bc(append: StateAction | boolean, action?: StateAction): RuleSpec {\n    return this.action(\n      action ? !!append : true,\n      BEFORE,\n      CLOSE,\n      'string' === typeof append ? this.def.fnref[append as FuncRef] as StateAction :\n        (action ?? (append as StateAction)),\n    )\n  }\n\n  ac(append: StateAction | boolean, action?: StateAction): RuleSpec {\n    return this.action(\n      action ? !!append : true,\n      AFTER,\n      CLOSE,\n      'string' === typeof append ? this.def.fnref[append as FuncRef] as StateAction :\n        (action ?? (append as StateAction)),\n    )\n  }\n\n  clear() {\n    this.def.open.length = 0\n    this.def.close.length = 0\n    this.def.bo.length = 0\n    this.def.ao.length = 0\n    this.def.bc.length = 0\n    this.def.ac.length = 0\n    return this\n  }\n\n  norm() {\n    this.def.open.map((alt) => normalt(alt, OPEN, this))\n    this.def.close.map((alt) => normalt(alt, CLOSE, this))\n\n    // [stateI is o=0,c=1][tokenI is 0..maxS-1][tins]\n    const columns: Tin[][][] = []\n\n    // Compute max lookahead depth declared across this rule's alts,\n    // per state. Generalizes the previous hard-coded 2-slot collation.\n    const maxS = (alts: any[]): number =>\n      alts.reduce((m: number, a: any) => Math.max(m, a.sN || 0), 0)\n    const maxOpen = maxS(this.def.open)\n    const maxClose = maxS(this.def.close)\n\n    for (let tI = 0; tI < maxOpen; tI++) {\n      this.def.open.reduce(...collate(0, tI, columns))\n    }\n    for (let tI = 0; tI < maxClose; tI++) {\n      this.def.close.reduce(...collate(1, tI, columns))\n    }\n\n    // Ensure tcol[stateI] exists with enough slots so lexer.ts:264-268\n    // can always index `tcol[oc][tI]` safely for any tI the parser\n    // passes (bounded by this rule's own maxS).\n    columns[0] = columns[0] || []\n    columns[1] = columns[1] || []\n    for (let tI = 0; tI < maxOpen; tI++) columns[0][tI] = columns[0][tI] || []\n    for (let tI = 0; tI < maxClose; tI++) columns[1][tI] = columns[1][tI] || []\n\n    this.def.tcol = columns\n\n    function collate(\n      stateI: number,\n      tokenI: number,\n      columns: Tin[][][],\n    ): [any, any] {\n      columns[stateI] = columns[stateI] || []\n      let tins = (columns[stateI][tokenI] = columns[stateI][tokenI] || [])\n\n      return [\n        function(tins: any, alt: any) {\n          let resolved = alt.t && alt.t[tokenI]\n          if (resolved && 0 < resolved.length) {\n            let newtins = [...new Set(tins.concat(resolved))]\n            tins.length = 0\n            tins.push(...newtins)\n          }\n          return tins\n        },\n        tins,\n      ]\n    }\n\n    return this\n  }\n\n\n  process(rule: Rule, ctx: Context, lex: Lex, state: RuleState): Rule {\n    ctx.log && ctx.log(S.rule, ctx, rule, lex)\n\n    let is_open = state === 'o'\n    let next = is_open ? rule : ctx.NORULE\n    let why = is_open ? 'O' : 'C'\n    let def = this.def\n\n    // Match alternates for current state.\n    let alts = (is_open ? def.open : def.close) as NormAltSpec[]\n\n    // Handle \"before\" call.\n    let befores = is_open ? (rule.bo ? def.bo : null) : rule.bc ? def.bc : null\n    if (befores) {\n      let bout: Token | void = undefined\n      for (let bI = 0; bI < befores.length; bI++) {\n        bout = befores[bI].call(this, rule, ctx, next, bout)\n        if (bout?.isToken && bout?.err) {\n          return this.bad(bout, rule, ctx, { is_open })\n        }\n      }\n    }\n\n    // Attempt to match one of the alts.\n    let alt: AltMatch =\n      0 < alts.length ? parse_alts(is_open, alts, lex, rule, ctx) : EMPTY_ALT\n\n    // Custom alt handler.\n    if (alt.h) {\n      alt = alt.h(rule, ctx, alt, next) || alt\n      why += 'H'\n    }\n\n    // Unconditional error.\n    if (alt.e) {\n      return this.bad(alt.e, rule, ctx, { is_open })\n    }\n\n    // Update counters.\n    if (alt.n) {\n      for (let cn in alt.n) {\n        rule.n[cn] =\n          // 0 reverts counter to 0.\n          0 === alt.n[cn]\n            ? 0\n            : // First seen, set to 0.\n            (null == rule.n[cn]\n              ? 0\n              : // Increment counter.\n              rule.n[cn]) + alt.n[cn]\n      }\n    }\n\n    // Set custom properties\n    if (alt.u) {\n      rule.u = Object.assign(rule.u, alt.u)\n    }\n    if (alt.k) {\n      rule.k = Object.assign(rule.k, alt.k)\n    }\n\n    // Record consumed tokens (matched minus backtrack) on the v\n    // history BEFORE running alt actions, so an action that calls\n    // ctx.rewind sees the just-matched tokens on top of the stack.\n    // The lookahead-buffer shift itself still happens at the end of\n    // process() so non-action paths behave identically.\n    //\n    // ctx.vAbs is an absolute monotonic counter used as the mark\n    // value — it's decoupled from ctx.v.length so the ring-buffer\n    // cap can evict old tokens from the front without invalidating\n    // outstanding marks (marks older than the retained window will\n    // simply fail at rewind time with a clear error).\n    const _cons = rule[is_open ? 'oN' : 'cN'] - (alt.b || 0)\n    if (0 < _cons) {\n      // Move consumed tokens from ctx.t → ctx.v. Clear the tbuf slots\n      // so a ctx.rewind call inside the subsequent alt action can\n      // distinguish \"token already in v\" (NOTOKEN here; will be\n      // replayed from v) from \"pre-lexed lookahead past consumed\"\n      // (real token in tbuf; needs re-queuing to preserve state).\n      const NOTOKEN = ctx.NOTOKEN\n      for (let i = 0; i < _cons; i++) {\n        ctx.v.push(ctx.t[i])\n        ctx.t[i] = NOTOKEN\n      }\n      ;(ctx as any).vAbs += _cons\n      // Amortised-O(1) ring-buffer cap: let v grow to twice the\n      // capacity, then splice its front back down. Batch-eviction\n      // makes each push O(1) on average even at the cap.\n      const cap = ctx.cfg.rewind.history\n      if (cap !== Infinity && ctx.v.length > 2 * cap) {\n        ctx.v.splice(0, ctx.v.length - cap)\n      }\n    }\n\n    // TODO: move after rule.next resolution\n    // (breaks Expr! - fix first)\n    // Action call.\n    if (alt.a) {\n      why += 'A'\n      let tout = alt.a(rule, ctx, alt)\n      if (tout && tout.isToken && tout.err) {\n        return this.bad(tout, rule, ctx, { is_open })\n      }\n    }\n\n    // Push a new rule onto the stack...\n    if (alt.p) {\n      ctx.rs[ctx.rsI++] = rule\n      let rulespec = ctx.rsm[alt.p]\n      if (rulespec) {\n        next = rule.child = makeRule(rulespec, ctx, rule.node)\n        next.parent = rule\n        next.n = { ...rule.n }\n        if (0 < Object.keys(rule.k).length) {\n          next.k = { ...rule.k }\n        }\n        why += 'P`' + alt.p + '`'\n      }\n      else {\n        return this.bad(this.unknownRule(ctx.t0, alt.p), rule, ctx, { is_open })\n      }\n    }\n\n    // ...or replace with a new rule.\n    else if (alt.r) {\n      let rulespec = ctx.rsm[alt.r]\n      if (rulespec) {\n        next = makeRule(rulespec, ctx, rule.node)\n        next.parent = rule.parent\n        next.prev = rule\n        next.n = { ...rule.n }\n        if (0 < Object.keys(rule.k).length) {\n          next.k = { ...rule.k }\n        }\n        why += 'R`' + alt.r + '`'\n      }\n      else {\n        return this.bad(this.unknownRule(ctx.t0, alt.r), rule, ctx, { is_open })\n      }\n    }\n\n    // Pop closed rule off stack.\n    else if (!is_open) {\n      next = ctx.rs[--ctx.rsI] || ctx.NORULE\n    }\n\n\n    // TODO: move action call here (alt.a)\n    // and set r.next = next, so that action has access to next\n\n    rule.next = next\n\n\n    // Handle \"after\" call.\n    let afters = is_open ? (rule.ao ? def.ao : null) : rule.ac ? def.ac : null\n    if (afters) {\n      let aout: Token | void = undefined\n      for (let aI = 0; aI < afters.length; aI++) {\n        aout = afters[aI](rule, ctx, next, aout)\n        if (aout?.isToken && aout?.err) {\n          return this.bad(aout, rule, ctx, { is_open })\n        }\n      }\n    }\n\n    next.why = why\n\n    ctx.log && ctx.log(S.node, ctx, rule, lex, next)\n\n    // Must be last as state change is for next process call.\n    if (OPEN === rule.state) {\n      rule.state = CLOSE\n    }\n\n    // Backtrack reduces consumed token count.\n    let consumed = rule[is_open ? 'oN' : 'cN'] - (alt.b || 0)\n    if (consumed < 0) consumed = 0\n\n    if (0 < consumed) {\n      // Shift the lookahead buffer left by `consumed` slots, filling\n      // vacated tail positions with NOTOKEN so later alts re-fetch.\n      // (The corresponding v-history push ran before alt actions.)\n      const L = ctx.t.length\n      for (let i = 0; i < L - consumed; i++) ctx.t[i] = ctx.t[i + consumed]\n      for (let i = Math.max(0, L - consumed); i < L; i++) ctx.t[i] = ctx.NOTOKEN\n    }\n\n    return next\n  }\n\n  bad(tkn: Token, rule: Rule, ctx: Context, parse: { is_open: boolean }): Rule {\n    throw new JsonicError(\n      tkn.err || S.unexpected,\n      {\n        ...tkn.use,\n        state: parse.is_open ? S.open : S.close,\n      },\n      tkn,\n      rule,\n      ctx,\n    )\n  }\n\n  unknownRule(tkn: Token, name: string): Token {\n    tkn.err = 'unknown_rule'\n    tkn.use = tkn.use || {}\n    tkn.use.rulename = name\n    return tkn\n  }\n}\n\nconst makeRuleSpec = (...params: ConstructorParameters<typeof RuleSpecImpl>) =>\n  new RuleSpecImpl(...params)\n\n// First match wins.\n// NOTE: input AltSpecs are used to build the Alt output.\nfunction parse_alts(\n  is_open: boolean,\n  alts: NormAltSpec[],\n  lex: Lex,\n  rule: Rule,\n  ctx: Context,\n): AltMatch {\n  let out = PALT\n  out.b = 0 // Backtrack n tokens.\n  out.p = EMPTY // Push named rule onto stack.\n  out.r = EMPTY // Replace current rule with named rule.\n  out.n = undefined // Increment named counters.\n  out.h = undefined // Custom handler function.\n  out.a = undefined // Rule action.\n  out.u = undefined // Custom rule properties.\n  out.k = undefined // Custom rule properties (propagated).\n  out.e = undefined // Error token.\n\n  let alt: NormAltSpec | null = null\n  let altI = 0\n  let t = ctx.cfg.t\n  let cond: boolean = true\n  let bitAA = 1 << (t.AA - 1)\n\n  let IGNORE = ctx.cfg.tokenSetTins.IGNORE\n\n  function next(r: Rule, alt: NormAltSpec, altI: number, tI: number) {\n    let tkn\n    do {\n      tkn = lex.next(r, alt, altI, tI)\n      ctx.tC++\n    } while (IGNORE[tkn.tin])\n    return tkn\n  }\n\n  // TODO: replace with lookup map\n  let len = alts.length\n  const NOTOKEN = ctx.NOTOKEN\n  const tbuf = ctx.t\n\n  for (altI = 0; altI < len; altI++) {\n    alt = alts[altI] as NormAltSpec\n\n    // Number of positions that matched in this alt. Tracked so the\n    // rule can record exactly which tokens it consumed.\n    let matched = 0\n    cond = true\n\n    const S = alt.S\n    const sN = alt.sN | 0\n\n    // Iterate alt's lookahead positions. Each position is fetched\n    // lazily and only when the previous position matched, preserving\n    // the original 2-slot lazy behaviour for any N.\n    //\n    // A null entry in S[i] means \"no Tin constraint at this position\"\n    // (wildcard) - the token is still fetched and consumed, but the\n    // bit-field check is skipped. This matches the `s` docstring\n    // (\"null if position matches any token\") and prevents silently\n    // dropping the check at a later required position.\n    for (let i = 0; i < sN; i++) {\n      let tkn = tbuf[i]\n      if (null == tkn || NOTOKEN === tkn) {\n        tkn = tbuf[i] = next(rule, alt, altI, i)\n      }\n\n      const Si = S ? S[i] : null\n      if (null != Si) {\n        const tin = tkn.tin\n        const part = (tin / 31) | 0\n        // bitAA lives in partition 0 (tin=AA=4). ORing it into the\n        // match mask for any partition other than 0 lets unrelated\n        // tokens in higher partitions collide with alts that merely\n        // set bit 3 of their own partition — a false positive. Apply\n        // bitAA only when testing a partition-0 token.\n        const aaBit = part === 0 ? bitAA : 0\n        if (!(Si[part] & ((1 << ((tin % 31) - 1)) | aaBit))) {\n          cond = false\n          break\n        }\n      }\n      matched = i + 1\n    }\n\n    if (is_open) {\n      rule.oN = matched\n      for (let i = 0; i < matched; i++) rule.o[i] = tbuf[i]\n      // Clear trailing slots so stale matches from earlier alts are\n      // not observed via rule.o[i] / rule.o0 / rule.o1 accessors.\n      for (let i = matched; i < rule.o.length; i++) rule.o[i] = NOTOKEN\n    } else {\n      rule.cN = matched\n      for (let i = 0; i < matched; i++) rule.c[i] = tbuf[i]\n      for (let i = matched; i < rule.c.length; i++) rule.c[i] = NOTOKEN\n    }\n\n    // Optional custom condition\n    if (cond && alt.c) {\n      cond = cond && alt.c(rule, ctx, out)\n    }\n\n    if (cond) {\n      break\n    }\n    else {\n      alt = null\n    }\n  }\n\n  if (!cond) {\n    out.e = tbuf[0] ?? NOTOKEN\n  }\n\n  if (alt) {\n    out.n = null != alt.n ? alt.n : out.n\n    out.h = null != alt.h ? alt.h : out.h\n    out.a = null != alt.a ? alt.a : out.a\n    out.u = null != alt.u ? alt.u : out.u\n    out.k = null != alt.k ? alt.k : out.k\n    out.g = null != alt.g ? alt.g : out.g\n\n    out.e = (alt.e && alt.e(rule, ctx, out)) || undefined\n\n    out.p =\n      null != alt.p && false !== alt.p\n        ? 'string' === typeof alt.p\n          ? alt.p\n          : alt.p(rule, ctx, out)\n        : out.p\n\n    out.r =\n      null != alt.r && false !== alt.r\n        ? 'string' === typeof alt.r\n          ? alt.r\n          : alt.r(rule, ctx, out)\n        : out.r\n\n    out.b =\n      null != alt.b && false !== alt.b\n        ? 'number' === typeof alt.b\n          ? alt.b\n          : alt.b(rule, ctx, out)\n        : out.b\n  }\n\n  let match = altI < alts.length\n\n  ctx.log && ctx.log(S.parse, ctx, rule, lex, match, cond, altI, alt, out)\n\n  return out\n}\n\n\nconst partify = (tins: Tin[], part: number) =>\n  tins.filter((tin) => 31 * part <= tin && tin < 31 * (part + 1))\n\nconst bitify = (s: Tin[], part: number) =>\n  s.reduce(\n    (bits: number, tin: Tin) => (1 << (tin - (31 * part + 1))) | bits,\n    0,\n  )\n\n\n// Valid group-tag pattern: lowercase letter followed by one or more\n// lowercase letters, digits, or hyphens. Enforced by normalt().\nconst GROUP_TAG_RE = /^[a-z][a-z0-9-]+$/\n\n// Normalize AltSpec (mutates).\nfunction normalt(a: AltSpec, rs: RuleState, r: RuleSpec): NormAltSpec {\n  // Ensure groups are a string[]\n  if (STRING === typeof a.g) {\n    a.g = (a as any).g.split(/\\s*,\\s*/)\n  } else if (null == a.g) {\n    a.g = []\n  }\n\n  // Validate every group tag (reject empty and non-matching tags).\n  for (let tag of (a.g as string[])) {\n    if (!GROUP_TAG_RE.test(tag)) {\n      throw new Error(\n        `Grammar: invalid group tag \"${tag}\" ` +\n        `in rule ${r.name} (${rs}) — must match ${GROUP_TAG_RE}`\n      )\n    }\n  }\n\n  a.g = (a as any).g.sort()\n\n  const aa = a as any\n\n  if (!a.s || 0 === a.s.length) {\n    a.s = null\n    aa.t = []\n    aa.S = null\n    aa.sN = 0\n  }\n  else {\n    const tinsify = (s: any[]): Tin[] => {\n      const tins = s\n        .flat()\n        .map((n) => 'string' === typeof n ? n.split(/\\s* +\\s*/) : n)\n        .flat()\n        .map((n) => 'string' === typeof n ? (r.ji.tokenSet(n) ?? r.ji.token(n)) : n)\n        .flat()\n        .filter((tin) => 'number' === typeof tin)\n      return tins\n    }\n\n\n    if ('string' === typeof a.s) {\n      a.s = a.s.split(/\\s* +\\s*/)\n    }\n\n    // Per-position resolved tins and bit-field match tables.\n    // alt.t[i] holds the Tin[] for position i (used by tcol collation);\n    // alt.S[i] holds the bit-packed lookup (null if position is empty,\n    // which should not normally occur - tinsify filters nulls).\n    const sN = a.s.length\n    const t: Tin[][] = new Array(sN)\n    const S: (number[] | null)[] = new Array(sN)\n\n    for (let i = 0; i < sN; i++) {\n      const tins: Tin[] = tinsify([a.s[i]])\n      t[i] = tins\n      // `#AA` is the ANY wildcard — a position whose tin list\n      // includes it must match every lexed token regardless of\n      // partition. Represent that by dropping to the existing\n      // `S[i] = null` sentinel (\"no constraint\"), bypassing the\n      // per-partition bitset check in parse_alts. The t[i] entry\n      // keeps the raw tin list so tcol collation still reflects\n      // what the user wrote.\n      const aaTin = r.ji.token('#AA')\n      if (aaTin != null && tins.includes(aaTin)) {\n        S[i] = null\n        continue\n      }\n      S[i] =\n        0 < tins.length\n          ? new Array(Math.max(...tins.map((tin) => (1 + tin / 31) | 0)))\n            .fill(null)\n            .map((_, j) => j)\n            .map((part) => bitify(partify(tins, part), part))\n          : null\n    }\n\n    aa.t = t\n    aa.S = S\n    aa.sN = sN\n  }\n\n  if (!a.p) {\n    a.p = null\n  }\n  else {\n    resolveFunctionRef('push', rs, r, a, 'p')\n  }\n\n  if (!a.r) {\n    a.r = null\n  }\n  else {\n    resolveFunctionRef('replace', rs, r, a, 'r')\n  }\n\n  if (!a.b) {\n    a.b = null\n  }\n  else {\n    resolveFunctionRef('back', rs, r, a, 'b')\n  }\n\n  if (!a.a) {\n    a.a = null\n  }\n  else {\n    resolveFunctionRef('action', rs, r, a, 'a')\n  }\n\n  if (!a.h) {\n    a.h = null\n  }\n  else {\n    resolveFunctionRef('modify', rs, r, a, 'h')\n  }\n\n  if (!a.e) {\n    a.e = null\n  }\n  else {\n    resolveFunctionRef('error', rs, r, a, 'e')\n  }\n\n\n  if (!a.c) {\n    a.c = null\n  }\n  else {\n    const ct = typeof a.c\n\n    if ('string' === ct) {\n      resolveFunctionRef('condition', rs, r, a, 'c')\n    }\n    else if ('function' === ct) {\n      if ('c' === a.c.name) {\n        defprop(a.c, 'name', { value: 'ruleCond' })\n      }\n    }\n    else if ('object' === ct) {\n      const ac: Record<string, any> = a.c\n      const conds: NormAltCond[] = []\n      const ruleprops = Object.keys(a.c)\n      for (let prop of ruleprops) {\n        const pspec = ac[prop]\n        if (null != pspec) {\n          if ('object' === typeof pspec) {\n            for (let co of Object.keys(pspec)) {\n              if (1 === COND_OPS[co]) {\n                conds.push(makeRuleCond(co, prop, pspec[co]))\n              }\n            }\n          }\n          else {\n            conds.push(makeRuleCond('$eq', prop, pspec))\n          }\n        }\n      }\n\n      if (0 === conds.length) {\n        delete a.c\n      }\n      else if (1 === conds.length) {\n        a.c = conds[0]\n      }\n      else {\n        a.c = function conjunctCond(r: Rule, c: Context, a: AltMatch) {\n          for (let cond of conds) {\n            let pass = cond(r, c, a)\n            if (false == pass) {\n              return false\n            }\n          }\n          return true\n        }\n      }\n    }\n    else {\n      throw new Error('Grammar: invalid condition: ' + a.c)\n    }\n  }\n\n  return a as NormAltSpec\n}\n\n\nfunction isfnref(v: any) {\n  return 'string' === typeof v && v.startsWith('@')\n}\n\n\nfunction resolveFunctionRef(\n  fkind: string,\n  rs: RuleState,\n  r: RuleSpec,\n  a: AltSpec,\n  k: keyof AltSpec\n) {\n  const val = a[k]\n\n  if (isfnref(val)) {\n    const func = r.def.fnref[val as FuncRef] as Function\n    if (null == func) {\n      throw new Error(`Grammar: unknown ${fkind} function reference: ` + val +\n        ` for rule ${r.name} (${rs}) and alt ${a.s} (${a.g})`)\n    }\n    a[k] = func as any\n  }\n}\n\n\nconst COND_OPS: Record<string, number> = {\n  $eq: 1,\n  $ne: 1,\n  $lt: 1,\n  $lte: 1,\n  $gt: 1,\n  $gte: 1,\n}\n\n\nfunction makeRuleCond(co: string, prop: string, val: any) {\n  const path = prop.split('.')\n\n  if ('$eq' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return rval === val\n    }\n  }\n  else if ('$ne' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return rval != val\n    }\n  }\n  else if ('$lt' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return null == rval || rval < val\n    }\n  }\n  else if ('$lte' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return null == rval || rval <= val\n    }\n  }\n\n  else if ('$gt' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return null == rval || rval > val\n    }\n  }\n  else if ('$gte' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return null == rval || rval >= val\n    }\n  }\n  else if ('$exist' === co) {\n    return function ruleCond(r: Rule, _c: Context, _a: AltMatch) {\n      const rval = getpath(r, path)\n      return true === val ? null != rval : null == rval\n    }\n  }\n  else {\n    throw new Error('Grammer: unknown comparison operator: ' + co)\n  }\n}\n\n\n\nexport { makeRule, makeNoRule, makeRuleSpec }\n"
  },
  {
    "path": "src/self.ts",
    "content": "\nimport { Jsonic, Plugin } from './jsonic'\n\n\n//  See aontu lang.ts\n//\n//  plugin: aontu: {\n//\n//    rule: val: open: '& :': {\n//      push: map\n//      back: 2\n//      num: pk: 1\n//      g: aontu_spread \n//    } \n//    \n//    rule: val: close: '& :' {\n//      back: 2\n//      g: aontu_spread,json,more \n//    } \n//    \n//    rule: map: open: '#CJ #CL' {\n//      push: pair\n//      back: 2\n//    }\n//    \n//    rule: map: close: '#CJ #CL' {\n//      push: pair\n//      back: 2\n//    }\n//    \n//    rule: map: pair: '#CJ #CL' {\n//      push: val\n//      user: spread: true\n//    } \n//    \n//    rule: map: pair: '#KEY ?' {\n//      replace: pair\n//      user: aontu_optional: true\n//    } \n//    \n//    rule: map: pair: '? :' {\n//      push: val\n//      user: pair: true\n//      cond: { prev.user.aontu_optional: true }\n//      act: pair_optional\n//    } \n//\n//  }\n//\n//\n\n// cond:\n// [] - or\n// {} - and, keys select, vals match\n\n\nlet Self: Plugin = function self(jsonic: Jsonic) {\n\n\n\n}\n\n\nexport {\n  Self\n}\n"
  },
  {
    "path": "src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"module\": \"nodenext\",\n    \"noEmitOnError\": true,\n    \"outDir\":\"../dist\",\n    \"rootDir\":\".\",\n    \"resolveJsonModule\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"target\": \"es2021\",\n    \"declaration\": true,\n    \"declarationDir\": \"../dist\",\n    \"types\": [\"node\"]\n  }\n}"
  },
  {
    "path": "src/types.ts",
    "content": "/* Copyright (c) 2021-2022 Richard Rodger, MIT License */\n\n/*  types.ts\n *  Type and constant definitions.\n */\n\nexport const OPEN: RuleState = 'o'\nexport const CLOSE: RuleState = 'c'\nexport const BEFORE: RuleStep = 'b'\nexport const AFTER: RuleStep = 'a'\nexport const EMPTY = ''\nexport const INSPECT = Symbol.for('nodejs.util.inspect.custom')\n\n// Sentinel value that acts as `undefined` in deep merge — the base value\n// is preserved.  Represented as \"@SKIP\" in grammar options.\nexport const SKIP: unique symbol = Symbol.for('jsonic.SKIP')\n\n// Empty rule used as a no-value placeholder.\n// export const NONE = ({ name: 'none', state: OPEN } as Rule)\n\nexport const STRING = 'string'\n\n// The main top-level utility function.\nexport type JsonicParse = (src: any, meta?: any, parent_ctx?: any) => any\n\n// The core API is exposed as methods on the main utility function.\nexport interface JsonicAPI {\n  // Explicit parse method.\n  parse: JsonicParse\n\n  // Get and set partial option trees. Accepts a Bag object or a\n  // jsonic-format string that is parsed into a Bag before applying.\n  options: Options & ((change_options?: Bag | string) => Bag)\n\n  // Get the current configuration (derived from options).\n  config: () => Config\n\n  // Create a new Jsonic instance to customize.\n  make: (options?: Options | string) => Jsonic\n\n  // Use a plugin\n  use: (plugin: Plugin, plugin_options?: Bag) => Jsonic\n\n  // Get and set parser rules.\n  rule: (\n    name?: string,\n    define?: RuleDefiner | null,\n  ) => Jsonic | RuleSpec | RuleSpecMap\n\n  // Provide new lex matcher.\n  // lex: (matchmaker: MakeLexMatcher) => void\n\n  empty: (options?: Options) => Jsonic\n\n  // Token get and set for plugins. Reference by either name or Tin.\n  // NOTE: creates token if not yet defined (but only for name).\n  token: TokenMap &\n  TinMap &\n  (<A extends string | Tin>(ref: A) => A extends string ? Tin : string)\n\n  // TokenSet get and set for plugins. Reference by either name or Tin.\n  // NOTE: name->Tin[], but Tin->name (of containing set)\n  tokenSet: TokenSetMap &\n  TinSetMap &\n  (<A extends string | Tin>(ref: A) => A extends string ? Tin[] : string)\n\n  // Fixed token src get and set for plugins. Reference by either src or Tin.\n  fixed: TokenMap &\n  TinMap &\n  (<A extends string | Tin>(\n    ref: A,\n  ) => undefined | (A extends string ? Tin : string))\n\n  // Unique identifier string for each Jsonic instance.\n  id: string\n\n  // Provide identifier for string conversion.\n  toString: () => string\n\n  // Subscribe to lexing and parsing events.\n  sub: (spec: { lex?: LexSub; rule?: RuleSub }) => Jsonic\n\n  util: Bag\n\n  grammar: (gs: GrammarSpec | string, setting?: GrammarSetting) => void\n\n  // Convert a BNF grammar string into a jsonic GrammarSpec and install\n  // it on this instance. Returns the generated spec. See src/bnf.ts.\n  // `bnf.toSpec(src, opts)` returns the spec without installing.\n  bnf: ((src: string, opts?: BnfConvertOptions) => GrammarSpec) & {\n    toSpec: (src: string, opts?: BnfConvertOptions) => GrammarSpec\n  }\n}\n\n// BNF converter options. Re-declared here rather than imported to keep\n// types.ts free of circular cross-file references.\nexport type BnfConvertOptions = {\n  start?: string\n  tag?: string\n}\n\n\n// Additional settings applied when processing a grammar spec.\n// If `rule.alt.g` is defined, its value(s) are appended to every\n// rule-alt `g` property in the grammar before the alts are installed.\nexport type GrammarSetting = {\n  rule?: {\n    alt?: {\n      g?: string | string[]\n    }\n  }\n}\n\n\n// The full library type.\nexport type Jsonic = JsonicParse & // A function that parses.\n  JsonicAPI & { [prop: string]: any } // A utility with API methods. // Extensible by plugin decoration.\n\n// Define a plugin to extend the provided Jsonic instance.\nexport type Plugin = ((\n  jsonic: Jsonic,\n  plugin_options?: any,\n) => void | Jsonic) & {\n  defaults?: Bag\n  options?: Bag // TODO: InstalledPlugin.options is always defined ?\n}\n\n// Parsing options. See defaults.ts for commentary.\nexport type Options = {\n  safe?: {\n    key: boolean\n  }\n  tag?: string\n  fixed?: {\n    lex?: boolean\n    token?: StrMap\n    check?: LexCheck\n  }\n  match?: {\n    lex?: boolean\n    token?: { [name: string]: RegExp | LexMatcher }\n    value?: {\n      [name: string]: {\n        match: RegExp | LexMatcher\n        val?: any\n      }\n    }\n    check?: LexCheck\n  }\n  tokenSet?: {\n    [name: string]: string[]\n  }\n  space?: {\n    lex?: boolean\n    chars?: string\n    check?: LexCheck\n  }\n  line?: {\n    lex?: boolean\n    chars?: string\n    rowChars?: string\n    single?: boolean\n    check?: LexCheck\n  }\n  text?: {\n    lex?: boolean\n    modify?: ValModifier | ValModifier[]\n    check?: LexCheck\n  }\n  number?: {\n    lex?: boolean\n    hex?: boolean\n    oct?: boolean\n    bin?: boolean\n    sep?: string | null\n    exclude?: RegExp\n    check?: LexCheck\n  }\n  comment?: {\n    lex?: boolean\n    def?: {\n      [name: string]:\n      | {\n        line?: boolean\n        start?: string\n        end?: string\n        lex?: boolean\n        suffix?: string | string[] | LexMatcher\n        eatline: boolean\n      }\n      | null\n      | undefined\n      | false\n    }\n    check?: LexCheck\n  }\n  string?: {\n    lex?: boolean\n    chars?: string\n    multiChars?: string\n    escapeChar?: string\n    escape?: {\n      [char: string]: string | null\n    }\n    allowUnknown?: boolean\n    replace?: { [char: string]: string | null }\n    abandon?: boolean\n    check?: LexCheck\n  }\n  map?: {\n    extend?: boolean\n    merge?: (prev: any, curr: any) => any\n    child?: boolean\n  }\n  list?: {\n    property: boolean\n    pair: boolean\n    child: boolean\n  }\n  info?: {\n    map?: boolean\n    list?: boolean\n    text?: boolean\n    marker?: string\n  }\n  value?: {\n    lex?: boolean\n    def?: {\n      [src: string]:\n      | undefined\n      | null\n      | false\n      | {\n        val: any\n\n        // RegExp values will always have lower priority than pure tokens\n        // as they are matched by the TextMatcher. For higher priority\n        // use the `match` option.\n        match?: RegExp\n        consume?: boolean\n      }\n    }\n  }\n  ender?: string | string[]\n  plugin?: Bag\n  debug?: {\n    get_console?: () => any\n    maxlen?: number\n    print?: {\n      config?: boolean\n      src?: (x: any) => string\n    }\n  }\n  error?: { [code: string]: string }\n  errmsg?: {\n    name?: string\n    suffix?: boolean | string | ((color?: any, spec?: any) => string)\n  }\n  hint?: any\n  lex?: {\n    empty?: boolean\n    emptyResult?: any\n    match: {\n      [name: string]: {\n        order: number\n        make: MakeLexMatcher\n      }\n    }\n  }\n  parse?: {\n    prepare?: { [name: string]: ParsePrepare }\n  }\n  rule?: {\n    start?: string\n    finish?: boolean\n    maxmul?: number\n    include?: string\n    exclude?: string\n  }\n  result?: {\n    fail: any[]\n  }\n  rewind?: {\n    // Maximum number of consumed tokens retained in ctx.v for\n    // ctx.rewind(). Defaults to Infinity (unbounded). Set a finite\n    // value to cap parse-time memory; ctx.rewind(mark) will throw\n    // if the mark has been evicted from the retained window.\n    history?: number\n  }\n  config?: {\n    modify?: {\n      [plugin_name: string]: (config: Config, options: Options) => void\n    }\n  }\n  parser?: {\n    start?: (\n      lexer: any,\n      src: string,\n      jsonic: any,\n      meta?: any,\n      parent_ctx?: any,\n    ) => any\n  }\n  standard$?: boolean\n  defaults$?: boolean\n  grammar$?: boolean\n\n  color?: {\n    active?: boolean\n    reset?: string\n    hi?: string\n    lo?: string\n    line?: string\n  }\n\n}\n\n// Parsing rule specification. The rule OPEN and CLOSE state token\n// match alternates, and the associated actions, can be defined with a\n// chainable API.\nexport interface RuleSpec {\n  name: string\n  def: {\n    open: AltSpec[]\n    close: AltSpec[]\n    bo: StateAction[]\n    bc: StateAction[]\n    ao: StateAction[]\n    ac: StateAction[]\n    tcol: Tin[][][]\n    fnref: FuncRefMap<AltNext | AltBack | AltCond | AltModifier | AltError>\n  }\n  ji: Jsonic\n\n  tin<R extends string | Tin, T extends R extends Tin ? string : Tin>(ref: R): T\n\n  fnref(frm: Record<FuncRef, Function>): RuleSpec\n  add(state: RuleState, a: AltSpec | AltSpec[], flags: any): RuleSpec\n  open(a: AltSpec | AltSpecish[], flags?: any): RuleSpec\n  close(a: AltSpec | AltSpecish[], flags?: any): RuleSpec\n  action(\n    prepend: boolean,\n    step: RuleStep,\n    state: RuleState,\n    action: StateAction,\n  ): RuleSpec\n  bo(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec\n  ao(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec\n  bc(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec\n  ac(first: StateAction | boolean | FuncRef, second?: StateAction): RuleSpec\n  clear(): RuleSpec\n  norm(): RuleSpec\n\n  process(rule: Rule, ctx: Context, lex: Lex, state: RuleState): Rule\n\n  bad(tkn: Token, rule: Rule, ctx: Context, parse: { is_open: boolean }): Rule\n}\n\n// Represents the application of a parsing rule. An instance is created\n// for each attempt to match tokens based on the RuleSpec, and pushed\n// onto the main parser rule stack. A Rule can be in two states:\n// \"open\" when first placed on the stack, and \"close\" when it needs to be\n// removed from the stack.\nexport interface Rule {\n  i: number // Rule index (unique to parse).\n  name: string // Rule name.\n  spec: RuleSpec // RuleSpec for this rule.\n  node: any // The parsed value, if any.\n  state: RuleState // Open (`o`) or Close (`c`).\n  child: Rule // The current child rule, created with the `p` command.\n  parent: Rule // The parent rule, that pushed this rule onto the stack.\n  prev: Rule // The previous sibling rule, that issued an `r` command.\n  next: Rule\n\n  // Open state tokens (matched against alt.s during OPEN).\n  o: Token[] // Array of matched tokens; o[i] is NOTOKEN if not matched.\n  oN: number // Number of open state tokens consumed (# 'opens').\n\n  // Close state tokens (matched against alt.s during CLOSE).\n  c: Token[] // Array of matched tokens; c[i] is NOTOKEN if not matched.\n  cN: number // Number of close state tokens consumed (# 'closes').\n\n  // Legacy aliases for the first two slots of o/c and the counters.\n  // @deprecated Use o[0], o[1], oN (and c[0], c[1], cN) instead.\n  os: number // Alias of oN.\n  o0: Token // Alias of o[0].\n  o1: Token // Alias of o[1].\n  cs: number // Alias of cN.\n  c0: Token // Alias of c[0].\n  c1: Token // Alias of c[1].\n\n  n: Counters // Named counter values.\n  d: number // The current stack depth.\n  u: Bag // Custom key-value store, this rule only.\n  k: Bag // Custom key-value store, propagates via push and replace (keep!).\n  bo: boolean // Flag: call bo (before-open).\n  ao: boolean // Flag: call ao (after-open).\n  bc: boolean // Flag: call bc (before-close).\n  ac: boolean // Flag: call ac (after-close).\n  why?: string // Internal tracing.\n\n  // Lex tokens needed\n  need: number\n\n  // Process the \"open\" or \"close\" state of the Rule, returning the\n  // next rule to process.\n  process(ctx: Context, lex: Lex): Rule\n\n  // Always false if counter is null or undefined; default limit is 0.\n  eq(counter: string, limit?: number): boolean\n  lt(counter: string, limit?: number): boolean\n  gt(counter: string, limit?: number): boolean\n  lte(counter: string, limit?: number): boolean\n  gte(counter: string, limit?: number): boolean\n}\n\n// The current parse state and associated context.\nexport type Context = {\n  uI: number // Rule index.\n  opts: Options // Jsonic instance options.\n  cfg: Config // Jsonic instance config.\n  meta: Bag // Parse meta parameters.\n  src: () => string // source text to parse.\n  root: () => any // Root node.\n  plgn: () => Plugin[] // Jsonic instance plugins.\n  inst: () => Jsonic // Current Jsonic instance.\n  rule: Rule // Current rule instance.\n  sub: {\n    lex?: LexSub[]\n    rule?: RuleSub[]\n  }\n  xs: Tin // Lex state tin.\n  v: Token[] // Stack of consumed tokens (newest at top), used for rewind.\n  vAbs: number // Absolute count of pushed-not-rewound tokens; mark/rewind key.\n  v2: Token // Previous previous token (alias of v[v.length - 2]).\n  v1: Token // Previous token (alias of v[v.length - 1]).\n  t: Token[] // Lookahead buffer; t[i] is NOTOKEN if unfetched.\n\n  // Save a rewind mark at the current parse position. The returned\n  // value can be passed to `rewind` to replay the tokens consumed\n  // since the mark was taken, re-feeding them through the lexer's\n  // pending-token queue.\n  mark: () => number\n  rewind: (mark: number) => void\n\n  // Legacy aliases for the first two slots of the lookahead buffer.\n  // @deprecated Use t[0] and t[1] instead.\n  t0: Token // Alias of t[0] (current token).\n  t1: Token // Alias of t[1] (next token).\n\n  tC: number // Token count.\n  kI: number // Parser rule iteration count.\n  rs: Rule[] // Rule stack.\n  rsI: number\n  rsm: { [name: string]: RuleSpec } // RuleSpec lookup map (by rule name).\n  // next: (r: Rule) => Token // Move to next token.\n  log?: (...rest: any) => void // Log parse/lex step (if defined).\n  F: (s: any) => string // Format arbitrary data as length-limited string.\n  u: Bag // Custom meta data (for use by plugins)\n  NOTOKEN: Token // Per parse \"null\" Token\n  NORULE: Rule // Per parse \"null\" Rule\n}\n\nexport interface Lex {\n  src: String\n  ctx: Context\n  cfg: Config\n  pnt: Point\n  fwd: string\n  refwd(): string\n\n  token(\n    ref: Tin | string,\n    val: any,\n    src: string,\n    pnt?: Point,\n    use?: any,\n    why?: string,\n  ): Token\n\n  next(rule: Rule, alt?: NormAltSpec, altI?: number, tI?: number): Token\n\n  tokenize<R extends string | Tin, T extends R extends Tin ? string : Tin>(\n    ref: R,\n  ): T\n\n  bad(why: string, pstart: number, pend: number): Token\n}\n\nexport type NextToken = (rule: Rule) => Token\n\n// Internal clean configuration built from options by\n// `utility.configure` and LexMatchers.\nexport type Config = {\n  safe: {\n    key: boolean\n  }\n  lex: {\n    match: LexMatcher[]\n    empty: boolean\n    emptyResult: any\n  }\n\n  parse: {\n    prepare: ParsePrepare[]\n  }\n\n  rule: {\n    start: string\n    maxmul: number\n    finish: boolean\n    include: string[]\n    exclude: string[]\n  }\n\n  // Fixed tokens (punctuation, operators, keywords, etc.)\n  fixed: {\n    lex: boolean\n    token: TokenMap\n    ref: Record<string | Tin, Tin | string>\n    check?: LexCheck\n  }\n\n  // Matched tokens and values (regexp, custom function)\n  match: {\n    lex: boolean\n    // Values have priority.\n    value: {\n      [name: string]: {\n        // NOTE: RegExp must begin with `^`.\n        match: RegExp | LexMatcher\n        val?: any\n      }\n    }\n    token: MatchMap\n    check?: LexCheck\n  }\n\n  // Token set derived config.\n  tokenSet: TokenSetMap\n\n  // Token set derived config.\n  tokenSetTins: {\n    [name: string]: { [tin: number]: boolean }\n  }\n\n  // Space characters.\n  space: {\n    lex: boolean\n    chars: Chars\n    check?: LexCheck\n  }\n\n  // Line end characters.\n  line: {\n    lex: boolean\n    chars: Chars\n    rowChars: Chars // Row counting characters.\n    single: boolean\n    check?: LexCheck\n  }\n\n  // Unquoted text\n  text: {\n    lex: boolean\n    modify: ValModifier[]\n    check?: LexCheck\n  }\n\n  // Numbers\n  number: {\n    lex: boolean\n    hex: boolean\n    oct: boolean\n    bin: boolean\n    sep: boolean\n    exclude?: RegExp\n    sepChar?: string | null\n    check?: LexCheck\n  }\n\n  // String quote characters.\n  string: {\n    lex: boolean\n    quoteMap: Chars\n    escMap: Bag\n    escChar?: string\n    escCharCode?: number\n    multiChars: Chars\n    allowUnknown: boolean\n    replaceCodeMap: { [charCode: number]: string }\n    hasReplace: boolean\n    abandon: boolean\n    check?: LexCheck\n  }\n\n  // Literal values\n  value: {\n    lex: boolean\n\n    // Fixed values\n    def: {\n      [src: string]: {\n        val: any\n      }\n    }\n\n    // Regexp processed values, pre-sorted by name at configure time so\n    // iteration during lexing is deterministic across runtimes.\n    defre: {\n      name: string\n      val: (res: any) => any\n      match: RegExp\n      consume: boolean\n    }[]\n  }\n\n  // Comment markers\n  comment: {\n    lex: boolean\n    def: {\n      [name: string]: {\n        name: string\n        line: boolean\n        start: string\n        end?: string\n        lex: boolean\n        eatline: boolean\n        // Normalized suffix terminators. Matches are consumed as the\n        // final part of the comment body.  Sorted longest-first.\n        suffixes?: string[]\n        // Optional function-form suffix probe. A non-nil return signals\n        // termination; len(token.src) characters are consumed.\n        suffixFn?: LexMatcher\n      }\n    }\n    check?: LexCheck\n  }\n\n  map: {\n    extend: boolean\n    merge?: (prev: any, curr: any, rule: Rule, ctx: Context) => any\n    child: boolean\n  }\n\n  list: {\n    property: boolean\n    pair: boolean\n    child: boolean\n  }\n\n  info: {\n    map: boolean\n    list: boolean\n    text: boolean\n    marker: string\n  }\n\n  debug: {\n    get_console: () => any\n    maxlen: number\n    print: {\n      config: boolean\n      src?: (x: any) => string\n    }\n  }\n\n  result: {\n    fail: any[]\n  }\n\n  rewind: {\n    history: number\n  }\n\n  error: { [code: string]: string }\n  errmsg: {\n    name: string\n    suffix: boolean | string | ((color?: any, spec?: any) => string)\n  }\n\n  hint: any\n\n  rePart: any\n  re: any\n\n  tI: number // Token identifier index.\n  t: Record<string, Tin> // Token index map.\n\n  color: {\n    active: boolean\n    reset: string\n    hi: string\n    lo: string\n    line: string\n  }\n}\n\n// Current character position in source - the \"point'.\nexport interface Point {\n  len: number // Length of source (for convenience).\n  sI: number // Source position (0-based).\n  rI: number // Source row (1-based as editors do that).\n  cI: number // Source column (1-based as editors do that).\n\n  // A LexMatcher might match more than one token in sequence.\n  // This array is first drained before matching is attempted again.\n  token: Token[]\n\n  // Once the end of the source is reached, generate a single \"end\"\n  // token, and keep returning it as the next Token (see Lex.next).\n  end?: Token\n}\n\n// Parser token (and where it was found).  Tokens are also the bearers\n// of parser errors, as they capture the position of the error in the\n// source.\nexport interface Token {\n  isToken: boolean // Marks object as token.\n  name: string // Token name.\n  tin: Tin // Token identification number.\n  val: any // Value of Token if literal (eg. number).\n  src: string // Source text of Token.\n  sI: number // Location of token index in source text (0-based).\n  rI: number // Row location of token in source text (1-based).\n  cI: number // Column location of token in source text (1-based).\n  len: number // Length of Token source text.\n  use?: Bag // Custom meta data from plugins goes here.\n  err?: string // Error code.\n  why?: string // Internal tracing.\n  ignored?: Token\n\n  // Convert into an error Token.\n  bad(err: string, details?: any): Token\n\n  resolveVal(rule: Rule, ctx: Context): any\n}\n\n// Specification for a parse-alternate within a Rule state.\n// Represent a possible token match (2-token lookahead)\nexport interface AltSpec {\n  // Token Tin sequence to match (0,1,2 Tins, or a subset of Tins; nulls filterd out).\n  s?: (Tin | Tin[] | null | undefined | string)[] | null | string\n\n  // Push named Rule onto stack (create child).\n  p?: string | AltNext | null | false | FuncRef\n\n  // Replace current rule with named Rule on stack (create sibling).\n  r?: string | AltNext | null | false | FuncRef\n\n  // Move token pointer back by indicated number of steps.\n  b?: number | AltBack | null | false | FuncRef\n\n  // Condition function, return true to match alternate.\n  // NOTE: Token sequence (s) must also match.\n  c?: AltCond | null\n\n  n?: Counters // Increment counters by specified amounts.\n  a?: AltAction | FuncRef | null // Perform an action if this alternate matches.\n  h?: AltModifier | null // Modify current Alt to customize parser.\n  u?: Bag // Key-value custom data.\n  k?: Bag // Key-value custom data (propagated).\n\n  g?:\n  | string // Named group tags for the alternate (allows filtering).\n  | string[] // - comma separated or string array\n\n  e?: AltError | FuncRef | null// Generate an error token (alternate is not allowed).\n}\n\n// Allow AltSpecs to be \"empty\" and thus ignored.\ntype AltSpecish = AltSpec | undefined | null | false | 0 | typeof NaN\n\n// List modifications\nexport type ListMods = {\n  append?: boolean // if `true` apppend new entries, otherwise prepend.\n  move?: number[] // [from,to,  from,to,  ...]\n  delete?: number[] // [index0, index1, ...]\n  custom?: (alts: AltSpec[]) => null | AltSpec[]\n}\n\n// Parse-alternate match (built from current tokens and AltSpec).\nexport interface AltMatch {\n  p?: string | null | false | 0 // Push rule (by name).\n  r?: string | null | false | 0 // Replace rule (by name).\n  b?: number | null | false // Move token position backward.\n  c?: AltCond // Custom alt match condition.\n  n?: Counters // increment named counters.\n  a?: AltAction // Match actions.\n  h?: AltModifier // Modify alternate match.\n  u?: Bag // Custom props to add to Rule.use.\n  k?: Bag // Custom props to add to Rule.keep and keep via push and replace.\n  g?: string[] // Named group tags (allows plugins to find alts).\n  e?: Token // Errored on this token.\n}\n\n// General container of named items.\nexport type Bag = { [key: string]: any }\n\nexport type FuncRef = `@${string}`\n\n// Named function references.\nexport type FuncRefMap<FT> = Record<FuncRef, FT>\n\n// A set of named counters.\nexport type Counters = { [key: string]: number }\n\n// Unique token identification number (aka \"tin\").\nexport type Tin = number\n\n// Map token name ('#' prefix removed) to Token index (Tin).\nexport type TokenMap = { [name: string]: Tin }\n\n// Map token name ('#' prefix removed) to Token index (Tin) set.\nexport type TokenSetMap = { [name: string]: Tin[] }\n\n// Map Token index (Tin) to token name ('#' prefix removed).\nexport type TinMap = { [ref: number]: string }\n\n// Map Token index (Tin) to token set name ('#' prefix removed).\nexport type TinSetMap = { [ref: number]: string }\n\n// Map token name to matcher.\nexport type MatchMap = { [name: string]: RegExp | LexMatcher }\n\n// Map character to code value.\nexport type Chars = { [char: string]: number }\n\n// Map string to string value.\nexport type StrMap = { [name: string]: string }\n\n// After rule stack push, Rules are in state OPEN ('o'),\n// after first process, awaiting pop, Rules are in state CLOSE ('c').\nexport type RuleState = 'o' | 'c'\n\n// When executing a Rule state (attempting a match), an action can be\n// executed BEFORE ('b') or AFTER ('a') the match.\nexport type RuleStep = 'b' | 'a'\n\n// A lexing function that attempts to match tokens.\nexport type LexMatcher = (\n  lex: Lex,\n  rule: Rule,\n  tI?: number,\n) => Token | undefined\n\n// Construct a lexing function based on configuration.\nexport type MakeLexMatcher = (\n  cfg: Config,\n  opts: Options,\n) => LexMatcher | null | undefined | false\n\nexport type LexCheck = (\n  lex: Lex,\n) => void | undefined | { done: boolean; token: Token | undefined }\n\nexport type ParsePrepare = (jsonic: Jsonic, ctx: Context, meta?: any) => void\n\nexport type RuleSpecMap = { [name: string]: RuleSpec }\n\nexport type RuleDefiner = (rs: RuleSpec, p: Parser) => void | RuleSpec\n\n// Normalized parse-alternate.\nexport interface NormAltSpec extends AltSpec {\n  s: (Tin | Tin[] | null | undefined)[]\n  p: string | AltNext | null | false\n  r: string | AltNext | null | false\n  b: number | AltBack | null | false\n  // Per-position bit-field match tables. S[i] is the bit-packed Tin set\n  // for the i-th token in `s` (null if position matches any token).\n  S: (number[] | null)[] | null\n  // Per-position resolved Tin arrays (used for tcol collation).\n  t: Tin[][]\n  // Cached length of `s` (number of lookahead positions this alt requires).\n  sN: number\n  c: NormAltCond | null | undefined // Convenience definition reduce to function for processing.\n  g: string[] // Named group tags\n  a: AltAction | null | undefined // Generate an error token (alternate is not allowed).\n  e: AltError | null | undefined // Generate an error token (alternate is not allowed).\n}\n\n// Conditionally pass an alternate.\nexport type AltCond =\n  ((rule: Rule, ctx: Context, alt: AltMatch) => boolean)\n  | Record<string, any>\n\nexport type NormAltCond =\n  ((rule: Rule, ctx: Context, alt: AltMatch) => boolean)\n\n\n// Arbitrarily modify an alternate to customize parser.\nexport type AltModifier = (\n  rule: Rule,\n  ctx: Context,\n  alt: AltMatch,\n  next: Rule,\n) => AltMatch\n\n// Execute an action when alternate matches.\nexport type AltAction = (rule: Rule, ctx: Context, alt: AltMatch) => any\n\n// Determine next rule name (for AltSpec r or p properties).\nexport type AltNext = (\n  rule: Rule,\n  ctx: Context,\n  alt: AltMatch,\n) => string | null | false | 0\n\n// Determine token push back.\nexport type AltBack = (\n  rule: Rule,\n  ctx: Context,\n  alt: AltMatch,\n) => number | null | false\n\n// Execute an action for a given Rule state and step:\n// bo: BEFORE OPEN, ao: AFTER OPEN, bc: BEFORE CLOSE, ac: AFTER CLOSE.\nexport type StateAction = (\n  rule: Rule,\n  ctx: Context,\n  next: Rule,\n  out?: Token | void, // TODO: why void?\n) => Token | void\n\n// Generate an error token (with an appropriate code).\n// NOTE: errors are specified using tokens in order to capture file row and col.\nexport type AltError = (\n  rule: Rule,\n  ctx: Context,\n  alt: AltMatch,\n) => Token | undefined\n\nexport type ValModifier = (\n  val: any,\n  lex: Lex,\n  cfg: Config,\n  opts: Options,\n) => string\n\nexport type LexSub = (tkn: Token, rule: Rule, ctx: Context) => void\nexport type RuleSub = (rule: Rule, ctx: Context) => void\n\nexport interface Parser {\n  options: Options\n  cfg: Config\n  rsm: RuleSpecMap\n\n  rule(\n    name?: string,\n    define?: RuleDefiner | null,\n  ): RuleSpec | RuleSpecMap | undefined\n\n  start(src: string, jsonic: any, meta?: any, parent_ctx?: any): any\n\n  clone(options: Options, config: Config, jsonic: Jsonic): Parser\n\n  norm(): void\n}\n\n\nexport type GrammarSpec = {\n  ref?: Record<FuncRef, Function>\n\n  // JSON-serializable options. Function-valued fields use FuncRef strings\n  // that are resolved against `ref` before being applied.\n  options?: Bag\n\n  rule?: Record<string, {\n    open?: GrammarAltSpec[] |\n    {\n      alts: GrammarAltSpec[],\n      inject: { append?: boolean, delete?: number[], move?: number[] }\n    }\n    close?: GrammarAltSpec[] |\n    {\n      alts: GrammarAltSpec[],\n      inject: { append?: boolean, delete?: number[], move?: number[] }\n    }\n\n  }>\n\n}\n\n\nexport type GrammarAltSpec = {\n  s?: string | string[],\n  b?: FuncRef | number,\n  p?: FuncRef | string,\n  r?: FuncRef | string,\n  a?: FuncRef,\n  e?: FuncRef,\n  h?: FuncRef,\n  c?: FuncRef | Record<string, any>,\n  n?: Record<string, number>,\n  u?: Record<string, any>\n  k?: Record<string, any>\n  g?: string | string[],\n}\n"
  },
  {
    "path": "src/utility.ts",
    "content": "/* Copyright (c) 2013-2024 Richard Rodger, MIT License */\n\n/*  utility.ts\n *  Utility functions and constants.\n */\n\nimport type {\n  AltSpec,\n  Chars,\n  Config,\n  Context,\n  Lex,\n  LexMatcher,\n  NormAltSpec,\n  Options,\n  Rule,\n  RuleSpec,\n  Tin,\n  Token,\n  ValModifier,\n  ListMods,\n} from './types'\n\nimport { EMPTY, SKIP, STRING } from './types'\n\nimport { makeToken, makePoint } from './lexer'\nimport { JsonicError } from './error'\n\n// Null-safe object and array utilities\n// TODO: should use proper types:\n// https://github.com/microsoft/TypeScript/tree/main/src/lib\nconst keys = (x: any) => (null == x ? [] : Object.keys(x))\nconst values = <T>(x: { [key: string]: T } | undefined | null): T[] =>\n  null == x ? ([] as T[]) : Object.values(x)\nconst entries = <T>(\n  x: { [key: string]: T } | undefined | null,\n): [string, T][] => (null == x ? ([] as [string, T][]) : Object.entries(x))\nconst assign = (x: any, ...r: any[]) => Object.assign(null == x ? {} : x, ...r)\nconst isarr = (x: any) => Array.isArray(x)\nconst defprop = Object.defineProperty\n\n\n// Map object properties using entries.\nconst omap = (o: any, f?: (e: any) => any) => {\n  return Object.entries(o || {}).reduce((o: any, e: any) => {\n    let me = f ? f(e) : e\n    if (undefined === me[0]) {\n      delete o[e[0]]\n    } else {\n      o[me[0]] = me[1]\n    }\n\n    // Additional pairs set additional keys.\n    let i = 2\n    while (undefined !== me[i]) {\n      o[me[i]] = me[i + 1]\n      i += 2\n    }\n\n    return o\n  }, {})\n}\n\n// TODO: remove!\n// A bit pedantic, but let's be strict about strings.\n// Also improves minification a little.\nconst S = {\n  indent: '. ',\n  logindent: '  ',\n  space: ' ',\n  gap: '  ',\n  Object: 'Object',\n  Array: 'Array',\n  object: 'object',\n  string: 'string',\n  function: 'function',\n  unexpected: 'unexpected',\n  map: 'map',\n  list: 'list',\n  elem: 'elem',\n  pair: 'pair',\n  val: 'val',\n  node: 'node',\n  no_re_flags: EMPTY,\n  unprintable: 'unprintable',\n  invalid_ascii: 'invalid_ascii',\n  invalid_unicode: 'invalid_unicode',\n  invalid_lex_state: 'invalid_lex_state',\n  unterminated_string: 'unterminated_string',\n  unterminated_comment: 'unterminated_comment',\n  lex: 'lex',\n  parse: 'parse',\n  error: 'error',\n  none: 'none',\n  imp_map: 'imp,map',\n  imp_list: 'imp,list',\n  imp_null: 'imp,null',\n  end: 'end',\n  open: 'open',\n  close: 'close',\n  rule: 'rule',\n  stack: 'stack',\n  nUll: 'null',\n  name: 'name',\n  make: 'make',\n  colon: ':',\n  step: 'step',\n}\n\n\n// Idempotent normalization of options.\n// See Config type for commentary.\nfunction configure(\n  jsonic: any,\n  incfg: Config | undefined,\n  opts: Options,\n): Config {\n  const cfg = incfg || ({} as Config)\n\n  cfg.t = cfg.t || {}\n  cfg.tI = cfg.tI || 1\n\n  const t = (tn: string) => tokenize(tn, cfg)\n\n  // Standard tokens. These names should not be changed.\n  if (false !== opts.standard$) {\n    t('#BD') // BAD\n    t('#ZZ') // END\n    t('#UK') // UNKNOWN\n    t('#AA') // ANY\n    t('#SP') // SPACE\n    t('#LN') // LINE\n    t('#CM') // COMMENT\n    t('#NR') // NUMBER\n    t('#ST') // STRING\n    t('#TX') // TEXT\n    t('#VL') // VALUE\n  }\n\n  cfg.safe = {\n    key: false === opts.safe?.key ? false : true,\n  }\n\n  cfg.fixed = {\n    lex: !!opts.fixed?.lex,\n    token: opts.fixed\n      ? omap(clean(opts.fixed.token), ([name, src]: [string, string]) => [\n        src,\n        tokenize(name, cfg),\n      ])\n      : {},\n    ref: undefined as any,\n    check: opts.fixed?.check,\n  }\n\n  cfg.fixed.ref = omap(cfg.fixed.token, ([tin, src]: [string, string]) => [\n    tin,\n    src,\n  ])\n  cfg.fixed.ref = Object.assign(\n    cfg.fixed.ref,\n    omap(cfg.fixed.ref, ([tin, src]: [string, string]) => [src, tin]),\n  )\n\n  cfg.match = {\n    lex: !!opts.match?.lex,\n    value: opts.match\n      ? omap(clean(opts.match.value), ([name, spec]: [string, any]) => [\n        name,\n        spec,\n      ])\n      : {},\n    token: opts.match\n      ? omap(\n        clean(opts.match.token),\n        ([name, matcher]: [string, RegExp | LexMatcher]) => [\n          tokenize(name, cfg),\n          matcher,\n        ],\n      )\n      : {},\n    check: opts.match?.check,\n  }\n\n  // Lookup tin directly from matcher\n  omap(cfg.match.token, ([tin, matcher]: [number, any]) => [\n    tin,\n    ((matcher.tin$ = +tin), matcher),\n  ])\n\n  // Convert tokenSet tokens names to tins\n  const tokenSet = opts.tokenSet\n    ? Object.keys(opts.tokenSet).reduce(\n      (a: any, n: string) => (\n        (a[n] = (opts.tokenSet as any)[n]\n          .filter((x: any) => null != x)\n          .map((n: string) => t(n))),\n        a\n      ),\n      {},\n    )\n    : {}\n\n  cfg.tokenSet = cfg.tokenSet || {}\n  entries(tokenSet).map((entry: any[]) => {\n    let name = entry[0]\n    let tinset = entry[1]\n\n    if (cfg.tokenSet[name]) {\n      cfg.tokenSet[name].length = 0\n      cfg.tokenSet[name].push(...tinset)\n    } else {\n      cfg.tokenSet[name] = tinset\n    }\n  })\n\n  // Lookup table for token tin in given tokenSet\n  cfg.tokenSetTins = entries(cfg.tokenSet).reduce(\n    (a: any, en: any[]) => (\n      (a[en[0]] = a[en[0]] || {}),\n      en[1].map((tin: number) => (a[en[0]][tin] = true)),\n      a\n    ),\n    {},\n  )\n\n  // The IGNORE tokenSet is special and should always exist, even if empty.\n  cfg.tokenSetTins.IGNORE = cfg.tokenSetTins.IGNORE || {}\n\n  cfg.space = {\n    lex: !!opts.space?.lex,\n    chars: charset(opts.space?.chars),\n    check: opts.space?.check,\n  }\n\n  cfg.line = {\n    lex: !!opts.line?.lex,\n    chars: charset(opts.line?.chars),\n    rowChars: charset(opts.line?.rowChars),\n    single: !!opts.line?.single,\n    check: opts.line?.check,\n  }\n\n  cfg.text = {\n    lex: !!opts.text?.lex,\n    modify: (cfg.text?.modify || [])\n      .concat(\n        (opts.text?.modify ? [opts.text.modify] : []).flat() as ValModifier[],\n      )\n      .filter((m) => null != m),\n    check: opts.text?.check,\n  }\n\n  cfg.number = {\n    lex: !!opts.number?.lex,\n    hex: !!opts.number?.hex,\n    oct: !!opts.number?.oct,\n    bin: !!opts.number?.bin,\n    sep: null != opts.number?.sep && '' !== opts.number.sep,\n    exclude: opts.number?.exclude,\n    sepChar: opts.number?.sep,\n    check: opts.number?.check,\n  }\n\n  // NOTE: these are not value ending tokens\n  cfg.value = {\n    lex: !!opts.value?.lex,\n    def: entries(opts.value?.def || {}).reduce(\n      (a: any, e: any[]) => (\n        null == e[1] || false === e[1] || e[1].match || (a[e[0]] = e[1]), a\n      ),\n      {} as any,\n    ),\n    // Pre-sort by name at configure time so iteration at lex time is\n    // deterministic across runtimes (does not depend on object key order).\n    defre: entries(opts.value?.def || {})\n      .filter(([, spec]: [string, any]) => spec && spec.match)\n      .map(([name, spec]: [string, any]) => ({\n        name,\n        val: spec.val,\n        match: spec.match,\n        consume: !!spec.consume,\n      }))\n      .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),\n\n    // TODO: just testing, move to a plugin for extended values\n    // 'undefined': { v: undefined },\n    // 'NaN': { v: NaN },\n    // 'Infinity': { v: Infinity },\n    // '+Infinity': { v: +Infinity },\n    // '-Infinity': { v: -Infinity },\n  }\n\n  cfg.rule = {\n    start: null == opts.rule?.start ? 'val' : opts.rule.start,\n    maxmul: null == opts.rule?.maxmul ? 3 : opts.rule.maxmul,\n    finish: !!opts.rule?.finish,\n    include: opts.rule?.include\n      ? opts.rule.include.split(/\\s*,+\\s*/).filter((g) => '' !== g)\n      : [],\n    exclude: opts.rule?.exclude\n      ? opts.rule.exclude.split(/\\s*,+\\s*/).filter((g) => '' !== g)\n      : [],\n  }\n\n  cfg.map = {\n    extend: !!opts.map?.extend,\n    merge: opts.map?.merge,\n    child: !!opts.map?.child,\n  }\n\n  cfg.list = {\n    property: !!opts.list?.property,\n    pair: !!opts.list?.pair,\n    child: !!opts.list?.child,\n  }\n\n  cfg.info = {\n    map: !!opts.info?.map,\n    list: !!opts.info?.list,\n    text: !!opts.info?.text,\n    marker: opts.info?.marker || '__info__',\n  }\n\n  let fixedSorted = Object.keys(cfg.fixed.token).sort(\n    (a: string, b: string) => b.length - a.length,\n  )\n\n  let fixedRE = fixedSorted.map((fixed) => escre(fixed)).join('|')\n\n  let commentStartRE = opts.comment?.lex\n    ? (opts.comment.def ? values(opts.comment.def) : [])\n      .filter((c) => c && c.lex)\n      .map((c: any) => escre(c.start))\n      .join('|')\n    : ''\n\n  // End-marker RE part\n  let enderRE = [\n    '([',\n    escre(\n      keys(\n        charset(\n          cfg.space.lex && cfg.space.chars,\n          cfg.line.lex && cfg.line.chars,\n        ),\n      ).join(''),\n    ),\n    ']',\n\n    ('string' === typeof opts.ender\n      ? opts.ender.split('')\n      : Array.isArray(opts.ender)\n        ? opts.ender\n        : []\n    )\n      .map((c: string) => '|' + escre(c))\n      .join(''),\n\n    '' === fixedRE ? '' : '|',\n    fixedRE,\n\n    '' === commentStartRE ? '' : '|',\n    commentStartRE,\n\n    '|$)', // EOF case\n  ]\n\n  cfg.rePart = {\n    fixed: fixedRE,\n    ender: enderRE,\n    commentStart: commentStartRE,\n  }\n\n  // TODO: friendlier names\n  cfg.re = {\n    ender: regexp(null, ...enderRE),\n\n    // TODO: prebuild these using a property on matcher?\n    rowChars: regexp(null, escre(opts.line?.rowChars)),\n\n    columns: regexp(null, '[' + escre(opts.line?.chars) + ']', '(.*)$'),\n  }\n\n  cfg.lex = {\n    empty: !!opts.lex?.empty,\n    emptyResult: opts.lex?.emptyResult,\n    match: opts.lex?.match\n      ? entries(opts.lex.match)\n        .reduce((list: any[], entry: any) => {\n          let name = entry[0]\n          let matchspec = entry[1]\n          if (matchspec) {\n            let matcher = matchspec.make(cfg, opts)\n            if (matcher) {\n              matcher.matcher = name\n              matcher.make = matchspec.make\n              matcher.order = matchspec.order\n            }\n            list.push(matcher)\n          }\n          return list\n        }, [])\n        .filter((m) => null != m && false !== m && -1 < +m.order)\n        .sort((a, b) => a.order - b.order)\n      : [],\n  }\n\n  cfg.parse = {\n    prepare: values(opts.parse?.prepare),\n  }\n\n  cfg.debug = {\n    get_console: opts.debug?.get_console || (() => console),\n    maxlen: null == opts.debug?.maxlen ? 99 : opts.debug.maxlen,\n    print: {\n      config: !!opts.debug?.print?.config,\n      src: opts.debug?.print?.src,\n    },\n  }\n\n  cfg.error = opts.error ?? {}\n  cfg.errmsg = (opts.errmsg ?? { suffix: true }) as any\n  cfg.hint = opts.hint ?? {}\n\n  // Apply any config modifiers (probably from plugins).\n  if (opts.config?.modify) {\n    keys(opts.config.modify).forEach((modifer: string) =>\n      (opts.config as any).modify[modifer](cfg, opts),\n    )\n  }\n\n  // Debug the config - useful for plugin authors.\n  if (cfg.debug.print.config) {\n    cfg.debug.get_console().dir(cfg, { depth: null })\n  }\n\n  cfg.result = {\n    fail: [],\n  }\n\n  if (opts.result) {\n    cfg.result.fail = [...opts.result.fail]\n  }\n\n  // Parse-time consumed-token history cap (for ctx.rewind).\n  cfg.rewind = {\n    history:\n      null == opts.rewind?.history ? Infinity : opts.rewind.history,\n  }\n\n  const optscolor = opts.color ?? {}\n  cfg.color = cfg.color ?? {}\n  cfg.color.active = optscolor.active ?? cfg.color.active ?? true\n  cfg.color.reset = optscolor.reset ?? cfg.color.reset ?? '\\x1b[0m'\n  cfg.color.hi = optscolor.hi ?? cfg.color.hi ?? '\\x1b[91m'\n  cfg.color.lo = optscolor.lo ?? cfg.color.lo ?? '\\x1b[2m'\n  cfg.color.line = optscolor.line ?? cfg.color.line ?? '\\x1b[34m'\n\n  assign(jsonic.options, opts)\n  assign(jsonic.token, cfg.t)\n  assign(jsonic.tokenSet, cfg.tokenSet)\n  assign(jsonic.fixed, cfg.fixed.ref)\n\n  return cfg\n}\n\n// Uniquely resolve or assign token by name (string) or identification number (Tin),\n// returning the associated Tin (for the name) or name (for the Tin).\nfunction tokenize<\n  R extends string | Tin,\n  T extends R extends Tin ? string : Tin,\n>(ref: R, cfg: Config, jsonic?: any): T {\n  let tokenmap: any = cfg.t\n  let token: string | Tin = tokenmap[ref]\n\n  if (null == token && STRING === typeof ref) {\n    token = cfg.tI++\n    tokenmap[token] = ref\n    tokenmap[ref] = token\n    tokenmap[(ref as string).substring(1)] = token\n\n    if (null != jsonic) {\n      assign(jsonic.token, cfg.t)\n    }\n  }\n\n  return token as T\n}\n\n// Find a tokenSet by name, or find the name of the TokenSet containing a given Tin.\nfunction findTokenSet<\n  R extends string | Tin,\n  T extends R extends Tin ? string : Tin,\n>(ref: R, cfg: Config): T {\n  let tokenSetMap: any = cfg.tokenSet\n  let found: string | Tin[] = (tokenSetMap[ref] ??\n    ('string' == typeof ref ? tokenSetMap[ref.replace(/#/g, '')] : undefined))\n  return found as T\n}\n\n// Mark a string for escaping by `util.regexp`.\nfunction mesc(s: string, _?: any) {\n  return (_ = new String(s)), (_.esc = true), _\n}\n\n// Construct a RegExp. Use mesc to mark string for escaping.\n// NOTE: flags first allows concatenated parts to be rest.\nfunction regexp(\n  flags: string | null,\n  ...parts: (string | (String & { esc?: boolean }))[]\n): RegExp {\n  return new RegExp(\n    parts\n      .map((p) =>\n        (p as any).esc\n          ? //p.replace(/[-\\\\|\\]{}()[^$+*?.!=]/g, '\\\\$&')\n          escre(p.toString())\n          : p,\n      )\n      .join(EMPTY),\n    null == flags ? '' : flags,\n  )\n}\n\nfunction escre(s: string | undefined) {\n  return null == s\n    ? ''\n    : s\n      .replace(/[-\\\\|\\]{}()[^$+*?.!=]/g, '\\\\$&')\n      .replace(/\\t/g, '\\\\t')\n      .replace(/\\r/g, '\\\\r')\n      .replace(/\\n/g, '\\\\n')\n}\n\n// Deep override for plain data. Mutates base object and array.\n// Array merge by `over` index, `over` wins non-matching types, except:\n// `undefined` always loses, `over` plain objects inject into functions,\n// and `over` functions always win. Over always copied.\nfunction deep(base?: any, ...rest: any): any {\n  let base_isf = S.function === typeof base\n  let base_iso = null != base && (S.object === typeof base || base_isf)\n  for (let over of rest) {\n    let over_isf = S.function === typeof over\n    let over_iso = null != over && (S.object === typeof over || over_isf)\n    let over_ctor\n    if (\n      base_iso &&\n      over_iso &&\n      !over_isf &&\n      Array.isArray(base) === Array.isArray(over)\n    ) {\n      for (let k in over) {\n        base[k] = deep(base[k], over[k])\n      }\n    } else {\n      base =\n        undefined === over || SKIP === over\n          ? base\n          : over_isf\n            ? over\n            : over_iso\n              ? S.function === typeof (over_ctor = over.constructor) &&\n                S.Object !== over_ctor.name &&\n                S.Array !== over_ctor.name\n                ? over\n                : deep(Array.isArray(over) ? [] : {}, over)\n              : over\n\n      base_isf = S.function === typeof base\n      base_iso = null != base && (S.object === typeof base || base_isf)\n    }\n  }\n  return base\n}\n\n\nfunction badlex(lex: Lex, BD: Tin, ctx: Context) {\n  let next = lex.next.bind(lex)\n\n  lex.next = (rule: Rule, alt: NormAltSpec, altI: number, tI: number) => {\n    let token = next(rule, alt, altI, tI)\n\n    if (BD === token.tin) {\n      let details: any = {}\n      if (null != token.use) {\n        details.use = token.use\n      }\n      throw new JsonicError(\n        token.why || S.unexpected,\n        details,\n        token,\n        rule,\n        ctx,\n      )\n    }\n\n    return token\n  }\n\n  return lex\n}\n\n// Special debug logging to console (use Jsonic('...', {log:N})).\n// log:N -> console.dir to depth N\n// log:-1 -> console.dir to depth 1, omitting objects (good summary!)\nfunction makelog(ctx: Context, meta: any) {\n  let trace = ctx.opts?.plugin?.debug?.trace\n\n  if (meta || trace) {\n    if ('number' === typeof meta?.log || trace) {\n      let exclude_objects = false\n      let logdepth = meta?.log\n      if (-1 === logdepth || trace) {\n        logdepth = 1\n        exclude_objects = true\n      }\n      ctx.log = (...rest: any) => {\n        if (exclude_objects) {\n          let logstr = rest\n            .filter((item: any) => S.object != typeof item)\n            .map((item: any) => (S.function == typeof item ? item.name : item))\n            .join(S.gap)\n          ctx.cfg.debug.get_console().log(logstr)\n        } else {\n          ctx.cfg.debug.get_console().dir(rest, { depth: logdepth })\n        }\n        return undefined\n      }\n    } else if ('function' === typeof meta.log) {\n      ctx.log = meta.log\n    }\n  }\n  return ctx.log\n}\n\nfunction srcfmt(config: Config): (s: any) => string {\n  return 'function' === typeof config.debug.print.src\n    ? config.debug.print.src\n    : (s: any) => {\n      let out =\n        null == s\n          ? EMPTY\n          : Array.isArray(s)\n            ? JSON.stringify(s).replace(\n              /]$/,\n              entries(s as any)\n                .filter((en: any) => isNaN(en[0]))\n                .map(\n                  (en, i) =>\n                    (0 === i ? ', ' : '') +\n                    en[0] +\n                    ': ' +\n                    JSON.stringify(en[1]),\n                ) + // Just one level of array props!\n              ']',\n            )\n            : JSON.stringify(s)\n      out =\n        out.substring(0, config.debug.maxlen) +\n        (config.debug.maxlen < out.length ? '...' : EMPTY)\n      return out\n    }\n}\n\nfunction str(o: any, len: number = 44) {\n  let s\n  try {\n    s = 'object' === typeof o ? JSON.stringify(o) : '' + o\n  } catch (e: any) {\n    s = '' + o\n  }\n  return snip(len < s.length ? s.substring(0, len - 3) + '...' : s, len)\n}\n\nfunction snip(s: any, len: number = 5) {\n  return undefined === s\n    ? ''\n    : ('' + s).substring(0, len).replace(/[\\r\\n\\t]/g, '.')\n}\n\nfunction clone(class_instance: any) {\n  return deep(\n    Object.create(Object.getPrototypeOf(class_instance)),\n    class_instance,\n  )\n}\n\n// Lookup map for a set of chars.\nfunction charset(...parts: (string | object | boolean | undefined)[]): Chars {\n  return null == parts\n    ? {}\n    : parts\n      .filter((p) => false !== p)\n      .map((p: any) => ('object' === typeof p ? keys(p).join(EMPTY) : p))\n      .join(EMPTY)\n      .split(EMPTY)\n      .reduce((a: any, c: string) => ((a[c] = c.charCodeAt(0)), a), {})\n}\n\n// Remove all properties with values null or undefined. Note: mutates argument.\nfunction clean<T>(o: T): T {\n  for (let p in o) {\n    if (null == o[p]) {\n      delete o[p]\n    }\n  }\n  return o\n}\n\n// TODO: rename to filterAlts\nfunction filterRules(rs: RuleSpec, cfg: Config) {\n  let rsnames: (keyof RuleSpec['def'])[] = ['open', 'close']\n  for (let rsn of rsnames) {\n    ; (rs.def[rsn] as AltSpec[]) = (rs.def[rsn] as AltSpec[])\n\n      // Convert comma separated rule group name list to string[].\n      .map(\n        (as: AltSpec) => (\n          (as.g =\n            'string' === typeof as.g\n              ? (as.g || '').split(/\\s*,+\\s*/)\n              : as.g || []),\n          as\n        ),\n      )\n\n      // Keep rule if any group name matches, or if there are no includes.\n      .filter((as: AltSpec) =>\n        cfg.rule.include.reduce(\n          (a: boolean, g) => a || (null != as.g && -1 !== as.g.indexOf(g)),\n          0 === cfg.rule.include.length,\n        ),\n      )\n\n      // Drop rule if any group name matches, unless there are no excludes.\n      .filter((as: AltSpec) =>\n        cfg.rule.exclude.reduce(\n          (a: boolean, g) => a && (null == as.g || -1 === as.g.indexOf(g)),\n          true,\n        ),\n      )\n  }\n\n  return rs\n}\n\nfunction prop(obj: any, path: string, val?: any): any {\n  let root = obj\n  try {\n    let parts = path.split('.')\n    let pn: any\n    for (let pI = 0; pI < parts.length; pI++) {\n      pn = parts[pI]\n      if ('__proto__' === pn) {\n        throw new Error(pn)\n      }\n      if (pI < parts.length - 1) {\n        obj = obj[pn] = obj[pn] || {}\n      }\n    }\n    if (undefined !== val) {\n      if ('__proto__' === pn) {\n        throw new Error(pn)\n      }\n      obj[pn] = val\n    }\n    return obj[pn]\n  } catch (e: any) {\n    throw new Error(\n      'Cannot ' +\n      (undefined === val ? 'get' : 'set') +\n      ' path ' +\n      path +\n      ' on object: ' +\n      str(root) +\n      (undefined === val ? '' : ' to value: ' + str(val, 22)),\n    )\n  }\n}\n\n// Mutates list based on ListMods.\nfunction modlist(list: any[], mods?: ListMods) {\n  if (mods && list) {\n    if (0 < list.length) {\n      // Delete before move so indexes still make sense, using null to preserve index.\n      if (mods.delete && 0 < mods.delete.length) {\n        for (let i = 0; i < mods.delete.length; i++) {\n          let mdI = mods.delete[i]\n          if (mdI < 0 ? -1 * mdI <= list.length : mdI < list.length) {\n            let dI = (list.length + mdI) % list.length\n            list[dI] = null\n          }\n        }\n      }\n\n      // Format: [from,to, from,to, ...]\n      if (mods.move) {\n        for (let i = 0; i < mods.move.length; i += 2) {\n          let fromI = (list.length + mods.move[i]) % list.length\n          let toI = (list.length + mods.move[i + 1]) % list.length\n          let entry = list[fromI]\n          list.splice(fromI, 1)\n          list.splice(toI, 0, entry)\n        }\n      }\n\n      // Filter out any deletes.\n      // return list.filter((a: AltSpec) => null != a)\n      let filtered = list.filter((entry) => null != entry)\n      if (filtered.length !== list.length) {\n        list.length = 0\n        list.push(...filtered)\n      }\n    }\n\n    // Custom modification of list.\n    if (mods.custom) {\n      let newlist = mods.custom(list)\n      if (null != newlist) {\n        list = newlist\n      }\n    }\n  }\n\n  return list\n}\n\nfunction parserwrap(parser: any) {\n  return {\n    start: function(\n      src: string,\n      // jsonic: Jsonic,\n      jsonic: any,\n      meta?: any,\n      parent_ctx?: any,\n    ) {\n      try {\n        return parser.start(src, jsonic, meta, parent_ctx)\n      } catch (ex: any) {\n        if ('SyntaxError' === ex.name) {\n          let loc = 0\n          let row = 0\n          let col = 0\n          let tsrc = EMPTY\n          let errloc = ex.message.match(\n            /^Unexpected token (.) .*position\\s+(\\d+)/i,\n          )\n          if (errloc) {\n            tsrc = errloc[1]\n            loc = parseInt(errloc[2])\n            row = src.substring(0, loc).replace(/[^\\n]/g, EMPTY).length\n            let cI = loc - 1\n            while (-1 < cI && '\\n' !== src.charAt(cI)) cI--\n            col = Math.max(src.substring(cI, loc).length, 0)\n          }\n\n          let token =\n            ex.token ||\n            makeToken(\n              '#UK',\n              tokenize('#UK', jsonic.internal().config),\n              undefined,\n              tsrc,\n              makePoint(\n                tsrc.length,\n                loc,\n                ex.lineNumber || row,\n                ex.columnNumber || col,\n              ),\n            )\n\n          throw new JsonicError(\n            ex.code || 'json',\n            ex.details || {\n              msg: ex.message,\n            },\n            token,\n            {} as Rule,\n\n            // TODO: this smells\n            ex.ctx ||\n            ({\n              uI: -1,\n              opts: jsonic.options,\n              cfg: jsonic.internal().config,\n              token: token,\n              meta,\n              src: () => src,\n              root: () => undefined,\n              plgn: () => jsonic.internal().plugins,\n              inst: () => jsonic,\n              rule: { name: 'no-rule' } as Rule,\n              sub: {},\n              xs: -1,\n              v2: token,\n              v1: token,\n              t: [token, token], // TODO: t[1] should be end token\n              tC: -1,\n              kI: -1,\n              rs: [],\n              rsI: 0,\n              rsm: {},\n              n: {},\n              log: meta ? meta.log : undefined,\n              F: srcfmt(jsonic.internal().config),\n              u: {},\n              NORULE: { name: 'no-rule' } as Rule,\n              NOTOKEN: { name: 'no-token' } as Token,\n            } as unknown as Context),\n          )\n        } else {\n          throw ex\n        }\n      }\n    },\n  }\n}\n\n\nfunction getpath(root: any, path: string | string[]): any {\n  path = 'string' === typeof path ? path.split('.') : path\n  let node = root\n  for (let i = 0; i < path.length && null != node; i++) {\n    node = node[path[i]]\n  }\n  return node\n}\n\n\n// Recursively resolve FuncRef strings in an options object to actual functions,\n// and `@/pattern/flags` strings to RegExp instances.\nfunction resolveFuncRefs(\n  obj: any,\n  ref?: Record<string, Function>,\n): any {\n  if (null == obj || 'object' !== typeof obj) {\n    if ('string' === typeof obj && '@' === obj[0]) {\n      // Escape: `@@` prefix produces a literal `@`-prefixed string.\n      if ('@' === obj[1]) {\n        return obj.substring(1)\n      }\n      // Sentinel: `@SKIP` resolves to the SKIP symbol (acts as undefined in deep merge).\n      if ('SKIP' === obj.substring(1)) {\n        return SKIP\n      }\n      // Match `@/pattern/flags` — a JSON-serializable RegExp literal.\n      const m = obj.match(/^@\\/(.*)\\/([\\w]*)$/)\n      if (m) {\n        return new RegExp(m[1], m[2])\n      }\n      if (ref) {\n        const fn = ref[obj]\n        if ('function' === typeof fn) {\n          return fn\n        }\n      }\n    }\n    return obj\n  }\n\n  if (Array.isArray(obj)) {\n    return obj.map((item: any) => resolveFuncRefs(item, ref))\n  }\n\n  // Preserve non-plain objects (RegExp, Date, etc.) without recursion.\n  const ctor = obj.constructor\n  if (ctor && 'Object' !== ctor.name) {\n    return obj\n  }\n\n  const out: any = {}\n  for (const key of Object.keys(obj)) {\n    out[key] = resolveFuncRefs(obj[key], ref)\n  }\n  return out\n}\n\n\nexport {\n  S,\n  assign,\n  badlex,\n  charset,\n  clean,\n  clone,\n  configure,\n  deep,\n  defprop,\n  entries,\n  escre,\n  filterRules,\n  getpath,\n  isarr,\n  makelog,\n  mesc,\n  regexp,\n  snip,\n  srcfmt,\n  tokenize,\n  parserwrap,\n  str,\n  omap,\n  keys,\n  values,\n  findTokenSet,\n  modlist,\n  resolveFuncRefs,\n}\n"
  },
  {
    "path": "test/aa-wildcard.test.js",
    "content": "/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Coverage for `#AA` as a true ANY-token wildcard in alt `s:` lists.\n//\n// Before the fix, an alt with `s: ['#AA']` only matched tokens whose\n// tin fit in partition 0 (tin < 31) because normalt sized the alt's\n// per-partition bitset from AA's own tin (4), leaving S[i][1..]\n// undefined. High-tin tokens silently failed the bitset AND.\n//\n// The fix: when `#AA` is in a position's tin list, normalt sets\n// `S[i] = null` — the existing \"no constraint\" sentinel — so the\n// matcher skips the bitset check entirely and any fetched token\n// matches. `t[i]` still carries the raw tin list so tcol collation\n// is unaffected.\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\n\nfunction make_norules(opts) {\n  let j = Jsonic.make(opts)\n  let rns = j.rule()\n  Object.keys(rns).map((rn) => j.rule(rn, null))\n  return j\n}\n\n\ndescribe('aa-wildcard', () => {\n\n  it('#AA matches a low-tin fixed token (partition 0)', () => {\n    const j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n    })\n\n    j.rule('top', (rs) => rs\n      .open([{ s: ['#AA'] }])\n      .close([{ s: '#ZZ' }])\n    )\n\n    assert.doesNotThrow(() => j('a'))\n  })\n\n\n  it('#AA matches a high-tin fixed token (partition 1+)', () => {\n    // Push the test token's tin above 31 by registering enough\n    // filler literals first. Without the fix, the alt's S[i] would\n    // only cover partition 0, and the lookup for tin >= 31 would\n    // hit undefined and fall through as a non-match.\n    const fillers = {}\n    for (let n = 0; n < 40; n++) fillers['F' + n] = 'f' + n\n    fillers['Tx'] = 'x'\n\n    const j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: fillers },\n    })\n    assert.ok(j.token('Tx') >= 31,\n      `test setup bug: Tx tin ${j.token('Tx')} expected >= 31`)\n\n    j.rule('top', (rs) => rs\n      .open([{ s: ['#AA'] }])\n      .close([{ s: '#ZZ' }])\n    )\n\n    assert.doesNotThrow(() => j('x'))\n  })\n\n\n  it('unrelated token bits no longer leak via bitAA', () => {\n    // Sibling regression: before the fix, bitAA was ORed into every\n    // partition's match mask, so any token whose tin sat at bit 3 of\n    // its partition (tin = 4 + 31·k for k >= 1, e.g. 35, 66, …)\n    // would falsely match alts that had some other token's bit 3\n    // flipped in the same partition. Confirm we now reject a token\n    // that should NOT match the alt.\n    const fillers = {}\n    // Fill up partition 0 and push tokens into partition 1.\n    for (let n = 0; n < 34; n++) fillers['F' + n] = 'f' + n\n    // Pa35 lands at tin=35 (bit 3 of partition 1) — the old bug\n    // vehicle. Pa44 lands at tin ~44 (a different bit in the same\n    // partition); these tins must stay distinct.\n    fillers['Pa35'] = 'p'\n    fillers['Pa44'] = 'q'\n\n    const j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: fillers },\n    })\n\n    j.rule('top', (rs) => rs\n      .open([{ s: ['Pa35'] }])  // only accept the first one\n      .close([{ s: '#ZZ' }])\n    )\n\n    assert.doesNotThrow(() => j('p'))\n    assert.throws(() => j('q'), /unexpected/,\n      'partition-1 tin at a different bit must not match via bitAA')\n  })\n\n})\n"
  },
  {
    "path": "test/alignment.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Alignment tests validate that TypeScript and Go produce identical results.\n// Shared TSV files in test/spec/alignment-*.tsv are run by both this TS runner\n// and the Go runner (go/alignment_test.go).\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, JsonicError } = require('..')\nconst { loadTSV } = require('./utility')\n\nconst j = Jsonic\nconst JS = (x) => JSON.stringify(x)\n\n// deepEqual compares values via JSON roundtrip to handle null-prototype objects.\nfunction deepEqual(actual, expected, msg) {\n  assert.deepStrictEqual(JSON.parse(JS(actual)), JSON.parse(JS(expected)), msg)\n}\n\n// --- Shared TSV test helpers ---\n\nfunction tsvTest(name, parser) {\n  parser = parser || Jsonic\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    const result = parser(input)\n    deepEqual(result, JSON.parse(expected),\n      `${name} row ${row}: input=${input} expected=${expected}`)\n  }\n}\n\nfunction tsvErrorTest(name, parser) {\n  parser = parser || Jsonic\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    if (!expected.startsWith('ERROR:')) {\n      throw new Error(`${name} row ${row}: expected column must start with ERROR:`)\n    }\n    const code = expected.slice(6)\n    assert.throws(() => parser(input), (err) => {\n      return err instanceof JsonicError && err.code === code\n    }, `${name} row ${row}: input=${input} expected=${expected}`)\n  }\n}\n\nfunction tsvNullTest(name) {\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    const result = Jsonic(input)\n    // TS returns undefined for empty/comment-only input, which is equivalent\n    // to Go's nil. The TSV says \"null\" but in TS this is actually undefined.\n    assert.ok(result === undefined || result === null,\n      `${name} row ${row}: input=${input} expected null/undefined, got ${JS(result)}`)\n  }\n}\n\ndescribe('alignment', function () {\n  // --- Shared TSV tests ---\n\n  it('alignment-values', () => {\n    tsvTest('alignment-values')\n  })\n\n  it('alignment-safe-key', () => {\n    // TS uses Object.create(null) so __proto__ is a normal key on objects.\n    // safe.key only blocks __proto__ on arrays (which have real prototypes).\n    const entries = loadTSV('alignment-safe-key')\n    for (const { cols: [input, expected], row } of entries) {\n      const result = Jsonic(input)\n      const exp = JSON.parse(expected)\n      deepEqual(result, exp,\n        `alignment-safe-key row ${row}: input=${input} expected=${expected}`)\n    }\n  })\n\n  it('alignment-map-merge', () => {\n    tsvTest('alignment-map-merge')\n  })\n\n  it('alignment-number-text', () => {\n    tsvTest('alignment-number-text')\n  })\n\n  it('alignment-structure', () => {\n    tsvTest('alignment-structure')\n  })\n\n  it('alignment-empty', () => {\n    tsvNullTest('alignment-empty')\n  })\n\n  it('alignment-errors', () => {\n    tsvErrorTest('alignment-errors')\n  })\n\n  // --- Exclude group TSV tests ---\n\n  it('exclude-strict-json', () => {\n    const jj = Jsonic.make({ rule: { exclude: 'jsonic,imp' } })\n    tsvTest('exclude-strict-json', jj)\n  })\n\n  it('exclude-strict-json-errors', () => {\n    const jj = Jsonic.make({ rule: { exclude: 'jsonic,imp' } })\n    tsvErrorTest('exclude-strict-json-errors', jj)\n  })\n\n  it('exclude-comma', () => {\n    const jj = Jsonic.make({ rule: { exclude: 'comma' } })\n    tsvTest('exclude-comma', jj)\n  })\n\n  it('exclude-comma-errors', () => {\n    const jj = Jsonic.make({ rule: { exclude: 'comma' } })\n    tsvErrorTest('exclude-comma-errors', jj)\n  })\n\n  // --- Include group TSV tests (parity for rule.include) ---\n\n  it('include-json', () => {\n    // include=\"json\" keeps only json-tagged alts, producing a parser that\n    // behaves like strict-JSON. Shared TSV ensures TS and Go agree.\n    const jj = Jsonic.make({ rule: { include: 'json' } })\n    tsvTest('include-json', jj)\n  })\n\n  it('include-json-errors', () => {\n    const jj = Jsonic.make({ rule: { include: 'json' } })\n    tsvErrorTest('include-json-errors', jj)\n  })\n\n  // --- Comment suffix TSV tests (parity for comment.def.suffix) ---\n\n  it('feature-comment-suffix-line', () => {\n    // Line comment with a custom suffix terminator — suffix is consumed.\n    // NOTE: TS's makeCommentMatcher requires lex:true explicitly on every\n    // def (Go defaults it to true); setting it here keeps both runners\n    // configured identically.\n    const jj = Jsonic.make({\n      comment: {\n        def: {\n          hash: { line: true, start: '#', lex: true, suffix: '@@' },\n          line: { line: true, start: '//', lex: true },\n          block: { line: false, start: '/*', end: '*/', lex: true },\n        },\n      },\n    })\n    tsvTest('feature-comment-suffix-line', jj)\n  })\n\n  it('feature-comment-suffix-block', () => {\n    // Block comment with a custom suffix terminator — suffix is consumed\n    // and short-circuits the usual End-required behaviour.\n    const jj = Jsonic.make({\n      comment: {\n        def: {\n          hash: { line: true, start: '#', lex: true },\n          line: { line: true, start: '//', lex: true },\n          block: { line: false, start: '/*', end: '*/', lex: true, suffix: '!!' },\n        },\n      },\n    })\n    tsvTest('feature-comment-suffix-block', jj)\n  })\n\n  // --- Lex error propagation tests ---\n  // Verifies that lex-level errors (unterminated_string, unterminated_comment)\n  // are not masked by generic \"unexpected\" in any parser state.\n\n  it('lex-errors-default', () => {\n    tsvErrorTest('lex-errors')\n  })\n\n  it('lex-errors-exclude-jsonic-imp', () => {\n    const jj = Jsonic.make({ rule: { exclude: 'jsonic,imp' } })\n    tsvErrorTest('lex-errors', jj)\n  })\n\n  it('lex-errors-exclude-jsonic-imp-comma', () => {\n    const jj = Jsonic.make({ rule: { exclude: 'jsonic,imp,comma' } })\n    tsvErrorTest('lex-errors', jj)\n  })\n\n  // --- Direct TS tests for option-dependent features ---\n\n  it('map-extend-false', () => {\n    const ji = Jsonic.make({ map: { extend: false } })\n    deepEqual(ji('{a:{b:1},a:{c:2}}'), { a: { c: 2 } })\n  })\n\n  it('map-merge-func', () => {\n    const ji = Jsonic.make({\n      map: {\n        merge: (prev, val) => prev,\n      },\n    })\n    deepEqual(ji('{a:1,a:2}'), { a: 1 })\n  })\n\n  it('safe-key-objects', () => {\n    const result = Jsonic('{__proto__:1,a:2}')\n    assert.strictEqual(result.__proto__, 1)\n    assert.strictEqual(result.a, 2)\n  })\n\n  it('safe-key-arrays', () => {\n    const result = Jsonic('[1,2,__proto__:3]')\n    deepEqual(result, [1, 2])\n    assert.notStrictEqual(result.__proto__.toString, '3')\n  })\n\n  it('safe-key-false', () => {\n    const ji = Jsonic.make({ safe: { key: false } })\n    const result = ji('[1,2,__proto__:{toString:FAIL}]')\n    assert.ok(('' + result.toString).startsWith('FAIL'))\n  })\n\n  it('string-escape-errors', () => {\n    const ji = Jsonic.make({ string: { allowUnknown: false } })\n    assert.throws(() => ji('\"\\\\w\"'))\n  })\n\n  it('string-abandon', () => {\n    const ji = Jsonic.make({ string: { abandon: true } })\n    const result = ji('\"abc')\n    assert.ok(result !== undefined && result !== null)\n  })\n\n  it('string-replace', () => {\n    const ji = Jsonic.make({\n      string: { replace: { A: 'B', D: '' } },\n    })\n    assert.strictEqual(ji('\"aAc\"'), 'aBc')\n    assert.strictEqual(ji('\"aAcDe\"'), 'aBce')\n  })\n\n  it('number-exclude', () => {\n    const ji = Jsonic.make({\n      number: {\n        exclude: /^00/,\n      },\n    })\n    assert.strictEqual(ji('0099'), '0099')\n    assert.strictEqual(ji('99'), 99)\n  })\n\n  it('line-single', () => {\n    const ji = Jsonic.make({ line: { single: true } })\n    deepEqual(ji('a\\n\\nb'), ['a', 'b'])\n  })\n\n  it('comment-eatline', () => {\n    const ji = Jsonic.make({\n      comment: {\n        def: {\n          hash: { line: true, start: '#', eatline: true },\n          line: { line: true, start: '//' },\n          block: { line: false, start: '/*', end: '*/' },\n        },\n      },\n    })\n    deepEqual(ji('a:1#x\\nb:2'), { a: 1, b: 2 })\n  })\n\n  it('text-modify', () => {\n    const ji = Jsonic.make({\n      text: {\n        modify: [(val) => (typeof val === 'string' ? val.toUpperCase() : val)],\n      },\n    })\n    assert.strictEqual(ji('hello'), 'HELLO')\n    assert.strictEqual(ji('\"hello\"'), 'hello')\n  })\n\n  it('list-property-guard', () => {\n    const ji = Jsonic.make({ list: { property: false, pair: false } })\n    assert.throws(() => ji('[a:1]'))\n  })\n\n  it('exclude-jsonic', () => {\n    const ji = Jsonic.make()\n\n    let openBefore, closeBefore\n    ji.rule('val', (rs) => {\n      openBefore = rs.def.open.length\n      closeBefore = rs.def.close.length\n    })\n\n    ji.rule('val', (rs) => {\n      rs.def.open = rs.def.open.filter((a) => !a.g || !a.g.includes('jsonic'))\n      rs.def.close = rs.def.close.filter((a) => !a.g || !a.g.includes('jsonic'))\n      assert.ok(rs.def.open.length < openBefore)\n      assert.ok(rs.def.close.length < closeBefore)\n      for (const alt of rs.def.open) {\n        const g = typeof alt.g === 'string' ? alt.g : ''\n        assert.ok(!g.split(',').map(s => s.trim()).includes('jsonic'),\n          `val.open alt still has jsonic tag: ${alt.g}`)\n      }\n    })\n  })\n\n  it('result-fail', () => {\n    const ji = Jsonic.make({ result: { fail: ['FAIL'] } })\n    assert.throws(() => ji('FAIL'))\n    assert.strictEqual(ji('OK'), 'OK')\n  })\n\n  it('finish-rule-false', () => {\n    const ji = Jsonic.make({ rule: { finish: false } })\n    assert.throws(() => ji('{a:1'))\n  })\n\n  it('empty-disabled', () => {\n    const ji = Jsonic.make({ lex: { empty: false } })\n    assert.throws(() => ji(''))\n  })\n\n  it('custom-values', () => {\n    const ji = Jsonic.make({\n      value: {\n        def: {\n          true: { val: true },\n          false: { val: false },\n          null: { val: null },\n          NaN: { val: 'NaN-custom' },\n        },\n      },\n    })\n    assert.strictEqual(ji('NaN'), 'NaN-custom')\n    assert.strictEqual(ji('true'), true)\n  })\n\n  it('deep-undefined', () => {\n    const { deep } = Jsonic.util\n    const base = { a: 1, b: 2 }\n    const over = { a: undefined, b: 3 }\n    const result = deep(base, over)\n    assert.strictEqual(result.a, 1)\n    assert.strictEqual(result.b, 3)\n  })\n\n  it('error-propagation', () => {\n    assert.throws(() => j('}'), /unexpected/)\n    assert.throws(() => j(']'), /unexpected/)\n  })\n\n  it('trailing-content', () => {\n    assert.throws(() => j('a:1,2'), /unexpected/)\n  })\n\n  it('lex-subscriber', () => {\n    const ji = Jsonic.make()\n    const tokens = []\n    ji.sub({ lex: (tkn) => {\n      tokens.push(tkn.tin)\n    }})\n    ji('a:1')\n    assert.ok(tokens.length > 0)\n  })\n})\n"
  },
  {
    "path": "test/also-bad-plugin.js",
    "content": "module.exports = 'bad-plugin'\n"
  },
  {
    "path": "test/angle.js",
    "content": "module.exports = function angle(jsonic) {\n  jsonic.options({\n    fixed: {\n      token: {\n        '#OB': '<',\n        '#CB': '>',\n      },\n    },\n  })\n}\n"
  },
  {
    "path": "test/api.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\nconst { Debug } = require('../dist/debug')\n\ndescribe('api', function () {\n  it('standard', () => {\n    const { keys } = Jsonic.util\n\n    // Ensure no accidental API expansion\n    assert.deepEqual(keys(Jsonic), [\n      'empty',\n      'parse',\n      'sub',\n      'id',\n      'toString',\n      'Jsonic',\n      'JsonicError',\n      'makeLex',\n      'makeParser',\n      'makeToken',\n      'makePoint',\n      'makeRule',\n      'makeRuleSpec',\n      'makeFixedMatcher',\n      'makeSpaceMatcher',\n      'makeLineMatcher',\n      'makeStringMatcher',\n      'makeCommentMatcher',\n      'makeNumberMatcher',\n      'makeTextMatcher',\n      'OPEN',\n      'CLOSE',\n      'BEFORE',\n      'AFTER',\n      'EMPTY',\n      'SKIP',\n      'util',\n      'make',\n      'S',\n    ])\n\n    assert.ok(Debug != null)\n  })\n})\n"
  },
  {
    "path": "test/bad-plugin.js",
    "content": "module.exports = B A D S Y N T A X\n"
  },
  {
    "path": "test/bar.jsonic",
    "content": "\nqaz: 2"
  },
  {
    "path": "test/bnf.test.js",
    "content": "/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\nconst Fs = require('node:fs')\nconst Path = require('node:path')\n\nconst { Jsonic } = require('..')\nconst {\n  bnf,\n  parseBnf,\n  eliminateLeftRecursion,\n  BnfParseError,\n} = require('../dist/bnf')\nconst BnfCli = require('../dist/jsonic-bnf-cli')\n\n\nconst FIXTURES = Path.join(__dirname, 'grammar')\n\nfunction loadFixture(name) {\n  return Fs.readFileSync(Path.join(FIXTURES, name)).toString()\n}\n\n\n// Strip emitter-injected action references from a spec so tests can\n// assert the structural shape without pinning the action identities.\nfunction stripActions(alt) {\n  if (Array.isArray(alt)) return alt.map(stripActions)\n  if (alt && typeof alt === 'object') {\n    const out = {}\n    for (const [k, v] of Object.entries(alt)) {\n      if (k === 'a') continue\n      out[k] = stripActions(v)\n    }\n    return out\n  }\n  return alt\n}\n\n\ndescribe('bnf', () => {\n\n  describe('converter', () => {\n\n    it('emits spec for alternation of terminals', () => {\n      const spec = bnf(loadFixture('greet.bnf'))\n      // A synthetic `__start__` wrapper ensures end-of-source is\n      // always consumed; it pushes the user's start rule.\n      assert.equal(spec.options.rule.start, '__start__')\n      assert.deepEqual(stripActions(spec.rule.__start__.open), [\n        { p: 'greet', g: 'bnf' },\n      ])\n      assert.deepEqual(stripActions(spec.rule.__start__.close), [\n        { s: '#ZZ', g: 'bnf' },\n      ])\n      // Case-insensitive ABNF strings emit as match.token regexes\n      // (with the `i` flag), not fixed tokens.\n      assert.deepEqual(spec.options.fixed.token, {})\n      assert.ok(spec.options.match.token['#HI'] instanceof RegExp)\n      assert.ok(spec.options.match.token['#HELLO'] instanceof RegExp)\n      assert.deepEqual(stripActions(spec.rule.greet.open), [\n        { s: '#HI', g: 'bnf' },\n        { s: '#HELLO', g: 'bnf' },\n      ])\n      assert.equal(spec.rule.greet.close, undefined)\n    })\n\n\n    it('emits spec for two-terminal sequence', () => {\n      const spec = bnf(loadFixture('pair.bnf'))\n      assert.deepEqual(stripActions(spec.rule.__start__.open), [\n        { p: 'pair', g: 'bnf' },\n      ])\n      assert.deepEqual(stripActions(spec.rule.pair.open), [\n        { s: '#A #B', g: 'bnf' },\n      ])\n    })\n\n\n    it('honours override of start rule', () => {\n      const spec = bnf('a = \"x\"\\nb = \"y\"', { start: 'b' })\n      assert.deepEqual(stripActions(spec.rule.__start__.open), [\n        { p: 'b', g: 'bnf' },\n      ])\n    })\n\n\n    it('emits a single N-token alt for long terminal sequences', () => {\n      const spec = bnf('long = \"a\" \"b\" \"c\" \"d\"')\n      assert.deepEqual(stripActions(spec.rule.long.open), [\n        { s: '#A #B #C #D', g: 'bnf' },\n      ])\n    })\n\n\n    it('chains aux rules for multi-segment alternatives', () => {\n      const spec = bnf('chain = \"a\" inner \"b\" inner \"c\"\\n' +\n        'inner = \"x\"')\n      // Root rule consumes 'a' then pushes inner; close replaces with\n      // the first continuation rule.\n      assert.deepEqual(stripActions(spec.rule.chain.open), [\n        { s: '#A', p: 'inner', g: 'bnf' },\n      ])\n      assert.deepEqual(stripActions(spec.rule.chain.close), [\n        { r: 'chain$step1', g: 'bnf' },\n      ])\n      // First continuation handles 'b' + inner.\n      assert.deepEqual(stripActions(spec.rule['chain$step1'].open), [\n        { s: '#B', p: 'inner', g: 'bnf' },\n      ])\n      // Last continuation handles the trailing 'c' and has no close.\n      assert.deepEqual(stripActions(spec.rule['chain$step2'].open), [\n        { s: '#C', g: 'bnf' },\n      ])\n      assert.equal(spec.rule['chain$step2'].close, undefined)\n    })\n\n\n    it('rejects unknown rule reference', () => {\n      assert.throws(\n        () => bnf('x = missing'),\n        /unknown rule 'missing'/,\n      )\n    })\n\n\n    it('rejects source with no productions', () => {\n      assert.throws(() => bnf('; just a comment\\n'), /no productions/)\n    })\n\n\n    it('surfaces line/column on malformed BNF', () => {\n      try {\n        parseBnf('a = \"x\" )')\n        assert.fail('expected BnfParseError')\n      } catch (e) {\n        assert.ok(e instanceof BnfParseError,\n          `expected BnfParseError, got ${e?.constructor?.name}`)\n        assert.equal(e.line, 1)\n        assert.ok(typeof e.column === 'number' && e.column > 0)\n        assert.match(e.message, /bnf: parse error at line 1, column \\d+/)\n      }\n    })\n\n  })\n\n\n  describe('parseBnf (jsonic-based)', () => {\n\n    it('parses a single terminal', () => {\n      const g = parseBnf('g = \"x\"')\n      assert.deepEqual(g, {\n        productions: [\n          { name: 'g', alts: [[{ kind: 'term', literal: 'x' }]] },\n        ],\n      })\n    })\n\n\n    it('parses alternation', () => {\n      const g = parseBnf('g = \"a\" / \"b\"')\n      assert.deepEqual(g.productions[0].alts, [\n        [{ kind: 'term', literal: 'a' }],\n        [{ kind: 'term', literal: 'b' }],\n      ])\n    })\n\n\n    it('parses sequences and references (angle and bare)', () => {\n      const g = parseBnf('a = foo bar\\nfoo = \"x\"\\nbar = \"y\"')\n      assert.deepEqual(g.productions[0].alts, [\n        [\n          { kind: 'ref', name: 'foo' },\n          { kind: 'ref', name: 'bar' },\n        ],\n      ])\n      assert.equal(g.productions.length, 3)\n    })\n\n\n    it('preserves empty alternatives', () => {\n      const g = parseBnf('x = / \"y\"')\n      assert.deepEqual(g.productions[0].alts, [\n        [],\n        [{ kind: 'term', literal: 'y' }],\n      ])\n    })\n\n\n    it('ignores semicolon comments', () => {\n      const g = parseBnf('; top comment\\ngreet = \"hi\" ; trailing\\n')\n      assert.deepEqual(g.productions[0].alts, [\n        [{ kind: 'term', literal: 'hi' }],\n      ])\n    })\n\n\n    it('parses multiple productions on one line', () => {\n      const g = parseBnf('a = \"x\" b = \"y\"')\n      assert.equal(g.productions.length, 2)\n      assert.equal(g.productions[0].name, 'a')\n      assert.equal(g.productions[1].name, 'b')\n    })\n\n\n    it('parses EBNF postfix operators', () => {\n      const g = parseBnf('g = [ \"a\" ] *\"b\" 1*\"c\"')\n      assert.deepEqual(g.productions[0].alts[0], [\n        // Optional is desugared as opt(group([[term-a]])).\n        {\n          kind: 'opt',\n          inner: { kind: 'group', alts: [[{ kind: 'term', literal: 'a' }]] },\n        },\n        { kind: 'star', inner: { kind: 'term', literal: 'b' } },\n        { kind: 'plus', inner: { kind: 'term', literal: 'c' } },\n      ])\n    })\n\n  })\n\n\n  describe('EBNF desugaring', () => {\n\n    it('optional: accepts presence or absence', () => {\n      const j = Jsonic.make()\n      j.bnf('g = \"hi\" [ \"there\" ]')\n      assert.doesNotThrow(() => j('hi'))\n      assert.doesNotThrow(() => j('hi there'))\n      assert.throws(() => j('hi nope'), /unexpected/)\n    })\n\n\n    it('star: zero or more', () => {\n      const j = Jsonic.make()\n      j.bnf('g = *\"x\" \"end\"')\n      assert.doesNotThrow(() => j('end'))\n      assert.doesNotThrow(() => j('x end'))\n      assert.doesNotThrow(() => j('x x x end'))\n      assert.throws(() => j('y end'), /unexpected/)\n    })\n\n\n    it('plus: one or more', () => {\n      const j = Jsonic.make()\n      j.bnf('g = 1*\"x\" \"end\"')\n      assert.doesNotThrow(() => j('x end'))\n      assert.doesNotThrow(() => j('x x x end'))\n      assert.throws(() => j('end'), /unexpected/)\n    })\n\n\n    it('bounded repetition m*n', () => {\n      // ABNF 2*4\"x\" matches 2, 3, or 4 occurrences.\n      const j = Jsonic.make()\n      j.bnf('g = 2*4\"x\" \"end\"')\n      assert.throws(() => j('end'), /unexpected/)        // 0\n      assert.throws(() => j('x end'), /unexpected/)      // 1\n      assert.doesNotThrow(() => j('x x end'))            // 2\n      assert.doesNotThrow(() => j('x x x end'))          // 3\n      assert.doesNotThrow(() => j('x x x x end'))        // 4\n      assert.throws(() => j('x x x x x end'), /unexpected/) // 5\n    })\n\n\n    it('exact repetition n', () => {\n      const j = Jsonic.make()\n      j.bnf('g = 3\"x\" \"end\"')\n      assert.throws(() => j('x x end'), /unexpected/)\n      assert.doesNotThrow(() => j('x x x end'))\n      assert.throws(() => j('x x x x end'), /unexpected/)\n    })\n\n\n    it('upper-bounded repetition *n', () => {\n      const j = Jsonic.make()\n      j.bnf('g = *2\"x\" \"end\"')\n      assert.doesNotThrow(() => j('end'))\n      assert.doesNotThrow(() => j('x end'))\n      assert.doesNotThrow(() => j('x x end'))\n      assert.throws(() => j('x x x end'), /unexpected/)\n    })\n\n  })\n\n\n  describe('ABNF numeric values', () => {\n\n    it('single hex value matches as the corresponding char', () => {\n      // %x61 = 'a'\n      const j = Jsonic.make()\n      j.bnf('g = %x61')\n      assert.doesNotThrow(() => j('a'))\n      assert.throws(() => j('b'), /unexpected/)\n    })\n\n\n    it('decimal and binary bases match the same char', () => {\n      // %d97 = %x61 = %b1100001 = 'a'\n      const dec = Jsonic.make()\n      dec.bnf('g = %d97')\n      assert.doesNotThrow(() => dec('a'))\n\n      const bin = Jsonic.make()\n      bin.bnf('g = %b1100001')\n      assert.doesNotThrow(() => bin('a'))\n    })\n\n\n    it('concatenated code points build a multi-char literal', () => {\n      // %x66.6f.6f = 'foo'\n      const j = Jsonic.make()\n      j.bnf('g = %x66.6f.6f')\n      assert.doesNotThrow(() => j('foo'))\n      assert.throws(() => j('bar'), /unexpected/)\n    })\n\n\n    it('ranges match any single char in the range', () => {\n      const j = Jsonic.make()\n      j.bnf('g = %x30-39')\n      assert.doesNotThrow(() => j('0'))\n      assert.doesNotThrow(() => j('5'))\n      assert.doesNotThrow(() => j('9'))\n      assert.throws(() => j('a'), /unexpected/)\n    })\n\n\n    it('ranges compose with ABNF repetition', () => {\n      // 1*DIGIT where DIGIT = %x30-39.\n      const j = Jsonic.make()\n      j.bnf('digits = 1*DIGIT\\nDIGIT = %x30-39')\n      assert.doesNotThrow(() => j('1'))\n      assert.doesNotThrow(() => j('12345'))\n      assert.throws(() => j('abc'), /unexpected/)\n    })\n\n  })\n\n\n  describe('ABNF case-insensitive strings', () => {\n\n    it('bare \"foo\" matches in any case (ABNF default)', () => {\n      const j = Jsonic.make()\n      j.bnf('g = \"GET\"')\n      assert.doesNotThrow(() => j('GET'))\n      assert.doesNotThrow(() => j('get'))\n      assert.doesNotThrow(() => j('Get'))\n      assert.doesNotThrow(() => j('gEt'))\n    })\n\n\n    it('%s\"foo\" forces a case-sensitive match', () => {\n      const j = Jsonic.make()\n      j.bnf('g = %s\"GET\"')\n      assert.doesNotThrow(() => j('GET'))\n      assert.throws(() => j('get'), /unexpected/)\n      assert.throws(() => j('Get'), /unexpected/)\n    })\n\n\n    it('%i\"foo\" is the explicit form of the default', () => {\n      const j = Jsonic.make()\n      j.bnf('g = %i\"GET\"')\n      assert.doesNotThrow(() => j('GET'))\n      assert.doesNotThrow(() => j('get'))\n    })\n\n\n    it('literal with no letters is case-independent regardless', () => {\n      const j = Jsonic.make()\n      j.bnf('g = \"+\"')\n      assert.doesNotThrow(() => j('+'))\n    })\n\n\n    it('sensitive and insensitive variants of the same literal are distinct', () => {\n      // %s\"foo\" (sensitive) only matches \"foo\" exactly; \"foo\"\n      // (insensitive) accepts any case. Both should coexist.\n      const j = Jsonic.make()\n      j.bnf('g = %s\"foo\" / \"BAR\"')\n      assert.doesNotThrow(() => j('foo'))\n      assert.throws(() => j('FOO'), /unexpected/)  // sensitive\n      assert.doesNotThrow(() => j('bar'))         // insensitive\n      assert.doesNotThrow(() => j('BAR'))\n    })\n\n  })\n\n\n  describe('ABNF incremental alternatives', () => {\n\n    it('name =/ alt folds into the earlier production', () => {\n      const j = Jsonic.make()\n      j.bnf('command = \"get\"\\ncommand =/ \"post\"\\ncommand =/ \"delete\"')\n      assert.doesNotThrow(() => j('get'))\n      assert.doesNotThrow(() => j('post'))\n      assert.doesNotThrow(() => j('delete'))\n      assert.throws(() => j('put'), /unexpected/)\n    })\n\n\n    it('a single =/ appends one alternative', () => {\n      const j = Jsonic.make()\n      j.bnf('g = \"a\"\\ng =/ \"b\"')\n      assert.doesNotThrow(() => j('a'))\n      assert.doesNotThrow(() => j('b'))\n    })\n\n\n    it('multi-alt increments merge cleanly', () => {\n      // An increment can itself introduce several alternatives.\n      const j = Jsonic.make()\n      j.bnf('g = \"a\"\\ng =/ \"b\" / \"c\"')\n      assert.doesNotThrow(() => j('a'))\n      assert.doesNotThrow(() => j('b'))\n      assert.doesNotThrow(() => j('c'))\n    })\n\n\n    it('=/ without an earlier base rule is rejected', () => {\n      assert.throws(\n        () => bnf('g =/ \"a\"'),\n        /has no earlier .* to extend/,\n      )\n    })\n\n  })\n\n\n  describe('ABNF line continuation', () => {\n\n    it('an alternation can wrap across multiple lines', () => {\n      // RFC 5234 lets a rule span several lines as long as each\n      // continuation line is indented. The parser achieves this\n      // implicitly: newlines are whitespace, production boundaries\n      // are detected by the `name =` pattern rather than by end of\n      // line, so a wrapped rule parses as one production.\n      const g = parseBnf([\n        'command = \"get\"',\n        '        / \"post\"',\n        '        / \"delete\"',\n      ].join('\\n'))\n      assert.equal(g.productions.length, 1)\n      assert.equal(g.productions[0].name, 'command')\n      assert.equal(g.productions[0].alts.length, 3)\n    })\n\n\n    it('a sequence can wrap across multiple lines', () => {\n      const j = Jsonic.make()\n      j.bnf([\n        'req = \"GET\"',\n        '      \"path\"',\n        '      \"end\"',\n      ].join('\\n'))\n      assert.doesNotThrow(() => j('GET path end'))\n    })\n\n\n    it('wrapped rules sit alongside normal rules without interaction', () => {\n      const j = Jsonic.make()\n      j.bnf([\n        'verb = \"GET\"',\n        '     / \"POST\"',\n        'path = \"/\"',\n        '     / \"/api\"',\n      ].join('\\n'))\n      // verb rule accepts both methods\n      const jverb = Jsonic.make()\n      jverb.bnf('verb = \"GET\" / \"POST\"')\n      assert.doesNotThrow(() => jverb('GET'))\n      assert.doesNotThrow(() => jverb('POST'))\n    })\n\n  })\n\n\n  describe('ABNF core rules (RFC 5234 Appendix B.1)', () => {\n\n    it('DIGIT is auto-included when referenced', () => {\n      const j = Jsonic.make()\n      j.bnf('number = 1*DIGIT')\n      assert.doesNotThrow(() => j('12345'))\n      assert.throws(() => j('abc'), /unexpected/)\n    })\n\n\n    it('ALPHA matches upper and lower letters', () => {\n      const j = Jsonic.make()\n      j.bnf('word = 1*ALPHA')\n      assert.doesNotThrow(() => j('hello'))\n      assert.doesNotThrow(() => j('WORLD'))\n      assert.doesNotThrow(() => j('MixedCase'))\n      assert.throws(() => j('123'), /unexpected/)\n    })\n\n\n    it('HEXDIG transitively pulls in DIGIT', () => {\n      const j = Jsonic.make()\n      j.bnf('hex = 1*HEXDIG')\n      assert.doesNotThrow(() => j('DEADBEEF'))\n      assert.doesNotThrow(() => j('12AB'))\n      assert.throws(() => j('xyz'), /unexpected/)\n    })\n\n\n    it('BIT matches 0 or 1', () => {\n      const j = Jsonic.make()\n      j.bnf('bits = 1*BIT')\n      assert.doesNotThrow(() => j('0101'))\n      assert.throws(() => j('012'), /unexpected/)\n    })\n\n\n    it('user can override a core rule', () => {\n      // Locally redefining DIGIT as \"only odd digits\" wins over\n      // the core definition.\n      const j = Jsonic.make()\n      j.bnf('number = 1*DIGIT\\nDIGIT = \"1\" / \"3\" / \"5\" / \"7\" / \"9\"')\n      assert.doesNotThrow(() => j('135'))\n      assert.throws(() => j('246'), /unexpected/)\n    })\n\n\n    it('a grammar that references nothing from the core library has no core rules added', () => {\n      const { parseBnf } = require('../dist/bnf')\n      const g = parseBnf('g = \"a\" / \"b\"')\n      assert.deepEqual(g.productions.map((p) => p.name), ['g'])\n    })\n\n\n    it('grouping selects among alternatives', () => {\n      const j = Jsonic.make()\n      j.bnf('g = (\"a\" / \"b\") \"c\"')\n      assert.doesNotThrow(() => j('a c'))\n      assert.doesNotThrow(() => j('b c'))\n      assert.throws(() => j('c'), /unexpected/)\n      assert.throws(() => j('x c'), /unexpected/)\n    })\n\n\n    it('group with plus: one or more sub-sequences', () => {\n      const j = Jsonic.make()\n      j.bnf('g = 1*(\"a\" \"b\") \"end\"')\n      assert.doesNotThrow(() => j('a b end'))\n      assert.doesNotThrow(() => j('a b a b a b end'))\n      assert.throws(() => j('end'), /unexpected/)\n    })\n\n\n    it('group of alternatives with star', () => {\n      const j = Jsonic.make()\n      j.bnf('g = *(\"a\" / \"b\") \"end\"')\n      assert.doesNotThrow(() => j('end'))\n      assert.doesNotThrow(() => j('a end'))\n      assert.doesNotThrow(() => j('a b a end'))\n      assert.throws(() => j('c end'), /unexpected/)\n    })\n\n  })\n\n\n  // `regex terminals` describe block was dropped when ABNF stage 2\n  // replaced alternation `|` with `/`, which conflicts with the\n  // regex delimiter. Equivalent tests based on ABNF %x numeric\n  // ranges will reappear once that syntax lands.\n\n\n  describe('fixture round-trips', () => {\n\n    it('arith.bnf accepts precedence-free arithmetic', () => {\n      const j = Jsonic.make()\n      j.bnf(loadFixture('arith.bnf'))\n      assert.doesNotThrow(() => j('1'))\n      assert.doesNotThrow(() => j('1 + 2'))\n      assert.doesNotThrow(() => j('1 + 2 * 3'))\n      assert.doesNotThrow(() => j('( 1 + 2 ) * 3'))\n      assert.doesNotThrow(() => j('1 + ( 2 * 3 ) - 4 / 5'))\n      assert.throws(() => j('1 +'), /unexpected/)\n      assert.throws(() => j('+ 1'), /unexpected/)\n    })\n\n\n    it('json-subset.bnf accepts nested structures', () => {\n      // The fixture uses simple single-letter terminals in place of\n      // quoted strings until ABNF %x ranges arrive — exercise it\n      // with matching inputs.\n      const j = Jsonic.make()\n      j.bnf(loadFixture('json-subset.bnf'))\n      assert.doesNotThrow(() => j('1'))\n      assert.doesNotThrow(() => j('a'))\n      assert.doesNotThrow(() => j('{ a : 1 }'))\n      assert.doesNotThrow(() => j('[ 1 , 2 , 3 ]'))\n      assert.doesNotThrow(() => j('{ a : [ 1 , 2 ] , b : c }'))\n      assert.throws(() => j('{ a 1 }'), /unexpected/)  // missing colon\n    })\n\n\n    it('arith-leftrec.bnf parses the same language as arith.bnf', () => {\n      // Same language, but written in the natural left-recursive form.\n      const j = Jsonic.make()\n      j.bnf(loadFixture('arith-leftrec.bnf'))\n      assert.doesNotThrow(() => j('1'))\n      assert.doesNotThrow(() => j('1 + 2 * 3'))\n      assert.doesNotThrow(() => j('( 1 + 2 ) * 3'))\n      assert.doesNotThrow(() => j('1 / 2 + 3 - 4 * 5'))\n      assert.throws(() => j('+ 1'), /unexpected/)\n    })\n\n  })\n\n\n  describe('left-recursion elimination', () => {\n\n    it('rewrites P -> P alpha | beta into P -> beta (alpha)*', () => {\n      const g = parseBnf('e = e \"+\" t / t\\nt = \"1\"')\n      const r = eliminateLeftRecursion(g)\n      const expr = r.productions.find((p) => p.name === 'e')\n      assert.equal(expr.alts.length, 1)\n      const alt = expr.alts[0]\n      assert.equal(alt.length, 2)\n      // Seed is t's body inlined (Paull's topo-orders t before e\n      // since e leads with t, so t's alts are substituted in).\n      assert.equal(alt[0].kind, 'term')\n      // Tail wrapped in a star.\n      assert.equal(alt[1].kind, 'star')\n    })\n\n\n    it('handles multiple recursive and seed alternatives', () => {\n      const g = parseBnf(\n        'e = e \"+\" t / e \"-\" t / t / \"(\" e \")\"\\n' +\n        't = \"1\"')\n      const r = eliminateLeftRecursion(g)\n      const e = r.productions.find((p) => p.name === 'e')\n      assert.equal(e.alts.length, 1)\n      const [seed, star] = e.alts[0]\n      // Two seeds → grouped.\n      assert.equal(seed.kind, 'group')\n      assert.equal(seed.alts.length, 2)\n      // Two recursives → star of group.\n      assert.equal(star.kind, 'star')\n      assert.equal(star.inner.kind, 'group')\n      assert.equal(star.inner.alts.length, 2)\n    })\n\n\n    it('rejects purely left-recursive productions (no seed)', () => {\n      assert.throws(\n        () => bnf('a = a \"x\"'),\n        /purely left-recursive/,\n      )\n    })\n\n\n    it('silently drops trivial P = P alternatives', () => {\n      // `a = a` adds nothing to the language (it just re-derives\n      // P with no progress), so the pass drops it. The remaining alt\n      // defines a's actual language.\n      const j = Jsonic.make()\n      j.bnf('a = a / \"x\"')\n      assert.doesNotThrow(() => j('x'))\n      assert.throws(() => j('y'), /unexpected/)\n    })\n\n\n    it('left-recursive grammar produces the same parses as the rewritten one', () => {\n      const j1 = Jsonic.make()\n      j1.bnf(loadFixture('arith.bnf'))\n      const j2 = Jsonic.make()\n      j2.bnf(loadFixture('arith-leftrec.bnf'))\n      // The two grammars accept the same set of strings (we only\n      // assert that both either accept or both reject — the trees\n      // differ in shape because the helpers are constructed\n      // differently).\n      const samples = ['1', '1 + 2', '( 1 + 2 ) * 3', '1 / 2 + 3 - 4']\n      for (const s of samples) {\n        assert.doesNotThrow(() => j1(s), `arith should accept ${s}`)\n        assert.doesNotThrow(() => j2(s), `arith-leftrec should accept ${s}`)\n      }\n    })\n\n  })\n\n\n  // Indirect left recursion (cycles through at least one intermediate\n  // rule) is not handled by the static rewrite in\n  // `eliminateLeftRecursion`. The tests below pin the desired\n  // behaviour once the k+sI runtime guard from the feasibility doc\n  // is wired in: legal inputs parse, illegal ones reject, and the\n  // parser never loops. Each test has an explicit timeout so a\n  // runaway parse is reported as a failure rather than hanging CI.\n  //\n  // Until the guard is implemented, these tests are expected to\n  // fail (they fall through the current emitter and either throw\n  // on convert, throw \"unexpected\" on parse, or — without the\n  // timeout — spin forever).\n  describe('indirect left recursion (runtime guard)', () => {\n\n    const INDIRECT_2 = 'p = q \"x\"\\nq = p \"y\" / \"z\"'\n    const INDIRECT_3 =\n      'a = b \"1\"\\nb = c \"2\"\\nc = a \"3\" / \"x\"'\n\n\n    it('two-rule cycle: accepts shortest seed derivation', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_2)\n      // p = q x ; q = z  → \"z x\"\n      assert.doesNotThrow(() => j('z x'))\n    })\n\n\n    it('two-rule cycle: accepts one-step unfold', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_2)\n      // q = p y = (z x) y  ⇒  p = q x = z x y x\n      assert.doesNotThrow(() => j('z x y x'))\n    })\n\n\n    it('two-rule cycle: accepts two-step unfold', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_2)\n      // p = z x y x y x (iterate once more)\n      assert.doesNotThrow(() => j('z x y x y x'))\n    })\n\n\n    it('two-rule cycle: rejects input missing required trailing x', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_2)\n      assert.throws(() => j('z'), /unexpected/)\n    })\n\n\n    it('two-rule cycle: rejects input starting on the wrong seed', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_2)\n      // \"y x\" has no legal derivation — must be preceded by q's seed.\n      assert.throws(() => j('y x'), /unexpected/)\n    })\n\n\n    it('two-rule cycle: does not infinite-loop on a malformed prefix', { timeout: 2000 }, () => {\n      // The test's own timeout is the safety net: the guard must\n      // terminate the parse attempt even when no legal derivation\n      // exists for the input.\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_2)\n      assert.throws(() => j('w'), /unexpected/)\n    })\n\n\n    it('three-rule cycle: accepts shortest seed derivation', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_3)\n      // c = x ; b = x 2 ; a = x 2 1\n      assert.doesNotThrow(() => j('x 2 1'))\n    })\n\n\n    it('three-rule cycle: accepts one-step unfold', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_3)\n      // c = a 3 = (x 2 1) 3 ; b = x 2 1 3 2 ; a = x 2 1 3 2 1\n      assert.doesNotThrow(() => j('x 2 1 3 2 1'))\n    })\n\n\n    it('three-rule cycle: rejects premature stop', { timeout: 2000 }, () => {\n      const j = Jsonic.make()\n      j.bnf(INDIRECT_3)\n      assert.throws(() => j('x 2'), /unexpected/)\n    })\n\n\n    it('hidden left recursion through a nullable ref (a → b a, b → y|ε)', { timeout: 2000 }, () => {\n      // The leading ref is `b`, not `a`, but `b` is nullable so\n      // `a` is reachable from itself through b's ε-alt. Paull's\n      // topo-orders b before a, inlines b's alts (including ε)\n      // into a, then drops the resulting `[a]` trivial alt.\n      const src = 'a = b a / \"x\"\\nb = \"y\" /'\n      const j = Jsonic.make()\n      j.bnf(src)\n      assert.doesNotThrow(() => j('x'))\n      assert.doesNotThrow(() => j('y x'))\n      assert.doesNotThrow(() => j('y y y x'))\n      assert.throws(() => j('y'), /unexpected/)\n      assert.throws(() => j('z'), /unexpected/)\n    })\n\n\n    it('hidden LR with three-rule nullable chain', { timeout: 2000 }, () => {\n      // a → b a | \"x\" ; b → c ; c → \"y\" | ε\n      // A longer chain of nullable leading refs that Paull's has\n      // to collapse in a single topo pass.\n      const src = 'a = b a / \"x\"\\nb = c\\nc = \"y\" /'\n      const j = Jsonic.make()\n      j.bnf(src)\n      assert.doesNotThrow(() => j('x'))\n      assert.doesNotThrow(() => j('y x'))\n      assert.doesNotThrow(() => j('y y x'))\n    })\n\n\n    it('indirect cycle alongside a direct seed still parses correctly', { timeout: 2000 }, () => {\n      // `a` has its own seed `\"init\"`, so the indirect cycle\n      // (a → b → a) is only exercised for `b`-starting inputs.\n      const src =\n        'a = b \"x\" / \"init\"\\n' +\n        'b = a \"y\" / \"z\"'\n      const j = Jsonic.make()\n      j.bnf(src)\n      assert.doesNotThrow(() => j('init'))    // a's direct seed\n      assert.doesNotThrow(() => j('z x'))     // through b's seed\n      assert.doesNotThrow(() => j('init y x')) // a-seed, then b-y, then a-x\n      assert.throws(() => j('z'), /unexpected/)\n    })\n\n  })\n\n\n  describe('jsonic.bnf()', () => {\n\n    it('installs grammar and parses matching input', () => {\n      const j = Jsonic.make()\n      j.bnf(loadFixture('greet.bnf'))\n      // Parser accepts both alternates without throwing.\n      assert.doesNotThrow(() => j('hi'))\n      assert.doesNotThrow(() => j('hello'))\n    })\n\n\n    it('rejects input outside the grammar', () => {\n      const j = Jsonic.make()\n      j.bnf(loadFixture('greet.bnf'))\n      assert.throws(() => j('bye'), /unexpected/)\n    })\n\n\n    it('parses a two-terminal sequence', () => {\n      const j = Jsonic.make()\n      j.bnf(loadFixture('pair.bnf'))\n      assert.doesNotThrow(() => j('a b'))\n    })\n\n\n    it('returns the emitted spec', () => {\n      const j = Jsonic.make()\n      const spec = j.bnf('g = \"x\"')\n      assert.equal(spec.options.rule.start, '__start__')\n      // Case-insensitive literal emits as a match.token regex.\n      assert.ok(spec.options.match.token['#X'] instanceof RegExp)\n    })\n\n\n    it('bnf.toSpec builds the spec without installing', () => {\n      const j = Jsonic.make()\n      const spec = j.bnf.toSpec('g = \"x\"')\n      // Spec is returned; the default JSON grammar is still active\n      // since toSpec does not install the BNF grammar.\n      assert.equal(spec.options.rule.start, '__start__')\n      assert.deepEqual(j('a:1'), { a: 1 })\n      // A second call to install still works afterwards.\n      j.bnf('g = \"x\"')\n      assert.deepEqual(j('x'), { rule: 'g', src: 'x', kids: [] })\n    })\n\n\n    it('produces an AST tagged by rule name', () => {\n      // Every user-declared rule becomes a `{rule, src, kids}`\n      // node. Leaf rules (only terminals / char classes) have empty\n      // kids and carry the matched text as `src`.\n      const j = Jsonic.make()\n      j.bnf('g = \"hi\" / \"hello\"')\n      assert.deepEqual(j('hi'), { rule: 'g', src: 'hi', kids: [] })\n      assert.deepEqual(j('hello'), { rule: 'g', src: 'hello', kids: [] })\n\n      // Note: under current Paull-style LR elimination, a leading\n      // ref to another user rule gets inlined, so that rule does\n      // NOT appear as a child node. `p = \"a\" q, q = \"b\"` works\n      // because `q` is NOT at the leading position of p's alt —\n      // it's preceded by the `\"a\"` terminal.\n      const j2 = Jsonic.make()\n      j2.bnf('p = \"a\" q\\nq = \"b\"')\n      assert.deepEqual(j2('a b'), {\n        rule: 'p',\n        src: 'ab',\n        kids: [{ rule: 'q', src: 'b', kids: [] }],\n      })\n    })\n\n\n    it('ergonomic AST for composite rules (pair = name \"=\" value)', () => {\n      const j = Jsonic.make()\n      j.bnf(`\nx = \"x\" name \"=\" value\nname = 1*ALPHA\nvalue = 1*DIGIT\n`)\n      // \"x\" at the leading position keeps name/value as kids; both\n      // leaf rules return as `{rule, src, kids: []}`.\n      assert.deepEqual(j('xfoo=42'), {\n        rule: 'x',\n        src: 'xfoo=42',\n        kids: [\n          { rule: 'name', src: 'foo', kids: [] },\n          { rule: 'value', src: '42', kids: [] },\n        ],\n      })\n    })\n\n\n    it('ergonomic AST for star-with-leading-terminal', () => {\n      // Star repetition with a leading terminal `[` keeps inner\n      // named rules visible in the AST (Paull's substitution only\n      // inlines LEADING refs; a `[` term at the alt's head blocks\n      // the inlining).\n      const j = Jsonic.make()\n      j.bnf(`\nlist = \"[\" item *(\",\" item) \"]\"\nitem = 1*ALPHA\n`)\n      assert.deepEqual(j('[a,b,c]'), {\n        rule: 'list',\n        src: '[a,b,c]',\n        kids: [\n          { rule: 'item', src: 'a', kids: [] },\n          { rule: 'item', src: 'b', kids: [] },\n          { rule: 'item', src: 'c', kids: [] },\n        ],\n      })\n    })\n\n  })\n\n\n  describe('cli', () => {\n\n    it('converts a fixture file', async () => {\n      const cn = makeConsole()\n      await BnfCli.run(\n        [0, 0, '-f', Path.join(FIXTURES, 'greet.bnf')],\n        cn,\n      )\n      const out = JSON.parse(cn.d.log[0][0])\n      // CLI output serialises actions as FuncRef strings; only assert\n      // that the dispatch to the user's start rule is in place.\n      assert.equal(out.rule.__start__.open[0].p, 'greet')\n      // Case-insensitive literals appear in match.token after\n      // JSON serialisation as the RegExp's stringified form.\n      assert.deepEqual(Object.keys(out.options.match.token).sort(),\n        ['#HELLO', '#HI'])\n    })\n\n\n    it('accepts inline bnf source', async () => {\n      const cn = makeConsole()\n      await BnfCli.run([0, 0, 'g = \"x\"'], cn)\n      const out = JSON.parse(cn.d.log[0][0])\n      assert.equal(out.rule.__start__.open[0].p, 'g')\n    })\n\n\n    it('honours --start', async () => {\n      const cn = makeConsole()\n      await BnfCli.run(\n        [0, 0, '--start', 'b', 'a = \"x\" b = \"y\"'],\n        cn,\n      )\n      const out = JSON.parse(cn.d.log[0][0])\n      assert.equal(out.rule.__start__.open[0].p, 'b')\n    })\n\n\n    it('reads from stdin when invoked with -', async () => {\n      const cn = makeConsole()\n      cn.test$ = 'g = \"x\"'\n      await BnfCli.run([0, 0, '-'], cn)\n      const out = JSON.parse(cn.d.log[0][0])\n      assert.equal(out.rule.__start__.open[0].p, 'g')\n    })\n\n\n    it('prints help with -h', async () => {\n      const cn = makeConsole()\n      await BnfCli.run([0, 0, '-h'], cn)\n      assert.match(cn.d.log[0][0], /Usage:/)\n    })\n\n\n    it('--parse validates a sample and prints the tree', async () => {\n      const cn = makeConsole()\n      const prevExitCode = process.exitCode\n      await BnfCli.run(\n        [0, 0, 'g = \"hi\" / \"hello\"', '--parse', 'hi'],\n        cn,\n      )\n      // Validation prints an `ok:` line to stdout; no spec dump.\n      assert.equal(cn.d.log.length, 1)\n      assert.match(cn.d.log[0][0], /^ok: \"hi\" ->/)\n      // A successful --parse leaves the process exit code unchanged.\n      assert.equal(process.exitCode, prevExitCode)\n    })\n\n\n    it('--parse flags mismatched samples as failures', async () => {\n      const cn = makeConsole()\n      const prevExitCode = process.exitCode\n      await BnfCli.run(\n        [0, 0, 'g = \"hi\"', '--parse', 'bye'],\n        cn,\n      )\n      assert.equal(cn.d.log.length, 0)\n      assert.equal(cn.d.err.length, 1)\n      assert.match(cn.d.err[0][0], /^fail: \"bye\":/)\n      assert.equal(process.exitCode, 1)\n      process.exitCode = prevExitCode\n    })\n\n  })\n\n})\n\n\nfunction makeConsole() {\n  const d = { log: [], err: [] }\n  return {\n    d,\n    log: (...rest) => d.log.push(rest),\n    error: (...rest) => d.err.push(rest),\n  }\n}\n"
  },
  {
    "path": "test/cli.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst JsonicCli = require('../dist/jsonic-cli')\nconst jr = async (...rest) => await JsonicCli.run(...rest)\n\ndescribe('cli', function () {\n  it('basic', () => {\n    let cn = make_cn()\n    jr([0, 0, 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    jr([0, 0, '-o', 'number.lex=false', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":\"1\"}')\n  })\n\n  it('args', async () => {\n    let cn = make_cn()\n\n    // TODO: fix test\n    // jr([0,0,'-d',\n    //     '-o','debug.maxlen=11',\n    //     '--option','value.lex=false',\n    //     '--',\n    //     'a:true'],cn)\n    // expect(cn.d.log[39][0]).equal('{\"a\":\"true\"}')\n\n    // jr([0,0,'--debug',\n    //     '--option','debug.maxlen=11',\n    //     '-o','text.lex_value=false',\n    //     'a:true'],cn)\n    // expect(cn.d.log[39][0]).equal('{\"a\":\"true\"}')\n\n    // TODO: review loggin via cli\n\n    // cn = make_cn()\n    // jr([0, 0, '--meta', 'log=-1', 'a:true'], cn)\n    // // console.log(cn.d.log.map((e,i)=>i+': '+e))\n    // expect(cn.d.log[48][0]).equal('{\"a\":true}')\n\n    // cn = make_cn()\n    // jr([0, 0, '-m', 'log=-1', 'a:true'], cn)\n    // expect(cn.d.log[48][0]).equal('{\"a\":true}')\n\n    cn = make_cn()\n    jr([0, 0, '-h'], cn)\n    assert.deepEqual(cn.d.log[0][0].includes('Usage:'), true)\n\n    cn = make_cn()\n    jr([0, 0, '--help'], cn)\n    assert.deepEqual(cn.d.log[0][0].includes('Usage:'), true)\n\n    cn = make_cn()\n    jr([0, 0, 'a:1', 'b:[2]', 'c:{x:1}'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1,\"b\":[2],\"c\":{\"x\":1}}')\n\n    // // TODO: `{zed:2}` should work too!\n    cn = make_cn()\n    await jr([0, 0, '-f', './test/foo.jsonic', 'zed:2'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"bar\":1,\"zed\":2}')\n\n    // TODO: jest borks this\n    // cn = make_cn()\n    // await jr([0,0,'-f','./test/foo.jsonic','--file','./test/bar.jsonic'],cn)\n    // //console.log('Q',cn.d.log)\n    // expect(cn.d.log[0][0]).equal('{\"bar\":1,\"qaz\":2}')\n\n    cn = make_cn()\n    jr([0, 0, '--not-an-arg-so-ignored', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    cn.test$ = '{a:1}'\n    await jr([0, 0], cn)\n    // console.log(cn.d.log)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    cn.test$ = '{a:1}'\n    await jr([0, 0, '-'], cn)\n    // console.log(cn.d.log)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    cn.test$ = '{a:1}'\n    await jr([0, 0, '-', 'b:2'], cn)\n    // console.log(cn.d.log)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1,\"b\":2}')\n  })\n\n  it('bad-args', async () => {\n    let cn = make_cn()\n    jr([0, 0, '-f', { bad: 1 }, 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    jr([0, 0, '-f', '', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    jr([0, 0, '-o', '', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    jr([0, 0, '-o', '=', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    jr([0, 0, '-o', 'bad=', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    // TODO: jest borks require so test won't work\n    // try {\n    //   cn = make_cn()\n    //   await jr([0,0,'-p','./test/bad-plugin','a:1'],cn)\n    //   Code.fail()\n    // }\n    // catch(e) {\n    //   expect(e.message.includes('identifier')).equal(true)\n    // }\n  })\n\n  it('plugin', async () => {\n    let cn = make_cn()\n    await jr([0, 0, '-p', '../test/p0', '-o', 'plugin.p0.x=0', 'a:X'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":0}')\n\n    cn = make_cn()\n    await jr(\n      [\n        0,\n        0,\n        '-p',\n        '../test/p0',\n        '-o',\n        'plugin.p0.x=0',\n        '-o',\n        'plugin.p0.s=W',\n        'a:W',\n      ],\n      cn,\n    )\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":0}')\n\n    cn = make_cn()\n    await jr([0, 0, '-o', 'plugin.p1.y=1', '-p', '../test/p1', 'a:Y'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":1}')\n\n    cn = make_cn()\n    await jr(\n      [\n        0,\n        0,\n        '-o',\n        'plugin.p0.x=0',\n        '-p',\n        '../test/p0',\n        '-o',\n        'plugin.p1.y=1',\n        '-p',\n        '../test/p1',\n        'a:X,b:Y',\n      ],\n      cn,\n    )\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":0,\"b\":1}')\n\n    cn = make_cn()\n    await jr([0, 0, '-p', '../test/p2', '-o', 'plugin.p2.z=2', 'a:Z'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":2}')\n\n    cn = make_cn()\n    await jr(\n      [0, 0, '-p', '../test/pa-qa.js', '-o', 'plugin.paqa.q=3', 'a:Q'],\n      cn,\n    )\n    assert.deepEqual(cn.d.log[0][0], '{\"a\":3}')\n\n  })\n\n  it('stringify', async () => {\n    let cn = make_cn()\n    jr([0, 0, '-o', 'JSON.space=2', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\\n  \"a\": 1\\n}')\n\n    cn = make_cn()\n    jr([0, 0, '-n', 'a:1'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\\n  \"a\": 1\\n}')\n\n    cn = make_cn()\n    jr([0, 0, '-o', 'JSON.replacer=[b]', 'a:1,b:2'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"b\":2}')\n\n    cn = make_cn()\n    jr([0, 0, '-o', 'JSON.replacer=b', 'a:1,b:2'], cn)\n    assert.deepEqual(cn.d.log[0][0], '{\"b\":2}')\n  })\n})\n\nfunction make_cn() {\n  let d = {\n    log: [],\n    dir: [],\n  }\n  return {\n    test$: true,\n    d,\n    log: (...rest) => d.log.push(rest),\n    dir: (...rest) => d.dir.push(rest),\n  }\n}\n"
  },
  {
    "path": "test/comma.test.js",
    "content": "/* Copyright (c) 2013-2023 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\nconst { loadTSV } = require('./utility')\n\nfunction tsvTest(name) {\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    try {\n      assert.deepEqual(Jsonic(input), JSON.parse(expected))\n    } catch (err) {\n      err.message = `${name} row ${row}: input=${input} expected=${expected}\\n${err.message}`\n      throw err\n    }\n  }\n}\n\ndescribe('comman', function () {\n  it('implicit-comma', () => {\n    tsvTest('comma-implicit-comma')\n  })\n\n  it('optional-comma', () => {\n    tsvTest('comma-optional-comma')\n  })\n})\n"
  },
  {
    "path": "test/comment.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst Util = require('util')\nconst I = Util.inspect\n\nconst { Jsonic, JsonicError, RuleSpec } = require('..')\n\nconst j = Jsonic\n\nconst JS = (x) => JSON.stringify(x)\n\ndescribe('comment', function () {\n  it('single-comment-line', () => {\n    assert.deepEqual(j('a#b'), 'a')\n    assert.deepEqual(j('a:1#b'), { a: 1 })\n    assert.deepEqual(j('#a:1'), undefined)\n    assert.deepEqual(j('#a:1\\nb:2'), { b: 2 })\n    assert.deepEqual(j('b:2\\n#a:1'), { b: 2 })\n    assert.deepEqual(j('b:2,\\n#a:1\\nc:3'), { b: 2, c: 3 })\n    assert.deepEqual(j('//a:1'), undefined)\n    assert.deepEqual(j('//a:1\\nb:2'), { b: 2 })\n    assert.deepEqual(j('b:2\\n//a:1'), { b: 2 })\n    assert.deepEqual(j('b:2,\\n//a:1\\nc:3'), { b: 2, c: 3 })\n  })\n\n  it('multi-comment', () => {\n    assert.deepEqual(j('/*a:1*/'), undefined)\n    assert.deepEqual(j('/*a:1*/\\nb:2'), { b: 2 })\n    assert.deepEqual(j('/*a:1\\n*/b:2'), { b: 2 })\n    assert.deepEqual(j('b:2\\n/*a:1*/'), { b: 2 })\n    assert.deepEqual(j('b:2,\\n/*\\na:1,\\n*/\\nc:3'), { b: 2, c: 3 })\n\n    assert.throws(() => j('/*'), /unterminated_comment].*:1:1/s)\n    assert.throws(() => j('\\n/*'), /unterminated_comment].*:2:1/s)\n    assert.throws(() => j('a/*'), /unterminated_comment].*:1:2/s)\n    assert.throws(() => j('\\na/*'), /unterminated_comment].*:2:2/s)\n\n    assert.throws(() => j('a:1/*\\n\\n*/{'), /unexpected].*:3:3/s)\n\n    // Implicit close\n    // TODO: OPTION\n    // expect(j('b:2\\n/*a:1')).equal({b:2})\n    // expect(j('b:2\\n/*/*/*a:1')).equal({b:2})\n  })\n\n  it('comment-off', () => {\n    let j0 = Jsonic.make({\n      comment: {\n        def: {\n          hash: null,\n          slash: false,\n          multi: { lex: false },\n        },\n      },\n    })\n\n    assert.deepEqual(j0('a: #b'), { a: '#b' })\n    assert.deepEqual(j0('a: //b'), { a: '//b' })\n    assert.deepEqual(j0('a: /*b*/'), { a: '/*b*/' })\n  })\n\n  // TODO: PLUGIN\n  // it('balanced-multi-comment', () => {\n  //   // Active by default\n  //   expect(j('/*/*/*a:1*/*/*/b:2')).equal({b:2})\n  //   expect(j('/*/*/*a:1*/*/b:2')).equal(undefined)\n  //   expect(j('/*/*/*a/b*/*/*/b:2')).equal({b:2})\n\n  //   let nobal = Jsonic.make({comment:{balance:false}})\n  //   expect(nobal.options.comment.balance).false()\n\n  //   // NOTE: comment markers inside text are active!\n  //   expect(nobal('/*/*/*a:1*/*/*/,b:2')).equal({ '*a': '1*', b: 2 })\n\n  //   // Custom multiline comments\n  //   let coffee = Jsonic.make({comment:{marker:{'###':'###'}}})\n  //   expect(coffee('\\n###a:1\\nb:2\\n###\\nc:3')).equal({c:3})\n\n  //   // NOTE: no balancing if open === close\n  //   expect(coffee('\\n###a:1\\n###b:2\\n###\\nc:3\\n###\\nd:4')).equal({b:2,d:4})\n  // })\n})\n"
  },
  {
    "path": "test/csv-grammar.test.js",
    "content": "/* Copyright (c) 2013-2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Minimal CSV grammar expressed directly against the jsonic parser API.\n// The grammar is intentionally simple: comma-separated values, newline-separated\n// rows, single-token fields (numbers, strings, keywords, and unquoted text).\n// Empty fields are emitted as empty strings; empty rows are skipped.\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\nfunction makeCsv() {\n  const j = Jsonic.make({\n    tokenSet: {\n      // Default IGNORE is [#SP, #LN, #CM]. Drop #LN so newlines become\n      // parsing tokens; keep space and comment in IGNORE.\n      IGNORE: [undefined, null, undefined],\n    },\n    rule: {\n      start: 'csv',\n      // Disable jsonic-only extensions (implicit maps, path-diving, etc.).\n      exclude: 'jsonic,imp',\n    },\n    lex: {\n      // Empty source should return [] (an empty CSV) rather than undefined.\n      emptyResult: [],\n    },\n  })\n\n  const { CA, LN, ZZ } = j.token\n  const { VAL } = j.tokenSet\n\n  // Top-level: collect rows into an array.\n  j.rule('csv', (rs) => {\n    rs.bo((r) => { r.node = [] })\n      .open([\n        { s: [ZZ] },\n        { p: 'row' },\n      ])\n      .close([\n        { s: [LN, ZZ] },\n        { s: [LN], r: 'csvcont' },\n        { s: [ZZ] },\n      ])\n      .bc((r) => {\n        if (Array.isArray(r.child.node) && r.child.node.length > 0) {\n          r.node.push(r.child.node)\n        }\n      })\n  })\n\n  // csv continuation: same as csv but without the node-resetting bo so the\n  // shared outer list survives the tail-call replace.\n  j.rule('csvcont', (rs) => {\n    rs.open([\n      { s: [ZZ] },\n      { p: 'row' },\n    ])\n    .close([\n      { s: [LN, ZZ] },\n      { s: [LN], r: 'csvcont' },\n      { s: [ZZ] },\n    ])\n    .bc((r) => {\n      if (Array.isArray(r.child.node) && r.child.node.length > 0) {\n        r.node.push(r.child.node)\n      }\n    })\n  })\n\n  // Row: first cell initialises r.node with its value so that subsequent\n  // rowcont iterations can push into the same array. An immediate row-ending\n  // token (LN/ZZ) produces an empty row (dropped by csv.bc).\n  j.rule('row', (rs) => {\n    rs.open([\n      { s: [VAL], a: (r) => { r.node = [r.o0.val] } },\n      { s: [CA], b: 1, a: (r) => { r.node = [''] } },\n      { s: [LN], b: 1, a: (r) => { r.node = [] } },\n      { s: [ZZ], b: 1, a: (r) => { r.node = [] } },\n    ])\n    .close([\n      { s: [CA], r: 'rowcont' },\n      { s: [LN], b: 1 },\n      { s: [ZZ], b: 1 },\n    ])\n  })\n\n  // Row continuation: push each subsequent cell onto the shared row array.\n  j.rule('rowcont', (rs) => {\n    rs.open([\n      { s: [VAL], a: (r) => { r.node.push(r.o0.val) } },\n      { s: [CA], b: 1, a: (r) => { r.node.push('') } },\n      { s: [LN], b: 1, a: (r) => { r.node.push('') } },\n      { s: [ZZ], b: 1, a: (r) => { r.node.push('') } },\n    ])\n    .close([\n      { s: [CA], r: 'rowcont' },\n      { s: [LN], b: 1 },\n      { s: [ZZ], b: 1 },\n    ])\n  })\n\n  return j\n}\n\ndescribe('csv-grammar', () => {\n  const csv = makeCsv()\n\n  it('empty-input', () => {\n    assert.deepEqual(csv(''), [])\n  })\n\n  it('single-row', () => {\n    assert.deepEqual(csv('a,b,c'), [['a', 'b', 'c']])\n  })\n\n  it('multiple-rows', () => {\n    assert.deepEqual(csv('a,b\\nc,d'), [['a', 'b'], ['c', 'd']])\n  })\n\n  it('trailing-newline', () => {\n    assert.deepEqual(csv('a,b,c\\n'), [['a', 'b', 'c']])\n  })\n\n  it('blank-lines-skipped', () => {\n    assert.deepEqual(csv('a,b\\n\\nc,d\\n'), [['a', 'b'], ['c', 'd']])\n  })\n\n  it('numbers-are-parsed', () => {\n    assert.deepEqual(csv('1,2,3'), [[1, 2, 3]])\n  })\n\n  it('quoted-strings', () => {\n    assert.deepEqual(csv('\"hello\",\"world\"'), [['hello', 'world']])\n  })\n\n  it('mixed-types', () => {\n    assert.deepEqual(csv('a,1,\"x\",true'), [['a', 1, 'x', true]])\n  })\n\n  it('empty-leading-field', () => {\n    assert.deepEqual(csv(',a,b'), [['', 'a', 'b']])\n  })\n\n  it('empty-middle-field', () => {\n    assert.deepEqual(csv('a,,b'), [['a', '', 'b']])\n  })\n\n  it('empty-trailing-field', () => {\n    assert.deepEqual(csv('a,b,'), [['a', 'b', '']])\n  })\n\n  it('single-cell-row', () => {\n    assert.deepEqual(csv('x\\ny'), [['x'], ['y']])\n  })\n\n  it('keywords-recognised', () => {\n    assert.deepEqual(csv('true,false,null'), [[true, false, null]])\n  })\n})\n"
  },
  {
    "path": "test/custom.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, JsonicError, makeRule, makeFixedMatcher } = require('..')\nconst { Debug } = require('../dist/debug')\n\nlet j = Jsonic\nlet { keys } = j.util\n\ndescribe('custom', () => {\n  it('fixed-tokens', () => {\n    let j = Jsonic.make({\n      fixed: {\n        token: {\n          '#NOT': '~',\n          '#IMPLIES': '=>',\n          '#DEFINE': ':-',\n          '#MARK': '##',\n          '#TRIPLE': '///',\n        },\n      },\n    })\n\n    let NOT = j.token['#NOT']\n    let IMPLIES = j.token['#IMPLIES']\n    let DEFINE = j.token['#DEFINE']\n    let MARK = j.token['#MARK']\n    let TRIPLE = j.token['#TRIPLE']\n\n    j.rule('val', (rs) => {\n      rs.open([\n        { s: [NOT], a: (r) => (r.node = '<not>') },\n        { s: [IMPLIES], a: (r) => (r.node = '<implies>') },\n        { s: [DEFINE], a: (r) => (r.node = '<define>') },\n        { s: [MARK], a: (r) => (r.node = '<mark>') },\n        { s: [TRIPLE], a: (r) => (r.node = '<triple>') },\n      ])\n    })\n\n    let out = j('a:~,b:1,c:~,d:=>,e::-,f:##,g:///,h:a,i:# foo')\n\n    assert.deepEqual(out, {\n      a: '<not>',\n      b: 1,\n      c: '<not>',\n      d: '<implies>',\n      e: '<define>',\n      f: '<mark>',\n      g: '<triple>',\n      h: 'a',\n      i: null, // implicit null\n    })\n  })\n\n  it('tokenset-idenkey', () => {\n    let days = {\n      monday: 'mon',\n      tuesday: 'tue',\n    }\n\n    let j = Jsonic.make()\n\n    j.options({\n      match: {\n        token: {\n          '#DAY': (lex, rule) => {\n            let pnt = lex.pnt\n            let daystr = lex.src.substring(pnt.sI, pnt.sI + 11).toLowerCase()\n            for (let day in days) {\n              if (daystr.startsWith(day)) {\n                let daylen = day.length\n                pnt.sI += daylen\n                pnt.cI += daylen\n\n                let tkn = lex.token(\n                  '#VL',\n                  days[day],\n                  lex.src.substring(pnt.sI, pnt.sI + daylen),\n                  pnt,\n                )\n\n                return tkn\n              }\n            }\n            return undefined\n          },\n          '#ID': /^[a-zA-Z_][a-zA-Z_0-9]*/,\n        },\n      },\n      tokenSet: {\n        // '#ID' is created by tokenize automatically\n        KEY: ['#ST', '#ID', null, null],\n        VAL: [, , , , '#ID'],\n      },\n    })\n\n    j.use(Debug)\n\n    let DAY = j.token('#DAY')\n\n    j.rule('val', (rs) => {\n      rs.open([{ s: [DAY] }])\n      return rs\n    })\n\n    assert.deepEqual(j('a:1', { xlog: -1 }), { a: 1 })\n    assert.deepEqual(j('a:x', { xlog: -1 }), { a: 'x' })\n    assert.throws(() => j('a*:1'), /unexpected/)\n\n    assert.deepEqual(j('a:monday', { xlog: -1 }), { a: 'mon' })\n\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n    assert.deepEqual(Jsonic('a*:1'), { 'a*': 1 })\n    assert.deepEqual(Jsonic('b:monday'), { b: 'monday' })\n  })\n\n  it('string-replace', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j0 = Jsonic.make({\n      string: {\n        replace: {\n          A: 'B',\n          D: '',\n        },\n      },\n    })\n\n    assert.deepEqual(j0('\"aAc\"'), 'aBc')\n    assert.deepEqual(j0('\"aAcDe\"'), 'aBce')\n    assert.throws(() => j0('x:\\n \"Ac\\n\"'), /unprintable.*2:6/s)\n\n    let j1 = Jsonic.make({\n      string: {\n        replace: {\n          A: 'B',\n          '\\n': 'X',\n        },\n      },\n    })\n\n    assert.deepEqual(j1('\"aAc\\n\"'), 'aBcX')\n    assert.throws(() => j1('x:\\n \"ac\\n\\r\"'), /unprintable.*2:7/s)\n\n    let j2 = Jsonic.make({\n      string: {\n        replace: {\n          A: 'B',\n          '\\n': '',\n        },\n      },\n    })\n\n    assert.deepEqual(j2('\"aAc\\n\"'), 'aBc')\n    assert.throws(() => j2('x:\\n \"ac\\n\\r\"'), /unprintable.*2:7/s)\n  })\n\n  it('parser-empty-clean', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j = Jsonic.empty()\n    assert.deepEqual(keys({ ...j.token }).length, 0)\n    assert.deepEqual(keys({ ...j.fixed }).length, 0)\n    assert.deepEqual(Object.keys(j.rule()), [])\n    assert.deepEqual(j('a:1'), undefined)\n  })\n\n  it('parser-empty-fixed', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j = Jsonic.empty({\n      fixed: {\n        lex: true,\n        token: {\n          '#T0': 't0',\n        },\n      },\n      rule: {\n        start: 'r0',\n      },\n      lex: {\n        match: {\n          fixed: { order: 0, make: makeFixedMatcher },\n          match: false,\n          space: false,\n          line: false,\n          string: false,\n          comment: false,\n          number: false,\n          text: false,\n        },\n      },\n    }).rule('r0', (rs) => {\n      rs.open({ s: [rs.tin('#T0')] }).bc((r) => (r.node = '~T0~'))\n    })\n\n    assert.deepEqual(j('t0', { xlog: -1 }), '~T0~')\n  })\n\n  it('parser-handler-actives', () => {\n    let b = ''\n    let j = make_norules({ rule: { start: 'top' } })\n    let cfg = j.internal().config\n\n    let AA = j.token.AA\n    j.rule('top', (rs) => {\n      rs.open([\n        {\n          s: [AA, AA],\n          h: (rule, ctx, alt) => {\n            // No effect: rule.bo - bo already called at this point.\n            // rule.bo = false\n            rule.ao = false\n            rule.bc = false\n            rule.ac = false\n            rule.node = 1111\n            return alt\n          },\n        },\n      ])\n        .close([{ s: [AA, AA] }])\n        .bo(() => (b += 'bo;'))\n        .ao(() => (b += 'ao;'))\n        .bc(() => (b += 'bc;'))\n        .ac(() => (b += 'ac;'))\n    })\n\n    assert.deepEqual(j('a'), 1111)\n    assert.deepEqual(b, 'bo;') // m: is too late to avoid bo\n  })\n\n  it('parser-action-errors', () => {\n    let b = ''\n    let j = make_norules({ rule: { start: 'top' } })\n\n    let AA = j.token.AA\n\n    let rsdef = (rs) =>\n      rs\n        .clear()\n        .open([{ s: [AA, AA] }])\n        .close([{ s: [AA, AA] }])\n\n    j.rule('top', (rs) =>\n      rsdef(rs).bo((rule, ctx) => ctx.t0.bad('foo', { bar: 'BO' })),\n    )\n    assert.throws(() => j('a'), /foo.*BO/s)\n\n    j.rule('top', (rs) =>\n      rsdef(rs).ao((rule, ctx) => ctx.t0.bad('foo', { bar: 'AO' })),\n    )\n    assert.throws(() => j('a'), /foo.*AO/s)\n\n    j.rule('top', (rs) =>\n      rsdef(rs).bc((rule, ctx) => ctx.t0.bad('foo', { bar: 'BC' })),\n    )\n    assert.throws(() => j('a'), /foo.*BC/s)\n\n    j.rule('top', (rs) =>\n      rsdef(rs).ac((rule, ctx) => ctx.t0.bad('foo', { bar: 'AC' })),\n    )\n    assert.throws(() => j('a'), /foo.*AC/s)\n  })\n\n  it('parser-before-after-state', () => {\n    let j = make_norules({ rule: { start: 'top' } })\n    let AA = j.token.AA\n\n    let rsdef = (rs) =>\n      rs\n        .clear()\n        .open([{ s: [AA, AA] }])\n        .close([{ s: [AA, AA] }])\n\n    j.rule('top', (rs) => rsdef(rs).bo((rule) => (rule.node = 'BO')))\n    assert.deepEqual(j('a'), 'BO')\n\n    j.rule('top', (rs) => rsdef(rs).ao((rule) => (rule.node = 'AO')))\n    assert.deepEqual(j('a'), 'AO')\n\n    j.rule('top', (rs) => rsdef(rs).bc((rule) => (rule.node = 'BC')))\n    assert.deepEqual(j('a'), 'BC')\n\n    j.rule('top', (rs) => rsdef(rs).ac((rule) => (rule.node = 'AC')))\n    assert.deepEqual(j('a'), 'AC')\n  })\n\n  it('parser-empty-seq', () => {\n    let j = make_norules({ rule: { start: 'top' } })\n\n    let AA = j.token.AA\n\n    let rsdef = (rs) => rs.clear().open([{ s: [AA] }])\n    j.rule('top', (rs) => rsdef(rs).bo((rule) => (rule.node = 4444)))\n\n    assert.deepEqual(j('a'), 4444)\n  })\n\n  it('parser-alt-ops', () => {\n    let j = make_norules({\n      fixed: {\n        token: {\n          Ta: 'a',\n          Tb: 'b',\n          Tc: 'c',\n          Td: 'd',\n          Te: 'e',\n        },\n      },\n\n      rule: { start: 'top' },\n    })\n\n    let Ta = j.token.Ta\n    let Tb = j.token.Tb\n    let Tc = j.token.Tc\n    let Td = j.token.Td\n    let Te = j.token.Te\n\n    let ZZ = j.token.ZZ\n\n    j.use((j) => {\n      j.rule('top', (rs) =>\n        rs\n          .bo((r) => (r.node = r.node || { o: '' }))\n          .open([\n            { s: [ZZ], g: 'gz' },\n            { s: [Ta], r: 'top', a: (r) => (r.node.o += 'A'), g: 'ga' },\n          ]),\n      )\n    })\n\n    assert.deepEqual(j('a', { xlog: -1 }), { o: 'A' })\n    assert.deepEqual(j.rule('top').def.open.map((alt) => alt.g[0]), ['gz', 'ga'])\n\n    // Prepend by default\n    j.use((j) => {\n      j.rule('top', (rs) =>\n        rs.open([{ s: [Tb], r: 'top', a: (r) => (r.node.o += 'B'), g: 'gb' }]),\n      )\n    })\n\n    assert.deepEqual(j('ab'), { o: 'AB' })\n    assert.deepEqual(j.rule('top').def.open.map((alt) => alt.g[0]), [\n      'gb',\n      'gz',\n      'ga',\n    ])\n\n    // Append flag\n    j.use((j) => {\n      j.rule('top', (rs) =>\n        rs.open([{ s: [Tc], r: 'top', a: (r) => (r.node.o += 'C'), g: 'gc' }], {\n          append: true,\n        }),\n      )\n    })\n\n    assert.deepEqual(j('abc'), { o: 'ABC' })\n    assert.deepEqual(j.rule('top').def.open.map((alt) => alt.g[0]), [\n      'gb',\n      'gz',\n      'ga',\n      'gc',\n    ])\n\n    // Delete op\n    j.use((j) => {\n      j.rule('top', (rs) =>\n        rs.open([{ s: [Td], r: 'top', a: (r) => (r.node.o += 'D'), g: 'gd' }], {\n          append: true,\n          delete: [2],\n        }),\n      )\n    })\n\n    assert.deepEqual(j('bcd'), { o: 'BCD' })\n    assert.deepEqual(j.rule('top').def.open.map((alt) => alt.g[0]), [\n      'gb',\n      'gz',\n      'gc',\n      'gd',\n    ])\n\n    // Move ops\n    j.use((j) => {\n      j.rule('top', (rs) =>\n        rs.open([{ s: [Te], r: 'top', a: (r) => (r.node.o += 'E'), g: 'ge' }], {\n          append: true,\n          move: [2, -1, 0, 1],\n        }),\n      )\n    })\n\n    assert.deepEqual(j('bcde'), { o: 'BCDE' })\n    assert.deepEqual(j.rule('top').def.open.map((alt) => alt.g[0]), [\n      'gz',\n      'gb', // 0 -> 1\n      'gd',\n      'ge',\n      'gc', // 2 -> -1\n    ])\n\n    // Delete ops\n    j.use((j) => {\n      j.rule('top', (rs) => rs.open([], { delete: [1, 3] }))\n    })\n\n    assert.deepEqual(j('cd'), { o: 'CD' })\n    assert.deepEqual(j.rule('top').def.open.map((alt) => alt.g[0]), [\n      'gz',\n      'gd',\n      'gc',\n    ])\n  })\n\n  it('parser-any-def', () => {\n    let j = make_norules({ rule: { start: 'top' } })\n    let rsdef = (rs) => rs.clear().open([{ s: [AA, TX] }])\n\n    let AA = j.token.AA\n    let TX = j.token.TX\n\n    j.rule('top', (rs) =>\n      rsdef(rs).ac((rule) => (rule.node = rule.o0.val + rule.o1.val)),\n    )\n\n    assert.deepEqual(j('a\\nb'), 'ab')\n    assert.throws(() => j('AAA,'), /unexpected.*AAA/)\n  })\n\n  it('parser-token-error-why', () => {\n    let j = make_norules({ rule: { start: 'top' } })\n\n    let AA = j.token.AA\n\n    j.rule('top', (rs) =>\n      rs\n        .clear()\n        .open([{ s: [AA] }])\n        .close([{ s: [AA] }])\n        .ac((rule, ctx) => ctx.t0.bad('foo', { bar: 'AAA' })),\n    )\n\n    assert.throws(() => j('a'), /foo.*AAA/s)\n  })\n\n  it('parser-multi-alts', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j = make_norules({ rule: { start: 'top' } })\n\n    j.options({\n      fixed: {\n        token: {\n          Ta: 'a',\n          Tb: 'b',\n          Tc: 'c',\n        },\n      },\n    })\n\n    let Ta = j.token.Ta\n    let Tb = j.token.Tb\n    let Tc = j.token.Tc\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, [Tb, Tc]] }])\n        .ac((r) => (r.node = (r.o0.src + r.o1.src).toUpperCase())),\n    )\n\n    assert.deepEqual(j('ab'), 'AB')\n    assert.deepEqual(j('ac'), 'AC')\n    assert.throws(() => j('ad'), /unexpected.*d/)\n  })\n\n  it('parser-value', () => {\n    function Car() {\n      this.m = true\n    }\n    let c0 = new Car()\n    assert.deepEqual(c0 instanceof Car, true)\n\n    let o0 = { x: 1 }\n    let f1 = () => 'F1'\n\n    let j = Jsonic.make({\n      value: {\n        def: {\n          foo: { val: 'FOO' },\n          bar: { val: 'BAR' },\n          zed: { val: 123 },\n          qaz: { val: false },\n          obj: { val: o0 },\n\n          car: { val: c0 },\n\n          // Functions build values dynamically\n          fun: { val: () => 'f0' },\n          high: { val: () => f1 },\n          ferry: { val: () => new Car() },\n        },\n      },\n    })\n\n    assert.deepEqual(j('foo'), 'FOO')\n    assert.deepEqual(j('bar'), 'BAR')\n    assert.deepEqual(j('zed'), 123)\n    assert.deepEqual(j('qaz'), false)\n\n    // Options get copied, so `obj` should remain {x:1}\n    o0.x = 2\n    assert.deepEqual(j('obj'), { x: 1 })\n\n    assert.deepEqual(j('car'), { m: true })\n    assert.deepEqual(j('car') instanceof Car, true)\n\n    assert.deepEqual(j('fun'), 'f0')\n    assert.deepEqual(j('high'), f1)\n\n    // constructor is protected\n    assert.deepEqual(j('ferry'), { m: true })\n    assert.deepEqual(j('ferry') instanceof Car, true)\n  })\n\n  it('parser-mixed-token', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let cs = [\n      'Q', // generic char\n      '/', // mixed use as comment marker\n    ]\n\n    for (let c of cs) {\n      let j = Jsonic.make()\n      j.options({\n        fixed: {\n          token: {\n            '#T/': c,\n          },\n        },\n      })\n\n      let FS = j.token['#T/']\n      let TX = j.token.TX\n\n      j.rule('val', (rs) => {\n        rs.open([\n          {\n            s: [FS, TX],\n            a: (r) => (r.o0.val = '@' + r.o1.val),\n          },\n        ])\n      })\n\n      j.rule('elem', (rs) => {\n        rs.close([\n          {\n            s: [FS, TX],\n            r: () => 'elem',\n            b: 2,\n          },\n        ])\n      })\n\n      assert.deepEqual(j('[' + c + 'x' + c + 'y]'), ['@x', '@y'])\n    }\n  })\n\n  it('merge', () => {\n    // verify standard merges\n    assert.deepEqual(Jsonic('a:1,a:2'), { a: 2 })\n    assert.deepEqual(Jsonic('a:1,a:2,a:3'), { a: 3 })\n    assert.deepEqual(Jsonic('a:{x:1},a:{y:2}'), { a: { x: 1, y: 2 } })\n    assert.deepEqual(Jsonic('a:{x:1},a:{y:2},a:{z:3}'), {\n      a: { x: 1, y: 2, z: 3 },\n    })\n\n    let b = ''\n    let j = Jsonic.make({\n      map: {\n        merge: (prev, curr) => {\n          return prev + curr\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:1,a:2'), { a: 3 })\n    assert.deepEqual(j('a:1,a:2,a:3'), { a: 6 })\n  })\n\n  it('parser-condition-depth', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j = make_norules({\n      fixed: { token: { '#F': 'f', '#B': 'b' } },\n      rule: { start: 'top' },\n    })\n\n    let FT = j.token.F\n    let BT = j.token.B\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ p: 'foo', c: (r) => r.d <= 0 }])\n        .bo((r) => (r.node = { o: 'T' })),\n    )\n\n    j.rule('foo', (rs) =>\n      rs\n        .open([\n          {\n            s: [FT],\n            p: 'bar',\n            c: (r) => r.d <= 1,\n          },\n        ])\n        .ao((r) => (r.node.o += 'F')),\n    )\n\n    j.rule('bar', (rs) =>\n      rs\n        .open([\n          {\n            s: [BT],\n            c: (r) => r.d <= 2,\n          },\n        ])\n        .ao((r) => (r.node.o += 'B')),\n    )\n\n    assert.deepEqual(j('fb'), { o: 'TFB' })\n\n    j.rule('bar', (rs) =>\n      rs\n        .clear()\n        .open([{ s: [BT], c: (r) => r.d <= 0 }])\n        .ao((r) => (r.node.o += 'B')),\n    )\n\n    assert.throws(() => j('fb'), /unexpected/)\n  })\n\n  it('parser-condition-counter', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j = make_norules({\n      fixed: { token: { '#F': 'f', '#B': 'b' } },\n      rule: { start: 'top' },\n    })\n    let cfg = j.internal().config\n\n    let FT = j.token.F\n    let BT = j.token.B\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ p: 'foo', n: { x: 1, y: 2 } }]) // incr x=1,y=2\n        .bo((r) => (r.node = { o: 'T' })),\n    )\n\n    j.rule('foo', (rs) =>\n      rs\n        .open([\n          {\n            s: [FT],\n            p: 'bar',\n            c: (r) => r.lte('x', 1) && r.lte('y', 2),\n            n: { y: 0 },\n          },\n        ]) // (x <= 1, y <= 2) -> pass\n        .ao((r) => (r.node.o += 'F')),\n    )\n\n    j.rule('bar', (rs) =>\n      rs\n        .open([\n          {\n            s: [BT],\n            c: (r) => r.lte('x', 1) && r.lte('y', 0),\n          },\n        ]) // (x <= 1, y <= 0) -> pass\n        .ao((r) => (r.node.o += 'B')),\n    )\n\n    assert.deepEqual(j('fb'), { o: 'TFB' })\n\n    j.rule('bar', (rs) =>\n      rs\n        .clear()\n        .open([\n          {\n            s: [BT],\n            c: (r) => r.lte('x'),\n          },\n        ]) // !(x <= 0) -> fail\n        .ao((r) => (r.node.o += 'B')),\n    )\n\n    assert.throws(() => j('fb'), /unexpected/)\n  })\n\n  it('parser-keep-propagates', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n\n    let j = make_norules({\n      fixed: { token: { '#F': 'f', '#B': 'b', '#Z': 'z' } },\n      rule: { start: 'top' },\n    })\n\n    let FT = j.token.F\n    let BT = j.token.B\n    let ZT = j.token.Z\n\n    j.rule('top', (rs) => {\n      rs.open([{ p: 'foo', k: { color: 'red' }, u: { planet: 'mars' } }])\n        .bo((r) => (r.node = { out: [] }))\n        .ao((r) => r.node.out.push(`AO-TOP<${r.k.color},${r.u.planet}>`))\n        .bc((r) => r.node.out.push(`BC-TOP<${r.k.color},${r.u.planet}>`))\n    })\n\n      .rule('foo', (rs) => {\n        rs.open([{ s: [FT], p: 'bar' }])\n          .ao((r) => r.node.out.push(`AO-FOO<${r.k.color},${r.u.planet}>`))\n          .bc((r) => r.node.out.push(`BC-FOO<${r.k.color},${r.u.planet}>`))\n      })\n\n      .rule('bar', (rs) => {\n        rs.open([{ s: [BT], p: 'zed', u: { planet: 'earth' } }])\n          .ao((r) => r.node.out.push(`AO-BAR<${r.k.color},${r.u.planet}>`))\n          .bc((r) => r.node.out.push(`BC-BAR<${r.k.color},${r.u.planet}>`))\n      })\n\n      .rule('zed', (rs) => {\n        rs.open([{ s: [ZT], k: { color: 'green' } }])\n          .ao((r) => r.node.out.push(`AO-ZED<${r.k.color},${r.u.planet}>`))\n          .bc((r) => r.node.out.push(`BC-ZED<${r.k.color},${r.u.planet}>`))\n      })\n\n    assert.deepEqual(j('fbz'), {\n      out: [\n        'AO-TOP<red,mars>',\n        'AO-FOO<red,undefined>',\n        'AO-BAR<red,earth>',\n        'AO-ZED<green,undefined>',\n        'BC-ZED<green,undefined>',\n        'BC-BAR<red,earth>',\n        'BC-FOO<red,undefined>',\n        'BC-TOP<red,mars>',\n      ],\n    })\n  })\n})\n\nfunction make_norules(opts) {\n  let j = Jsonic.make(opts)\n  let rns = j.rule()\n  Object.keys(rns).map((rn) => j.rule(rn, null))\n  return j\n}\n"
  },
  {
    "path": "test/debug.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, JsonicError } = require('..')\nconst { Debug } = require('../dist/debug')\n\ndescribe('debug', function () {\n  it('plugin', () => {\n    let jd = Jsonic.make().use(Debug)\n    assert.ok(jd.debug.describe() != null)\n  })\n})\n"
  },
  {
    "path": "test/directive-grammar.test.js",
    "content": "/* Copyright (c) 2013-2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Minimal directive-style plugin defined inline for the test. A directive\n// binds a fixed OPEN token to a named rule that reads the following val\n// and replaces it with the result of an action callback. Mirrors the\n// essential shape of @jsonic/directive — token + rule + transform —\n// without the full plugin's close-token, rule-filtering, or error-plumbing\n// surface area. Kept in-test so the core repo carries no plugin dependency.\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\nfunction defineDirective(j, { name, open, action }) {\n  const tokenName = '#OD_' + name\n  j.options({ fixed: { token: { [tokenName]: open } } })\n  const OPEN = j.token(tokenName)\n\n  j.rule(name, (rs) => {\n    rs.bo((r) => { r.node = undefined })\n      .open([{ p: 'val' }])\n      .bc((r) => { r.node = action(r.child.node) })\n  })\n\n  j.rule('val', (rs) => {\n    rs.open({ s: [OPEN], p: name })\n  })\n}\n\nfunction makeJ() {\n  const j = Jsonic.make()\n  defineDirective(j, {\n    name: 'upper',\n    open: '@up',\n    action: (val) => String(val).toUpperCase(),\n  })\n  defineDirective(j, {\n    name: 'wrap',\n    open: '@wrap',\n    action: (val) => ({ wrapped: val }),\n  })\n  return j\n}\n\ndescribe('directive-grammar', () => {\n  const j = makeJ()\n\n  it('upper-string', () => {\n    assert.equal(j('@up \"hello\"'), 'HELLO')\n  })\n\n  it('upper-bare', () => {\n    assert.equal(j('@up hello'), 'HELLO')\n  })\n\n  it('upper-number', () => {\n    assert.equal(j('@up 42'), '42')\n  })\n\n  it('wrap-number', () => {\n    assert.deepEqual(j('@wrap 42'), { wrapped: 42 })\n  })\n\n  it('wrap-keyword', () => {\n    assert.deepEqual(j('@wrap true'), { wrapped: true })\n  })\n\n  it('directive-in-list', () => {\n    assert.deepEqual(j('[1, @up \"x\", 2]'), [1, 'X', 2])\n  })\n\n  it('directive-in-map', () => {\n    assert.deepEqual(j('{a: @up \"v\", b: @wrap 3}'), { a: 'V', b: { wrapped: 3 } })\n  })\n\n  it('nested-directives', () => {\n    assert.deepEqual(j('@wrap @up \"hi\"'), { wrapped: 'HI' })\n  })\n\n  it('directive-wrapping-list', () => {\n    assert.deepEqual(j('@wrap [1, @up \"x\"]'), { wrapped: [1, 'X'] })\n  })\n\n  it('directive-wrapping-map', () => {\n    assert.deepEqual(j('@wrap {k: @up \"v\"}'), { wrapped: { k: 'V' } })\n  })\n})\n"
  },
  {
    "path": "test/dive.js",
    "content": "const { Jsonic, util } = require('..')\nconst { Debug } = require('../dist/debug')\n\nlet j = Jsonic.make()\n  .use(function dive(jsonic) {\n    jsonic.options({\n      fixed: {\n        token: {\n          // TODO: disambig by moving FixedMatcher later\n          '#DOT': '.',\n        },\n      },\n    })\n\n    let { DOT, CL } = jsonic.token\n    let { KEY } = jsonic.tokenSet\n\n    jsonic\n      .rule('pair', (rs) => {\n        rs.open([{ s: [KEY, DOT], b: 2, p: 'dive' }])\n      })\n      .rule('dive', (rs) => {\n        rs.open([\n          {\n            s: [KEY, DOT],\n            p: 'dive',\n            a: (r) => {\n              r.parent.node[r.o0.val] = r.node = {}\n            },\n          },\n          {\n            s: [KEY, CL],\n            p: 'val',\n            u: { dive_end: true },\n          },\n        ]).bc((r) => {\n          if (r.u.dive_end) {\n            r.node[r.o0.val] = r.child.node\n          }\n        })\n      })\n  })\n  .use(Debug, { trace: true })\n\nconsole.log(j.debug.describe())\n\nconsole.log(\n  j(\n    `\n{\n  a: 1\n  b.c: 2\n  d: 3\n  e: 4.5\n  f: 127.0.0.1\n}\n`,\n    { log: -1 },\n  ),\n)\n"
  },
  {
    "path": "test/doc.test.js",
    "content": "/* Copyright (c) 2021 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\n// const Util = require('util')\n\n// let Lab = require('@hapi/lab')\n// Lab = null != Lab.script ? Lab : require('hapi-lab-shim')\n\n// \n// const lab = (exports.lab = Lab.script())\n// const describe = lab.describe\n// const it = lab.it\n// \nconst {\n  Jsonic,\n  Parser,\n  JsonicError,\n  OPEN,\n  CLOSE,\n  BEFORE,\n  AFTER,\n  make,\n} = require('..')\n\ndescribe('doc', function () {\n  it('method-jsonic', () => {\n    let earth = Jsonic('name: Terra, moons: [{name: Luna}]')\n    assert.deepEqual(earth, {\n      name: 'Terra',\n      moons: [\n        {\n          name: 'Luna',\n        },\n      ],\n    })\n  })\n\n  // TODO: test without actually writing to STDOUT\n  // it('method-jsonic-log', () => {\n  //   let one = Jsonic('1', {log:-1}) // one === 1\n  //   expect(one).equal(1)\n  // })\n\n  it('method-make', () => {\n    let array_of_numbers = Jsonic('1,2,3')\n    // array_of_numbers === [1, 2, 3]\n    assert.deepEqual(array_of_numbers, [1, 2, 3])\n\n    let no_numbers_please = Jsonic.make({ number: { lex: false } })\n    let array_of_strings = no_numbers_please('1,2,3')\n    // array_of_strings === ['1', '2', '3']\n    assert.deepEqual(array_of_strings, ['1', '2', '3'])\n  })\n\n  it('method-make-inherit', () => {\n    let no_numbers_please = Jsonic.make({ number: { lex: false } })\n    let out = no_numbers_please('1,2,3') // === ['1', '2', '3'] as before\n    assert.deepEqual(out, ['1', '2', '3'])\n\n    let pipe_separated = no_numbers_please.make({\n      fixed: { token: { '#CA': '|' } },\n    })\n    out = pipe_separated('1|2|3') // === ['1', '2', '3'], but:\n    assert.deepEqual(out, ['1', '2', '3'])\n    out = pipe_separated('1,2,3') // === '1,2,3' !!!\n    assert.deepEqual(out, '1,2,3')\n  })\n\n  it('method-options', () => {\n    let jsonic = Jsonic.make()\n\n    let options = jsonic.options()\n    assert.deepEqual(options.comment.lex, true)\n    assert.deepEqual(jsonic.options.comment.lex, true)\n\n    let no_comment = Jsonic.make()\n    no_comment.options({ comment: { lex: false } })\n    assert.deepEqual(no_comment.options().comment.lex, false)\n    assert.deepEqual(no_comment.options.comment.lex, false)\n\n    // Returns {\"a\": 1, \"#b\": 2}\n    let out = no_comment(`\n   a: 1\n   #b: 2\n `)\n    assert.deepEqual(out, { a: 1, '#b': 2 })\n\n    // Whereas this returns only {\"a\": 1} as # starts a one line comment\n    out = Jsonic(`\n  a: 1\n  #b: 2\n`)\n    assert.deepEqual(out, { a: 1 })\n  })\n\n  it('method-use', () => {\n    let jsonic = Jsonic.make().use(function piper(jsonic) {\n      jsonic.options({ fixed: { token: { '#CA': '~' } } })\n    })\n\n    assert.deepEqual(jsonic.options.fixed.token['#CA'], '~')\n    assert.deepEqual(jsonic.internal().config.fixed.token['~'], 17)\n\n    let out = jsonic('a~b~c') // === ['a', 'b', 'c']\n    assert.deepEqual(out, ['a', 'b', 'c'])\n  })\n\n  it('method-use-options', () => {\n    function sepper(jsonic) {\n      let sep = jsonic.options.plugin.sepper.sep\n      jsonic.options({ fixed: { token: { '#CA': sep } } })\n    }\n    let jsonic = Jsonic.make().use(sepper, { sep: ';' })\n    let out = jsonic('a;b;c') // === ['a', 'b', 'c']\n    assert.deepEqual(out, ['a', 'b', 'c'])\n  })\n\n  it('method-use-chaining', () => {\n    function foo(jsonic) {\n      jsonic.foo = function () {\n        return 1\n      }\n    }\n    function bar(jsonic) {\n      jsonic.bar = function () {\n        return this.foo() * 2\n      }\n    }\n    let jsonic = Jsonic.make().use(foo).use(bar)\n    assert.deepEqual(jsonic.foo(), 1)\n    assert.deepEqual(jsonic.bar(), 2)\n  })\n\n  it('method-rule', () => {\n    let concat = Jsonic.make()\n    assert.deepEqual(Object.keys(concat.rule()), [\n      'val',\n      'map',\n      'list',\n      'pair',\n      'elem',\n    ])\n\n    assert.deepEqual(concat.rule('val').name, 'val')\n\n    let ST = concat.token.ST\n    concat.rule('val', (rulespec) => {\n      //rulespec.def.open.unshift({\n      rulespec.open([\n        {\n          s: [ST, ST],\n          a: (rule, ctx) => (rule.node = rule.o0.val + rule.o1.val),\n        },\n      ])\n    })\n\n    assert.deepEqual(concat('\"a\" \"b\"', { xlog: -1 }), 'ab')\n    assert.deepEqual(concat('[\"a\" \"b\"]', { xlog: -1 }), ['ab'])\n    assert.deepEqual(concat('{x:\"a\" \"b\",y:1}', { xlog: -1 }), { x: 'ab', y: 1 })\n\n    concat.options({\n      fixed: { token: { '#HH': '%' } },\n    })\n\n    let HH = concat.token.HH\n\n    concat.rule('hundred', (rs) => rs.ao((rule) => (rule.node = 100)))\n\n    concat.rule('val', (rulespec) => {\n      rulespec.open([{ s: [HH], p: 'hundred' }])\n    })\n\n    assert.deepEqual(concat('{x:1, y:%}', { xlog: -1 }), { x: 1, y: 100 })\n  })\n\n  /* METHOD REMOVED FROM API\n  it('method-lex', () => {\n    let tens = Jsonic.make()\n\n    tens.lex((cfg, opts) => (lex, rule) => {\n      let pnt = lex.pnt\n      let marks = lex.src.substring(pnt.sI).match(/^%+/)\n      if (marks) {\n        let len = marks[0].length\n        let tkn = lex.token('#VL', 10 * marks[0].length, marks, lex.pnt)\n        pnt.sI += len\n        pnt.cI += len\n        return tkn\n      }\n    })\n\n    assert.deepEqual(tens('a:1,b:%%,c:[%%%%]'), { a: 1, b: 20, c: [40] })\n  })\n  */\n\n  it('method-token', () => {\n    let jsonic = Jsonic.make()\n    jsonic.token.ST // === 11, String token identification number\n    jsonic.token(11) // === '#ST', String token name\n    jsonic.token('#ST') // === 11, String token name\n  })\n\n  it('property-id', () => {\n    assert.deepEqual(null != Jsonic.id.match(/Jsonic.*/), true)\n    assert.deepEqual(null != Jsonic.make({ tag: 'foo' }).id.match(/Jsonic.*foo/), true)\n  })\n})\n"
  },
  {
    "path": "test/error.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, JsonicError } = require('..')\n\nconst je = (s) => () => Jsonic(s)\n\nconst JS = (x) => JSON.stringify(x)\n\ndescribe('error', function () {\n  it('error-message', () => {\n    let src0 = '\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n   \"\\\\u0000\"'\n    try {\n      Jsonic(src0)\n    } catch (e) {\n      assert.deepEqual(e.message, \n        `\\u001b[91m[jsonic/invalid_unicode]:\\u001b[0m invalid unicode escape: \"\\\\\\\\u0000\"\n  \\u001b[34m-->\\u001b[0m <no-file>:10:6\n\\u001b[34m   8 | \\u001b[0m\n\\u001b[34m   9 | \\u001b[0m\n\\u001b[34m  10 | \\u001b[0m   \"\\\\u0000\"\n             \\u001b[91m^^^^^^ invalid unicode escape: \"\\\\\\\\u0000\"\\u001b[0m\n\\u001b[34m  11 | \\u001b[0m\n\\u001b[34m  12 | \\u001b[0m\n\n  The escape sequence \"\\\\\\\\u0000\" does not encode a valid unicode code point\n  number. You may need to validate your string data manually using test\n  code to see how Java script will interpret it. Also consider that your\n  data may have become corrupted, or the escape sequence has not been\n  generated correctly.\n\n  \\u001b[2mhttps://jsonic.senecajs.org\\u001b[0m\n  \\u001b[2m--internal: rule=val~open; token=#BD~foo; plugins=--\\u001b[0m'\n`,\n      )\n    }\n  })\n\n  it('plugin-errors', () => {\n    let k = Jsonic.make().use(function foo(jsonic) {\n      jsonic.options({\n        tag: 'zed',\n        error: {\n          foo: 'foo: {src}!',\n        },\n        hint: {\n          foo: 'Foo hint.',\n        },\n        lex: {\n          match: {\n            foo: {\n              order: 9e5,\n              make: () => (lex) => {\n                if (lex.src.substring(lex.pnt.sI).startsWith('FOO')) {\n                  return lex.bad('foo', lex.pnt.sI, lex.pnt.sI + 4)\n                }\n              },\n            },\n          },\n        },\n      })\n      // jsonic.lex(() => (lex) => {\n      //   if (lex.src.substring(lex.pnt.sI).startsWith('FOO')) {\n      //     return lex.bad('foo', lex.pnt.sI, lex.pnt.sI + 4)\n      //   }\n      // })\n    })\n\n    let src0 = 'a:1,\\nb:FOO'\n\n    /*\n    try {\n      k(src0)\n    }\n    catch(e) {\n      console.log(e)\n    } \n    */\n\n    try {\n      k(src0, { xlog: -1 })\n    } catch (e) {\n      assert.deepEqual(e.message, \n        '\\u001b[91m[jsonic/foo]:\\u001b[0m foo: FOO!\\n' +\n          '  \\u001b[34m-->\\u001b[0m <no-file>:2:3\\n' +\n          '\\u001b[34m  1 | \\u001b[0ma:1,\\n' +\n          '\\u001b[34m  2 | \\u001b[0mb:FOO\\n' +\n          '        \\u001b[34m^^^ foo: FOO!\\u001b[0m\\n' +\n          '\\u001b[34m  3 | \\u001b[0m\\n' +\n          '\\u001b[34m  4 | \\u001b[0m\\n' +\n          '\\n' +\n          '  Foo hint.\\n' +\n          '\\n' +\n          '  \\u001b[2mhttps://jsonic.senecajs.org\\u001b[0m\\n' +\n          '  \\u001b[2m--internal: tag=zed; rule=val~o; token=#BD~foo;' +\n          ' plugins=foo--\\u001b[0m',\n      )\n    }\n\n    assert.throws(() => k('a:1,\\nb:FOO'), /foo/)\n  })\n\n  it('lex-unicode', () => {\n    let src0 = '\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n   \"\\\\uQQQQ\"'\n    //je(src0)()\n    assert.throws(je(src0), /invalid_unicode/)\n\n    let src1 = '\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n   \"\\\\u{QQQQQQ}\"'\n    //je(src1)()\n    assert.throws(je(src0), /invalid_unicode/)\n  })\n\n  it('lex-ascii', () => {\n    let src0 = '\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n   \"\\\\x!!\"'\n    // je(src0)()\n    assert.throws(je(src0), /invalid_ascii/)\n  })\n\n  it('lex-unprintable', () => {\n    let src0 = '\"\\x00\"'\n    assert.throws(je(src0), /unprintable/)\n  })\n\n  it('lex-unterminated', () => {\n    let src0 = '\"a'\n\n    assert.throws(je(src0), /unterminated/)\n\n    /*\n    try {\n      Jsonic(src0)\n    }\n    catch(e) {\n      console.log(e)\n    } \n    */\n  })\n\n  it('parse-unexpected', () => {\n    let src0 = '\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n   }'\n\n    assert.throws(je(src0), /unexpected/)\n\n    /*\n    try {\n      Jsonic(src0)\n    }\n    catch(e) {\n      console.log(e)\n    } \n    */\n  })\n\n  it('error-json-desc', () => {\n    try {\n      Jsonic(']')\n    } catch (e) {\n      // console.log(e)\n      assert.deepEqual(\n        JSON.stringify(e).includes(\n          '{\"code\":\"unexpected\",\"details\":{\"state\":\"open\"},' +\n            '\"meta\":{},\"lineNumber\":1,\"columnNumber\":1',\n        ), true)\n    }\n  })\n\n  it('bad-syntax', () => {\n    // TODO: unexpected end of src needs own case, otherwise incorrect explanation\n    // expect(je('{a')).throw(/incomplete/)\n\n    // TODO: should all be null\n    //expect(Jsonic('a:')).equal({a:undefined})\n    //expect(Jsonic('{a:')).equal({a:undefined})\n    //expect(Jsonic('{a:,b:')).equal({a:undefined,b:undefined})\n    //expect(Jsonic('a:,b:')).equal({a:undefined,b:undefined})\n\n    // Invalid pair.\n    assert.throws(je('{]'), /unexpected/)\n    assert.throws(je('[}'), /unexpected/)\n    assert.throws(je(':'), /unexpected/)\n    assert.throws(je(':a'), /unexpected/)\n    assert.throws(je(' : '), /unexpected/)\n    assert.throws(je('{,]'), /unexpected/)\n    assert.throws(je('[,}'), /unexpected/)\n    assert.throws(je(',:'), /unexpected/)\n    assert.throws(je(',:a'), /unexpected/)\n    assert.throws(je('[:'), /unexpected/)\n    assert.throws(je('[:a'), /unexpected/)\n\n    // Unexpected close\n    assert.throws(je(']'), /unexpected/)\n    assert.throws(je('}'), /unexpected/)\n    assert.throws(je(' ]'), /unexpected/)\n    assert.throws(je(' }'), /unexpected/)\n    assert.throws(je(',}'), /unexpected/)\n    assert.throws(je('a]'), /unexpected/)\n    assert.throws(je('a}'), /unexpected/)\n    assert.throws(je('{a]'), /unexpected/)\n    assert.throws(je('[a}'), /unexpected/)\n    assert.throws(je('{a}'), /unexpected/)\n    assert.throws(je('{a:1]'), /unexpected/)\n\n    // These are actually OK\n    assert.deepEqual(Jsonic(',]'), [null])\n    assert.deepEqual(Jsonic('{a:}'), { a: null })\n    assert.deepEqual(Jsonic('{a:b:}'), { a: { b: null } })\n\n    assert.deepEqual(JS(Jsonic('[a:1]')), '[]')\n    assert.deepEqual(Jsonic('[a:1]').a, 1)\n\n    assert.deepEqual(JS(Jsonic('[a:]')), '[]')\n    assert.deepEqual(Jsonic('[a:]').a, null)\n  })\n\n  it('api-error', () => {\n    assert.throws(() => Jsonic.make().use(null), /Jsonic\\.use:/)\n  })\n})\n"
  },
  {
    "path": "test/exhaust.js",
    "content": "// Fuzz test; measure memory usage.\n\nconst Util = require('util')\nconst I = Util.inspect\n\nconst { Jsonic, Lexer } = require('..')\n\nmodule.exports = exhaust\n\nif (require.main === module) {\n  exhaust(parseInt(process.argv[2] || 3), true)\n}\n\nfunction exhaust(size, print) {\n  const j01 = Jsonic.make()\n\n  const config = j01.internal().config\n  const opts = j01.options\n\n  const ZZ = j01.token.ZZ\n\n  if (print) {\n    console.log('\\n')\n    console.log('# parse')\n    console.log('# ==================')\n  }\n\n  let max = 256\n  let total = Math.pow(max - 1, size)\n  let percent = ~~(total / 100)\n\n  if (print) {\n    console.log('# size:', size, 'total:', total, '1%=', percent)\n    let mp = Object.keys(process.memoryUsage())\n    console.log(\n      [\n        'P',\n        'T',\n        ...mp.map((x) => 'lo_' + x),\n        ...mp.map((x) => 'hi_' + x),\n        ...mp.map((x) => 'in_' + x),\n      ].join(', '),\n    )\n  }\n\n  let i = 0 // 256*256*120 // 1\n  let rm = {}\n  let em = {}\n  let rmc = 0\n  let emc = 0\n  let ecc = {}\n  let strgen = make_strgen(size, max) //,[0,0,120])\n  let ts = null\n\n  let mt = [0, 0, 0, 0]\n  let mb = [Infinity, Infinity, Infinity, Infinity]\n\n  let start = Date.now()\n  while (i <= total) {\n    ts = strgen()\n    if (0 === i % percent) {\n      let m = Object.values(process.memoryUsage())\n      mb = m.map((x, i) => (m[i] < mb[i] ? m[i] : mb[i]))\n      mt = m.map((x, i) => (m[i] > mt[i] ? m[i] : mt[i]))\n      if (print) {\n        console.log(\n          [\n            1 + ~~((100 * i) / total) + '%',\n            Date.now() - start,\n            ...mb,\n            ...mt,\n            ...m,\n          ].join(','),\n        )\n      }\n    }\n    try {\n      let d = j01(ts.s)\n      rmc++\n      //rm[''+ts.c+'|`'+ts.s+'`'] = d\n    } catch (e) {\n      emc++\n      ecc[e.code] = 1 + (ecc[e.code] = ecc[e.code] || 0)\n      em['' + ts.c + '|`' + ts.s + '`'] = e.name + ':' + e.code\n    }\n    i++\n  }\n\n  //console.log('#',ts.c)\n\n  let dur = Date.now() - start\n\n  if (print) {\n    console.log('# dur: ' + dur)\n    console.log('# rmc:  ' + rmc)\n    console.log('# emc:  ' + emc)\n    console.log('# ec:  ' + I(ecc))\n\n    // Uncomment to see src strings\n    // console.log('EM:')\n    // console.log(em)\n  }\n\n  return {\n    dur,\n    rmc,\n    emc,\n    ecc,\n  }\n}\n\nfunction make_strgen(size, max, init) {\n  let cc = []\n\n  if (null == init) {\n    for (let i = 0; i <= size; i++) {\n      cc[i] = 1 //0\n    }\n    cc[0] = 0 //-1\n    cc[size] = 0\n  } else {\n    cc = init\n  }\n\n  return function strgen() {\n    cc[0]++\n    for (let i = 0; i < size; i++) {\n      if (max <= cc[i]) {\n        cc[i + 1]++\n        cc[i] = 1 //0\n      }\n    }\n    /*\n    if(0 < cc[size]) {\n      return null\n    }\n    */\n\n    let out = { c: cc.slice(0, size) }\n    out.s = out.c.map((c) => String.fromCharCode(c)).join('')\n    return out\n  }\n}\n"
  },
  {
    "path": "test/feature.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst Util = require('util')\nconst I = Util.inspect\n\nconst { Jsonic, JsonicError, RuleSpec } = require('..')\nconst { loadTSV } = require('./utility')\n\nconst j = Jsonic\n\nconst JS = (x) => JSON.stringify(x)\nconst JP = (x) => JSON.parse(JSON.stringify(x))\n\nfunction tsvTest(name) {\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    try {\n      assert.deepEqual(JP(Jsonic(input)), JSON.parse(expected))\n    } catch (err) {\n      err.message = `${name} row ${row}: input=${input} expected=${expected}\\n${err.message}`\n      throw err\n    }\n  }\n}\n\nfunction tsvTestWith(name, opts) {\n  const instance = Jsonic.make(opts)\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    try {\n      assert.deepEqual(JP(instance(input)), JSON.parse(expected))\n    } catch (err) {\n      err.message = `${name} row ${row}: input=${input} expected=${expected}\\n${err.message}`\n      throw err\n    }\n  }\n}\n\nfunction tsvTestListChild(name, opts) {\n  const instance = Jsonic.make(opts)\n  const entries = loadTSV(name)\n  for (const { cols, row } of entries) {\n    const [input, expectedArray, expectedChild] = cols\n    try {\n      let result = instance(input)\n      assert.deepEqual(JS(result), expectedArray)\n      if (expectedChild !== undefined && expectedChild !== '') {\n        assert.deepEqual(JP(result['child$']), JSON.parse(expectedChild))\n      } else {\n        assert.deepEqual(result['child$'], undefined)\n      }\n    } catch (err) {\n      err.message = `${name} row ${row}: input=${input} expected_array=${expectedArray} expected_child=${expectedChild}\\n${err.message}`\n      throw err\n    }\n  }\n}\n\ndescribe('feature', function () {\n  it('test-util-match', () => {\n    assert.equal(match(1, 1), undefined)\n    assert.deepEqual(match([], [1]), '$[0]/val:undefined!=1')\n    assert.equal(match([], []), undefined)\n    assert.equal(match([1], [1]), undefined)\n    assert.equal(match([[]], [[]]), undefined)\n    assert.deepEqual(match([1], [2]), '$[0]/val:1!=2')\n    assert.deepEqual(match([[1]], [[2]]), '$[0][0]/val:1!=2')\n    assert.equal(match({}, {}), undefined)\n    assert.equal(match({ a: 1 }, { a: 1 }), undefined)\n    assert.deepEqual(match({ a: 1 }, { a: 2 }), '$.a/val:1!=2')\n    assert.equal(match({ a: { b: 1 } }, { a: { b: 1 } }), undefined)\n    assert.deepEqual(match({ a: 1 }, { a: 1, b: 2 }), '$.b/val:undefined!=2')\n    assert.deepEqual(match({ a: 1 }, { b: 1 }), '$.b/val:undefined!=1')\n    assert.deepEqual(match({ a: { b: 1 } }, { a: { b: 2 } }), '$.a.b/val:1!=2')\n    assert.equal(match({ a: 1, b: 2 }, { a: 1 }), undefined)\n    assert.deepEqual(match({ a: 1, b: 2 }, { a: 1 }, { miss: false }), \n      '$/key:{a,b}!={a}',\n    )\n    assert.equal(match([1], []), undefined)\n    assert.deepEqual(match([], [1]), '$[0]/val:undefined!=1')\n    assert.deepEqual(match([2, 1], [undefined, 1], { miss: false }), \n      '$[0]/val:2!=undefined',\n    )\n    assert.equal(match([2, 1], [undefined, 1]), undefined)\n  })\n\n  it('single-char', () => {\n    assert.deepEqual(j(), undefined)\n    assert.deepEqual(j(''), undefined)\n    assert.deepEqual(j('À'), 'À') // #192 verbatim text\n    assert.deepEqual(j(' '), ' ') // #160 non-breaking space, verbatim text\n    assert.deepEqual(j('{'), {}) // auto-close\n    assert.deepEqual(j('a'), 'a') // verbatim text\n    assert.deepEqual(j('['), []) // auto-close\n    assert.deepEqual(j(','), [null]) // implict list, prefixing-comma means null element\n    assert.deepEqual(j('#'), undefined) // comment\n    assert.deepEqual(j(' '), undefined) // ignored space\n    assert.deepEqual(j('\\u0010'), '\\x10') // verbatim text\n    assert.deepEqual(j('\\b'), '\\b') // verbatim\n    assert.deepEqual(j('\\t'), undefined) // ignored space\n    assert.deepEqual(j('\\n'), undefined) // ignored newline\n    assert.deepEqual(j('\\f'), '\\f') // verbatim\n    assert.deepEqual(j('\\r'), undefined) // ignored newline\n\n    assert.throws(() => j('\"'), /unterminated/)\n    assert.throws(() => j(\"'\"), /unterminated/)\n    assert.throws(() => j(':'), /unexpected/)\n    assert.throws(() => j(']'), /unexpected/)\n    assert.throws(() => j('`'), /unterminated/)\n    assert.throws(() => j('}'), /unexpected/)\n  })\n\n  it('number', () => {\n    assert.deepEqual(j('1'), 1)\n    assert.deepEqual(j('-1'), -1)\n    assert.deepEqual(j('+1'), 1)\n    assert.deepEqual(j('0'), 0)\n\n    assert.deepEqual(j('1.'), 1)\n    assert.deepEqual(j('-1.'), -1)\n    assert.deepEqual(j('+1.'), 1)\n    assert.deepEqual(j('0.'), 0)\n\n    assert.deepEqual(j('.1'), 0.1)\n    assert.deepEqual(j('-.1'), -0.1)\n    assert.deepEqual(j('+.1'), 0.1)\n    assert.deepEqual(j('.0'), 0)\n\n    assert.deepEqual(j('0.9'), 0.9)\n    assert.deepEqual(j('-0.9'), -0.9)\n    assert.deepEqual(j('[1]'), [1])\n    assert.deepEqual(j('a:1'), { a: 1 })\n    assert.deepEqual(j('1:a'), { 1: 'a' })\n    assert.deepEqual(j('{a:1}'), { a: 1 })\n    assert.deepEqual(j('{1:a}'), { 1: 'a' })\n    assert.deepEqual(j('1.2'), 1.2)\n    assert.deepEqual(j('1e2'), 100)\n    assert.deepEqual(j('10_0'), 100)\n    assert.deepEqual(j('-1.2'), -1.2)\n    assert.deepEqual(j('-1e2'), -100)\n    assert.deepEqual(j('-10_0'), -100)\n    assert.deepEqual(j('1e+2'), 100)\n    assert.deepEqual(j('1e-2'), 0.01)\n\n    assert.deepEqual(j('0xA'), 10)\n    assert.deepEqual(j('0xa'), 10)\n    assert.deepEqual(j('+0xA'), 10)\n    assert.deepEqual(j('+0xa'), 10)\n    assert.deepEqual(j('-0xA'), -10)\n    assert.deepEqual(j('-0xa'), -10)\n\n    assert.deepEqual(j('0o12'), 10)\n    assert.deepEqual(j('0b1010'), 10)\n    assert.deepEqual(j('0x_A'), 10)\n    assert.deepEqual(j('0x_a'), 10)\n    assert.deepEqual(j('0o_12'), 10)\n    assert.deepEqual(j('0b_1010'), 10)\n    assert.deepEqual(j('1e6:a'), { '1e6': 'a' }) // NOTE: \"1e6\" not \"1000000\"\n    assert.deepEqual(j('01'), 1)\n    assert.deepEqual(j('-01'), -1)\n    assert.deepEqual(j('0099'), 99)\n    assert.deepEqual(j('-0099'), -99)\n\n    assert.deepEqual(j('a:1'), { a: 1 })\n    assert.deepEqual(j('a:-1'), { a: -1 })\n    assert.deepEqual(j('a:+1'), { a: 1 })\n    assert.deepEqual(j('a:0'), { a: 0 })\n    assert.deepEqual(j('a:0.1'), { a: 0.1 })\n    assert.deepEqual(j('a:[1]'), { a: [1] })\n    assert.deepEqual(j('a:a:1'), { a: { a: 1 } })\n    assert.deepEqual(j('a:1:a'), { a: { 1: 'a' } })\n    assert.deepEqual(j('a:{a:1}'), { a: { a: 1 } })\n    assert.deepEqual(j('a:{1:a}'), { a: { 1: 'a' } })\n    assert.deepEqual(j('a:1.2'), { a: 1.2 })\n    assert.deepEqual(j('a:1e2'), { a: 100 })\n    assert.deepEqual(j('a:10_0'), { a: 100 })\n    assert.deepEqual(j('a:-1.2'), { a: -1.2 })\n    assert.deepEqual(j('a:-1e2'), { a: -100 })\n    assert.deepEqual(j('a:-10_0'), { a: -100 })\n    assert.deepEqual(j('a:1e+2'), { a: 100 })\n    assert.deepEqual(j('a:1e-2'), { a: 0.01 })\n    assert.deepEqual(j('a:0xA'), { a: 10 })\n    assert.deepEqual(j('a:0xa'), { a: 10 })\n    assert.deepEqual(j('a:0o12'), { a: 10 })\n    assert.deepEqual(j('a:0b1010'), { a: 10 })\n    assert.deepEqual(j('a:0x_A'), { a: 10 })\n    assert.deepEqual(j('a:0x_a'), { a: 10 })\n    assert.deepEqual(j('a:0o_12'), { a: 10 })\n    assert.deepEqual(j('a:0b_1010'), { a: 10 })\n    assert.deepEqual(j('a:1e6:a'), { a: { '1e6': 'a' } }) // NOTE: \"1e6\" not \"1000000\"\n    assert.deepEqual(j('[1,0]'), [1, 0])\n    assert.deepEqual(j('[1,0.5]'), [1, 0.5])\n\n    // text as +- not value enders\n    assert.deepEqual(j('1+'), '1+')\n    assert.deepEqual(j('1-'), '1-')\n    assert.deepEqual(j('1-+'), '1-+')\n\n    // partial numbers are converted to text\n    assert.deepEqual(j('-'), '-')\n    assert.deepEqual(j('+'), '+')\n    assert.deepEqual(j('1a'), '1a')\n\n    let jn = j.make({ number: { lex: false } })\n    assert.deepEqual(jn('1'), '1') // Now it's a string.\n    assert.deepEqual(j('1'), 1)\n    assert.deepEqual(jn('a:1'), { a: '1' })\n    assert.deepEqual(j('a:1'), { a: 1 })\n\n    let jh = j.make({ number: { hex: false } })\n    assert.deepEqual(jh('1'), 1)\n    assert.deepEqual(jh('0x10'), '0x10')\n    assert.deepEqual(jh('0o20'), 16)\n    assert.deepEqual(jh('0b10000'), 16)\n    assert.deepEqual(j('1'), 1)\n    assert.deepEqual(j('0x10'), 16)\n    assert.deepEqual(j('0o20'), 16)\n    assert.deepEqual(j('0b10000'), 16)\n\n    let jo = j.make({ number: { oct: false } })\n    assert.deepEqual(jo('1'), 1)\n    assert.deepEqual(jo('0x10'), 16)\n    assert.deepEqual(jo('0o20'), '0o20')\n    assert.deepEqual(jo('0b10000'), 16)\n    assert.deepEqual(j('1'), 1)\n    assert.deepEqual(j('0x10'), 16)\n    assert.deepEqual(j('0o20'), 16)\n    assert.deepEqual(j('0b10000'), 16)\n\n    let jb = j.make({ number: { bin: false } })\n    assert.deepEqual(jb('1'), 1)\n    assert.deepEqual(jb('0x10'), 16)\n    assert.deepEqual(jb('0o20'), 16)\n    assert.deepEqual(jb('0b10000'), '0b10000')\n    assert.deepEqual(j('1'), 1)\n    assert.deepEqual(j('0x10'), 16)\n    assert.deepEqual(j('0o20'), 16)\n    assert.deepEqual(j('0b10000'), 16)\n\n    let js0 = j.make({ number: { sep: null } })\n    assert.deepEqual(js0('1_0'), '1_0')\n    assert.deepEqual(j('1_0'), 10)\n\n    let js1 = j.make({ number: { sep: ' ' } })\n    assert.deepEqual(js1('1 0'), 10)\n    assert.deepEqual(js1('a:1 0'), { a: 10 })\n    assert.deepEqual(js1('a:1 0, b : 2 000 '), { a: 10, b: 2000 })\n    assert.deepEqual(j('1_0'), 10)\n  })\n\n  it('value-standard', () => {\n    assert.deepEqual(j(''), undefined)\n\n    assert.deepEqual(j('true'), true)\n    assert.deepEqual(j('false'), false)\n    assert.deepEqual(j('null'), null)\n\n    assert.deepEqual(j('true\\n'), true)\n    assert.deepEqual(j('false\\n'), false)\n    assert.deepEqual(j('null\\n'), null)\n\n    assert.deepEqual(j('true#'), true)\n    assert.deepEqual(j('false#'), false)\n    assert.deepEqual(j('null#'), null)\n\n    assert.deepEqual(j('true//'), true)\n    assert.deepEqual(j('false//'), false)\n    assert.deepEqual(j('null//'), null)\n\n    assert.deepEqual(j('{a:true}'), { a: true })\n    assert.deepEqual(j('{a:false}'), { a: false })\n    assert.deepEqual(j('{a:null}'), { a: null })\n\n    assert.deepEqual(j('{true:1}'), { true: 1 })\n    assert.deepEqual(j('{false:1}'), { false: 1 })\n    assert.deepEqual(j('{null:1}'), { null: 1 })\n\n    assert.deepEqual(j('a:true'), { a: true })\n    assert.deepEqual(j('a:false'), { a: false })\n    assert.deepEqual(j('a:null'), { a: null })\n    assert.deepEqual(j('a:'), { a: null })\n\n    assert.deepEqual(j('true,'), [true])\n    assert.deepEqual(j('false,'), [false])\n    assert.deepEqual(j('null,'), [null])\n\n    assert.deepEqual(\n      j('a:true,b:false,c:null,d:{e:true,f:false,g:null},h:[true,false,null]'), {\n      a: true,\n      b: false,\n      c: null,\n      d: { e: true, f: false, g: null },\n      h: [true, false, null],\n    })\n  })\n\n  it('value-custom', () => {\n    let jv0 = j.make({\n      number: { lex: false }, // needed for commadigits\n      value: {\n        def: {\n          foo: { val: 99 },\n          bar: { val: { x: 1 } },\n          zed: {\n            match: /Z(\\d)/,\n            val: (res) => +res[1],\n          },\n          qaz: {\n            match: /HEX<(.+)>/,\n            val: (res) => {\n              let val = parseInt(res[1], 16)\n              if (isNaN(val)) {\n                let e = new Error('Bad hex: ' + res[0])\n                e.code = 'badhex'\n                throw e\n              }\n              return val\n            },\n          },\n\n          // Stops at tokens\n          cap: {\n            match: /[A-Z]+/,\n            val: (res) => res[0].toLowerCase(),\n          },\n\n          // Does not stop at tokens\n          commadigits: {\n            match: /^\\d+(,\\d+)+/,\n            val: (res) => 20 * +res[0].replace(/,/g, ''),\n            consume: true,\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(jv0(''), undefined)\n    assert.deepEqual(jv0('foo'), 99)\n    assert.deepEqual(jv0('bar'), { x: 1 })\n    assert.deepEqual(jv0('a:foo'), { a: 99 })\n    assert.deepEqual(jv0('a:bar'), { a: { x: 1 } })\n\n    assert.deepEqual(jv0('a:Z1'), { a: 1 })\n    assert.deepEqual(jv0('a:Zx'), { a: 'Zx' })\n\n    assert.deepEqual(jv0('a:HEX<>'), { a: 'HEX<>' })\n    assert.deepEqual(jv0('a:HEX<a>'), { a: 10 })\n    assert.throws(() => jv0('a:HEX<x>'), /badhex/)\n\n    assert.deepEqual(jv0('[A,B]'), ['a', 'b'])\n    assert.deepEqual(jv0('[1 2,3] '), ['1', 460])\n  })\n\n  it('match-custom', () => {\n    let jv0 = j\n      .make({\n        match: {\n          value: {\n            foobar: {\n              match: /foobar(\\d)/,\n              val: (res) => +res[1],\n            },\n\n            // no need to turn of number lexing\n            commadigits: {\n              match: /^\\d+(,\\d+)+/,\n              val: (res) => 20 * +res[0].replace(/,/g, ''),\n            },\n          },\n          token: {\n            FOO: /foo/,\n          },\n        },\n      })\n      .rule('val', (rs, p) => {\n        rs.open({ s: [p.cfg.t.FOO], a: (r) => (r.node = 'Foo') })\n      })\n\n    assert.deepEqual(jv0('foo'), 'Foo')\n    assert.deepEqual(jv0('foobar1'), 1)\n\n    // Still parses numbers\n    assert.deepEqual(jv0('[1 2,3 4]'), [1, 460, 4])\n  })\n\n  it('null-or-undefined', () => {\n    // All ignored, so undefined\n    assert.deepEqual(j(''), undefined)\n    assert.deepEqual(j(' '), undefined)\n    assert.deepEqual(j('\\n'), undefined)\n    assert.deepEqual(j('#'), undefined)\n    assert.deepEqual(j('//'), undefined)\n    assert.deepEqual(j('/**/'), undefined)\n\n    // JSON only has nulls\n    assert.deepEqual(j('null'), null)\n    assert.deepEqual(j('a:null'), { a: null })\n\n    assert.deepEqual(JS(j('[a:1]')), '[]')\n    assert.deepEqual(j('[a:1]').a, 1)\n\n    assert.deepEqual(j('[{a:null}]'), [{ a: null }])\n\n    assert.deepEqual(JS(j('[a:null]')), '[]')\n    assert.deepEqual(j('[a:null]').a, null)\n\n    assert.deepEqual(j('a:null,b:null'), { a: null, b: null })\n    assert.deepEqual(j('{a:null,b:null}'), { a: null, b: null })\n\n    assert.deepEqual(JS(j('[a:]')), '[]')\n    assert.deepEqual(j('[a:]').a, null)\n\n    assert.deepEqual(JS(j('[a:,]')), '[]')\n    assert.deepEqual(j('[a:,]').a, null)\n\n    assert.deepEqual(JS(j('[a:,b:]')), '[]')\n    assert.deepEqual({ ...j('[a:,b:]') }, { a: null, b: null })\n\n    assert.deepEqual(JS(j('[a:,b:c:]')), '[]')\n    assert.deepEqual({ ...j('[a:,b:c:]') }, { a: null, b: { c: null } })\n\n    assert.deepEqual(j('a:'), { a: null })\n    assert.deepEqual(j('a:,b:'), { a: null, b: null })\n    assert.deepEqual(j('a:,b:c:'), { a: null, b: { c: null } })\n\n    assert.deepEqual(j('{a:}'), { a: null })\n    assert.deepEqual(j('{a:,b:}'), { a: null, b: null })\n    assert.deepEqual(j('{a:,b:c:}'), { a: null, b: { c: null } })\n  })\n\n  it('value-text', () => {\n    assert.deepEqual(j('a'), 'a')\n    assert.deepEqual(j('1a'), '1a') // NOTE: not a number!\n    assert.deepEqual(j('a/b'), 'a/b')\n    assert.deepEqual(j('a#b'), 'a')\n\n    assert.deepEqual(j('a//b'), 'a')\n    assert.deepEqual(j('a/*b*/'), 'a')\n    assert.deepEqual(j('a\\\\n'), 'a\\\\n')\n    assert.deepEqual(j('\\\\s+'), '\\\\s+')\n\n    assert.deepEqual(j('x:a'), { x: 'a' })\n    assert.deepEqual(j('x:a/b'), { x: 'a/b' })\n    assert.deepEqual(j('x:a#b'), { x: 'a' })\n    assert.deepEqual(j('x:a//b'), { x: 'a' })\n    assert.deepEqual(j('x:a/*b*/'), { x: 'a' })\n    assert.deepEqual(j('x:a\\\\n'), { x: 'a\\\\n' })\n    assert.deepEqual(j('x:\\\\s+'), { x: '\\\\s+' })\n\n    assert.deepEqual(j('[a]'), ['a'])\n    assert.deepEqual(j('[a/b]'), ['a/b'])\n    assert.deepEqual(j('[a#b]'), ['a'])\n    assert.deepEqual(j('[a//b]'), ['a'])\n    assert.deepEqual(j('[a/*b*/]'), ['a'])\n    assert.deepEqual(j('[a\\\\n]'), ['a\\\\n'])\n    assert.deepEqual(j('[\\\\s+]'), ['\\\\s+'])\n\n    // TODO: REVIEW\n    // // Force text re to fail (also tests infinite loop protection).\n    // let j0 = j.make()\n    // j0.internal().config.re.te =\n    //   new RegExp(j0.internal().config.re.te.source.replace('#','#a'))\n    // expect(()=>j0('a')).throw(/unexpected/)\n  })\n\n  it('value-string', () => {\n    assert.deepEqual(j(\"''\"), '')\n    assert.deepEqual(j('\"\"'), '')\n    assert.deepEqual(j('``'), '')\n\n    assert.deepEqual(j(\"'a'\"), 'a')\n    assert.deepEqual(j('\"a\"'), 'a')\n    assert.deepEqual(j('`a`'), 'a')\n\n    assert.deepEqual(j(\"'a b'\"), 'a b')\n    assert.deepEqual(j('\"a b\"'), 'a b')\n    assert.deepEqual(j('`a b`'), 'a b')\n\n    assert.deepEqual(j(\"'a\\\\tb'\"), 'a\\tb')\n    assert.deepEqual(j('\"a\\\\tb\"'), 'a\\tb')\n    assert.deepEqual(j('`a\\\\tb`'), 'a\\tb')\n\n    // NOTE: backslash inside string is always removed\n    assert.deepEqual(j('`a\\\\qb`'), 'aqb')\n\n    assert.deepEqual(j(\"'a\\\\'b\\\"`c'\"), 'a\\'b\"`c')\n    assert.deepEqual(j('\"a\\\\\"b`\\'c\"'), 'a\"b`\\'c')\n    assert.deepEqual(j('`a\\\\`b\"\\'c`'), 'a`b\"\\'c')\n\n    assert.deepEqual(j('\"\\\\u0061\"'), 'a')\n    assert.deepEqual(j('\"\\\\x61\"'), 'a')\n\n    assert.deepEqual(j('`\\n`'), '\\n')\n    assert.throws(() => j('\"\\n\"'), /unprintable]/)\n    assert.throws(() => j('\"\\t\"'), /unprintable]/)\n    assert.throws(() => j('\"\\f\"'), /unprintable]/)\n    assert.throws(() => j('\"\\b\"'), /unprintable]/)\n    assert.throws(() => j('\"\\v\"'), /unprintable]/)\n    assert.throws(() => j('\"\\0\"'), /unprintable]/)\n\n    assert.deepEqual(j('\"\\\\n\"'), '\\n')\n    assert.deepEqual(j('\"\\\\t\"'), '\\t')\n    assert.deepEqual(j('\"\\\\f\"'), '\\f')\n    assert.deepEqual(j('\"\\\\b\"'), '\\b')\n    assert.deepEqual(j('\"\\\\v\"'), '\\v')\n    assert.deepEqual(j('\"\\\\\"\"'), '\"')\n    assert.deepEqual(j('\"\\\\\\'\"'), \"'\")\n    assert.deepEqual(j('\"\\\\`\"'), '`')\n\n    assert.deepEqual(j('\"\\\\w\"'), 'w')\n    assert.deepEqual(j('\"\\\\0\"'), '0')\n\n    assert.throws(() => j('`\\x1a`'), /unprintable]/)\n    assert.throws(() => j('\"\\x1a\"'), /unprintable]/)\n\n    assert.throws(() => j('\"x'), /unterminated_string].*:1:1/s)\n    assert.throws(() => j(' \"x'), /unterminated_string].*:1:2/s)\n    assert.throws(() => j('  \"x'), /unterminated_string].*:1:3/s)\n    assert.throws(() => j('a:\"x'), /unterminated_string].*:1:3/s)\n    assert.throws(() => j('aa:\"x'), /unterminated_string].*:1:4/s)\n    assert.throws(() => j('aaa:\"x'), /unterminated_string].*:1:5/s)\n    assert.throws(() => j(' a:\"x'), /unterminated_string].*:1:4/s)\n    assert.throws(() => j(' a :\"x'), /unterminated_string].*:1:5/s)\n\n    assert.throws(() => j(\"'x\"), /unterminated_string].*:1:1/s)\n    assert.throws(() => j(\" 'x\"), /unterminated_string].*:1:2/s)\n    assert.throws(() => j(\"  'x\"), /unterminated_string].*:1:3/s)\n    assert.throws(() => j(\"a:'x\"), /unterminated_string].*:1:3/s)\n    assert.throws(() => j(\"aa:'x\"), /unterminated_string].*:1:4/s)\n    assert.throws(() => j(\"aaa:'x\"), /unterminated_string].*:1:5/s)\n    assert.throws(() => j(\" a:'x\"), /unterminated_string].*:1:4/s)\n    assert.throws(() => j(\" a :'x\"), /unterminated_string].*:1:5/s)\n\n    assert.throws(() => j('`x'), /unterminated_string].*:1:1/s)\n    assert.throws(() => j(' `x'), /unterminated_string].*:1:2/s)\n    assert.throws(() => j('  `x'), /unterminated_string].*:1:3/s)\n    assert.throws(() => j('a:`x'), /unterminated_string].*:1:3/s)\n    assert.throws(() => j('aa:`x'), /unterminated_string].*:1:4/s)\n    assert.throws(() => j('aaa:`x'), /unterminated_string].*:1:5/s)\n    assert.throws(() => j(' a:`x'), /unterminated_string].*:1:4/s)\n    assert.throws(() => j(' a :`x'), /unterminated_string].*:1:5/s)\n\n    assert.throws(() => j('`\\nx'), /unterminated_string].*:1:1/s)\n    assert.throws(() => j(' `\\nx'), /unterminated_string].*:1:2/s)\n    assert.throws(() => j('  `\\nx'), /unterminated_string].*:1:3/s)\n    assert.throws(() => j('a:`\\nx'), /unterminated_string].*:1:3/s)\n    assert.throws(() => j('aa:`\\nx'), /unterminated_string].*:1:4/s)\n    assert.throws(() => j('aaa:`\\nx'), /unterminated_string].*:1:5/s)\n    assert.throws(() => j(' a:`\\nx'), /unterminated_string].*:1:4/s)\n    assert.throws(() => j(' a :`\\nx'), /unterminated_string].*:1:5/s)\n\n    assert.throws(() => j('\\n\\n\"x'), /unterminated_string].*:3:1/s)\n    assert.throws(() => j('\\n\\n \"x'), /unterminated_string].*:3:2/s)\n    assert.throws(() => j('\\n\\n  \"x'), /unterminated_string].*:3:3/s)\n    assert.throws(() => j('\\n\\na:\"x'), /unterminated_string].*:3:3/s)\n    assert.throws(() => j('\\n\\naa:\"x'), /unterminated_string].*:3:4/s)\n    assert.throws(() => j('\\n\\naaa:\"x'), /unterminated_string].*:3:5/s)\n    assert.throws(() => j('\\n\\n a:\"x'), /unterminated_string].*:3:4/s)\n    assert.throws(() => j('\\n\\n a :\"x'), /unterminated_string].*:3:5/s)\n\n    // string.escape.allowUnknown:false\n    let j1 = j.make({ string: { allowUnknown: false } })\n    assert.deepEqual(j1('\"\\\\n\"'), '\\n')\n    assert.deepEqual(j1('\"\\\\t\"'), '\\t')\n    assert.deepEqual(j1('\"\\\\f\"'), '\\f')\n    assert.deepEqual(j1('\"\\\\b\"'), '\\b')\n    assert.deepEqual(j1('\"\\\\v\"'), '\\v')\n    assert.deepEqual(j1('\"\\\\\"\"'), '\"')\n    assert.deepEqual(j1('\"\\\\\\\\\"'), '\\\\')\n    assert.throws(() => j1('\"\\\\w\"'), /unexpected].*:1:3/s)\n    assert.throws(() => j1('\"\\\\0\"'), /unexpected].*:1:3/s)\n\n    // TODO: PLUGIN csv\n    // let k = j.make({string:{escapedouble:true}})\n    // expect(k('\"a\"\"b\"')).equal('a\"b')\n    // expect(k('`a``b`')).equal('a`b')\n    // expect(k('\\'a\\'\\'b\\'')).equal('a\\'b')\n  })\n\n  it('multiline-string', () => {\n    assert.deepEqual(j('`a`'), 'a')\n    assert.deepEqual(j('`\\na`'), '\\na')\n    assert.deepEqual(j('`\\na\\n`'), '\\na\\n')\n    assert.deepEqual(j('`a\\nb`'), 'a\\nb')\n    assert.deepEqual(j('`a\\n\\nb`'), 'a\\n\\nb')\n    assert.deepEqual(j('`a\\nc\\nb`'), 'a\\nc\\nb')\n    assert.deepEqual(j('`a\\r\\n\\r\\nb`'), 'a\\r\\n\\r\\nb')\n\n    assert.throws(() => j('`\\n'), /unterminated_string.*:1:1/s)\n    assert.throws(() => j(' `\\n'), /unterminated_string.*:1:2/s)\n    assert.throws(() => j('\\n `\\n'), /unterminated_string.*:2:2/s)\n\n    assert.throws(() => j('`a``b'), /unterminated_string.*:1:4/s)\n    assert.throws(() => j('\\n`a``b'), /unterminated_string.*:2:4/s)\n    assert.throws(() => j('\\n`a`\\n`b'), /unterminated_string.*:3:1/s)\n    assert.throws(() => j('\\n`\\na`\\n`b'), /unterminated_string.*:4:1/s)\n    assert.throws(() => j('\\n`\\na`\\n`\\nb'), /unterminated_string.*:4:1/s)\n\n    assert.throws(() => j('`a` `b'), /unterminated_string.*:1:5/s)\n    assert.throws(() => j('`a`\\n `b'), /unterminated_string.*:2:2/s)\n\n    assert.throws(() => j('`a\\n` `b'), /unterminated_string.*:2:3/s)\n    assert.throws(() => j('`a\\n`,`b'), /unterminated_string.*:2:3/s)\n    assert.throws(() => j('[`a\\n` `b'), /unterminated_string.*:2:3/s)\n    assert.throws(() => j('[`a\\n`,`b'), /unterminated_string.*:2:3/s)\n    assert.throws(() => j('1\\n `b'), /unterminated_string.*:2:2/s)\n    assert.throws(() => j('[1\\n,`b'), /unterminated_string.*:2:2/s)\n\n    // TODO: PLUGIN\n    // expect(j(\"'''a\\nb'''\")).equal('a\\nb')\n    // expect(j(\"'''\\na\\nb'''\")).equal('a\\nb')\n    // expect(j(\"'''\\na\\nb\\n'''\")).equal('a\\nb')\n    // expect(j(\"\\n'''\\na\\nb\\n'''\\n\")).equal('a\\nb')\n    // expect(j(\" '''\\na\\nb\\n''' \")).equal('a\\nb')\n\n    // expect(j(\"''' a\\nb\\n'''\")).equal(' a\\nb')\n    // expect(j(\" '''a\\n b\\n'''\")).equal('a\\nb')\n    // expect(j(\" ''' \\na\\n b\\n'''\")).equal('a\\nb')\n    // expect(j(\" ''' \\na\\n  b\\n'''\")).equal('a\\n b')\n    // expect(j(\" ''' \\na\\nb\\n'''\")).equal('a\\nb')\n    // expect(j(\" ''' a\\n b\\n'''\")).equal('a\\nb')\n    // expect(j(\" ''' a\\nb\\n'''\")).equal('a\\nb')\n\n    //     expect(j(`{\n    //   md:\n    //     '''\n    //     First line.\n    //     Second line.\n    //       This line is indented by two spaces.\n    //     '''\n    // }`)).equal({\n    //   md: \"First line.\\nSecond line.\\n  This line is indented by two spaces.\",\n    // })\n\n    // expect(j(\"'''\\na\\nb\\n'''\")).equal('a\\nb')\n    // expect(j(\"'''a\\nb'''\")).equal('a\\nb')\n  })\n\n  it('implicit-object', () => {\n    tsvTest('feature-implicit-object')\n  })\n\n  it('implicit-list', () => {\n    // implicit null element preceeds empty comma\n    assert.deepEqual(j(','), [null])\n    assert.deepEqual(j(',a'), [null, 'a'])\n    assert.deepEqual(j(',\"a\"'), [null, 'a'])\n    assert.deepEqual(j(',1'), [null, 1])\n    assert.deepEqual(j(',true'), [null, true])\n    assert.deepEqual(j(',[]'), [null, []])\n    assert.deepEqual(j(',{}'), [null, {}])\n    assert.deepEqual(j(',[1]'), [null, [1]])\n    assert.deepEqual(j(',{a:1}'), [null, { a: 1 }])\n\n    assert.deepEqual(JS(j(',a:1')), '[null]')\n    assert.deepEqual(j(',a:1').a, 1)\n\n    // Top level comma imlies list; ignore trailing comma\n    assert.deepEqual(j('a,'), ['a'])\n    assert.deepEqual(j('\"a\",'), ['a'])\n    assert.deepEqual(j('1,'), [1])\n    assert.deepEqual(j('1,,'), [1, null])\n    assert.deepEqual(j('1,,,'), [1, null, null])\n    assert.deepEqual(j('1,null'), [1, null])\n    assert.deepEqual(j('1,null,'), [1, null])\n    assert.deepEqual(j('1,null,null'), [1, null, null])\n    assert.deepEqual(j('1,null,null,'), [1, null, null])\n    assert.deepEqual(j('true,'), [true])\n    assert.deepEqual(j('[],'), [[]])\n    assert.deepEqual(j('{},'), [{}])\n    assert.deepEqual(j('[1],'), [[1]])\n    assert.deepEqual(j('{a:1},'), [{ a: 1 }])\n\n    // NOTE: special case, this is considered a map pair\n    assert.deepEqual(j('a:1,'), { a: 1 })\n\n    assert.deepEqual(j('a,'), ['a'])\n    assert.deepEqual(j('\"a\",'), ['a'])\n    assert.deepEqual(j('true,'), [true])\n    assert.deepEqual(j('1,'), [1])\n    assert.deepEqual(j('a,1'), ['a', 1])\n    assert.deepEqual(j('\"a\",1'), ['a', 1])\n    assert.deepEqual(j('true,1'), [true, 1])\n    assert.deepEqual(j('1,1'), [1, 1])\n\n    assert.deepEqual(j('a,b'), ['a', 'b'])\n    assert.deepEqual(j('a,b,c'), ['a', 'b', 'c'])\n    assert.deepEqual(j('a,b,c,d'), ['a', 'b', 'c', 'd'])\n\n    assert.deepEqual(j('a b'), ['a', 'b'])\n    assert.deepEqual(j('a b c'), ['a', 'b', 'c'])\n    assert.deepEqual(j('a b c d'), ['a', 'b', 'c', 'd'])\n\n    assert.deepEqual(j('[a],[b]'), [['a'], ['b']])\n    assert.deepEqual(j('[a],[b],[c]'), [['a'], ['b'], ['c']])\n    assert.deepEqual(j('[a],[b],[c],[d]'), [['a'], ['b'], ['c'], ['d']])\n\n    assert.deepEqual(j('[a] [b]'), [['a'], ['b']])\n    assert.deepEqual(j('[a] [b] [c]'), [['a'], ['b'], ['c']])\n    assert.deepEqual(j('[a] [b] [c] [d]'), [['a'], ['b'], ['c'], ['d']])\n\n    // TODO: note this in docs as it enables parsing of JSON logs/records\n    assert.deepEqual(j('{a:1} {b:1}'), [{ a: 1 }, { b: 1 }])\n    assert.deepEqual(j('{a:1} {b:1} {c:1}'), [{ a: 1 }, { b: 1 }, { c: 1 }])\n    assert.deepEqual(j('{a:1} {b:1} {c:1} {d:1}'), [\n      { a: 1 },\n      { b: 1 },\n      { c: 1 },\n      { d: 1 },\n    ])\n    assert.deepEqual(j('\\n{a:1}\\n{b:1}\\r\\n{c:1}\\n{d:1}\\r\\n'), [\n      { a: 1 },\n      { b: 1 },\n      { c: 1 },\n      { d: 1 },\n    ])\n\n    assert.deepEqual(j('{a:1},'), [{ a: 1 }])\n    assert.deepEqual(j('[1],'), [[1]])\n\n    assert.deepEqual(JS(j('[a:1]')), '[]')\n    assert.deepEqual({ ...j('[a:1]') }, { a: 1 })\n\n    assert.deepEqual(JS(j('[a:1,b:2]')), '[]')\n    assert.deepEqual({ ...j('[a:1,b:2]') }, { a: 1, b: 2 })\n\n    assert.deepEqual(JS(j('[a:1,b:2,c:3]')), '[]')\n    assert.deepEqual({ ...j('[a:1,b:2,c:3]') }, { a: 1, b: 2, c: 3 })\n\n    assert.deepEqual(JS(j('[a:1,b:2,c:3,d:4]')), '[]')\n    assert.deepEqual({ ...j('[a:1,b:2,c:3,d:4]') }, { a: 1, b: 2, c: 3, d: 4 })\n  })\n\n  it('implicit-map', () => {\n    tsvTest('feature-implicit-map')\n  })\n\n  it('nested-space-pairs', () => {\n    tsvTest('feature-nested-space-pairs')\n  })\n\n  it('extension', () => {\n    assert.deepEqual(j('a:{b:1,c:2},a:{c:3,e:4}'), { a: { b: 1, c: 3, e: 4 } })\n\n    assert.deepEqual(j('a:{b:1,x:1},a:{b:2,y:2},a:{b:3,z:3}'), {\n      a: { b: 3, x: 1, y: 2, z: 3 },\n    })\n\n    assert.deepEqual(j('a:[{b:1,x:1}],a:[{b:2,y:2}],a:[{b:3,z:3}]'), {\n      a: [{ b: 3, x: 1, y: 2, z: 3 }],\n    })\n\n    assert.deepEqual(j('a:[{b:1},{x:1}],a:[{b:2},{y:2}],a:[{b:3},{z:3}]'), {\n      a: [{ b: 3 }, { x: 1, y: 2, z: 3 }],\n    })\n\n    let k = j.make({ map: { extend: false } })\n    assert.deepEqual(k('a:{b:1,c:2},a:{c:3,e:4}'), { a: { c: 3, e: 4 } })\n  })\n\n  it('finish', () => {\n    assert.deepEqual(j('a:{b:'), { a: { b: null } })\n    assert.deepEqual(j('{a:{b:{c:1}'), { a: { b: { c: 1 } } })\n    assert.deepEqual(j('[[1'), [[1]])\n\n    let k = j.make({ rule: { finish: false } })\n    assert.throws(() => k('a:{b:'), /end_of_source/)\n    assert.throws(() => k('{a:{b:{c:1}'), /end_of_source/)\n    assert.throws(() => k('[[1'), /end_of_source/)\n  })\n\n  it('property-dive', () => {\n    assert.deepEqual(j('{a:1,b:2}'), { a: 1, b: 2 })\n    assert.deepEqual(j('{a:1,b:{c:2}}'), { a: 1, b: { c: 2 } })\n    assert.deepEqual(j('{a:1,b:{c:2},d:3}'), { a: 1, b: { c: 2 }, d: 3 })\n    assert.deepEqual(j('{b:{c:2,e:4},d:3}'), { b: { c: 2, e: 4 }, d: 3 })\n\n    assert.deepEqual(j('{a:{b:{c:1,d:2},e:3},f:4}'), {\n      a: { b: { c: 1, d: 2 }, e: 3 },\n      f: 4,\n    })\n    assert.deepEqual(j('a:b:c'), { a: { b: 'c' } })\n    assert.deepEqual(j('a:b:c, d:e:f'), { a: { b: 'c' }, d: { e: 'f' } })\n    assert.deepEqual(j('a:b:c\\nd:e:f'), { a: { b: 'c' }, d: { e: 'f' } })\n\n    assert.deepEqual(j('a:b:c,d:e'), { a: { b: 'c' }, d: 'e' })\n    assert.deepEqual(j('a:b:c:1,d:e'), { a: { b: { c: 1 } }, d: 'e' })\n    assert.deepEqual(j('a:b:c:f:{g:1},d:e'), {\n      a: { b: { c: { f: { g: 1 } } } },\n      d: 'e',\n    })\n    assert.deepEqual(j('c:f:{g:1,h:2},d:e'), { c: { f: { g: 1, h: 2 } }, d: 'e' })\n    assert.deepEqual(j('c:f:[{g:1,h:2}],d:e'), {\n      c: { f: [{ g: 1, h: 2 }] },\n      d: 'e',\n    })\n\n    assert.deepEqual(j('a:b:c:1\\nd:e'), { a: { b: { c: 1 } }, d: 'e' })\n\n    assert.deepEqual(j('[{a:1,b:2}]'), [{ a: 1, b: 2 }])\n    assert.deepEqual(j('[{a:1,b:{c:2}}]'), [{ a: 1, b: { c: 2 } }])\n    assert.deepEqual(j('[{a:1,b:{c:2},d:3}]'), [{ a: 1, b: { c: 2 }, d: 3 }])\n    assert.deepEqual(j('[{b:{c:2,e:4},d:3}]'), [{ b: { c: 2, e: 4 }, d: 3 }])\n\n    assert.deepEqual(j('[{a:{b:{c:1,d:2},e:3},f:4}]'), [\n      { a: { b: { c: 1, d: 2 }, e: 3 }, f: 4 },\n    ])\n\n    assert.deepEqual(JS(j('[a:b:c]')), '[]')\n    assert.deepEqual(j('[a:b:c]').a, { b: 'c' })\n\n    // NOTE: this validates that array props also merge!\n    assert.deepEqual(JS(j('[a:b:c, a:d:e]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c, a:d:e]') }, { a: { b: 'c', d: 'e' } })\n\n    assert.deepEqual(JS(j('[a:b:c, d:e:f]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c, d:e:f]') }, { a: { b: 'c' }, d: { e: 'f' } })\n\n    assert.deepEqual(JS(j('[a:b:c\\nd:e:f]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c\\nd:e:f]') }, { a: { b: 'c' }, d: { e: 'f' } })\n\n    assert.deepEqual(JS(j('[a:b:c,d:e]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c,d:e]') }, { a: { b: 'c' }, d: 'e' })\n\n    assert.deepEqual(JS(j('[a:b:c:1,d:e]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c:1,d:e]') }, { a: { b: { c: 1 } }, d: 'e' })\n\n    assert.deepEqual(JS(j('[a:b:c:f:{g:1},d:e]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c:f:{g:1},d:e]') }, {\n      a: { b: { c: { f: { g: 1 } } } },\n      d: 'e',\n    })\n\n    assert.deepEqual(JS(j('[c:f:{g:1,h:2},d:e]')), '[]')\n    assert.deepEqual({ ...j('[c:f:{g:1,h:2},d:e]') }, {\n      c: { f: { g: 1, h: 2 } },\n      d: 'e',\n    })\n\n    assert.deepEqual(JS(j('[c:f:[{g:1,h:2}],d:e]')), '[]')\n    assert.deepEqual({ ...j('[c:f:[{g:1,h:2}],d:e]') }, {\n      c: { f: [{ g: 1, h: 2 }] },\n      d: 'e',\n    })\n\n    assert.deepEqual(JS(j('[a:b:c:1\\nd:e]')), '[]')\n    assert.deepEqual({ ...j('[a:b:c:1\\nd:e]') }, { a: { b: { c: 1 } }, d: 'e' })\n\n    assert.deepEqual(j('a:b:{x:1},a:b:{y:2}'), { a: { b: { x: 1, y: 2 } } })\n    assert.deepEqual(j('a:b:{x:1},a:b:{y:2},a:b:{z:3}'), {\n      a: { b: { x: 1, y: 2, z: 3 } },\n    })\n\n    assert.deepEqual(j('a:b:c:{x:1},a:b:c:{y:2}'), {\n      a: { b: { c: { x: 1, y: 2 } } },\n    })\n    assert.deepEqual(j('a:b:c:{x:1},a:b:c:{y:2},a:b:c:{z:3}'), {\n      a: { b: { c: { x: 1, y: 2, z: 3 } } },\n    })\n  })\n\n  it('list-property', () => {\n    assert.deepEqual(j('[a:1]').a, 1)\n\n    let k = j.make({ list: { property: false } })\n    assert.throws(() => k('[a:1]'), /unexpected/)\n  })\n\n  it('list-pair', () => {\n    tsvTestWith('feature-list-pair', { list: { pair: true } })\n  })\n\n  it('list-pair-interaction', () => {\n    // === pair=false, property=true (default behavior) ===\n    // Pairs become properties on the array object, not elements\n    assert.deepEqual(JS(j('[a:1]')), '[]')\n    assert.deepEqual(j('[a:1]').a, 1)\n    assert.deepEqual(JS(j('[a:1,b:2]')), '[]')\n    assert.deepEqual({ ...j('[a:1,b:2]') }, { a: 1, b: 2 })\n    // Mixed: properties on array, values as elements\n    assert.deepEqual(JS(j('[a:1,2,b:3]')), '[2]')\n    assert.deepEqual({ ...j('[a:1,2,b:3]') }, { 0: 2, a: 1, b: 3 })\n\n    // === pair=false, property=false ===\n    // Pairs in lists are errors\n    let pp_ff = j.make({ list: { pair: false, property: false } })\n    assert.throws(() => pp_ff('[a:1]'), /unexpected/)\n    assert.throws(() => pp_ff('[a:1,b:2]'), /unexpected/)\n    // Plain list values still work\n    assert.deepEqual(pp_ff('[1,2,3]'), [1, 2, 3])\n    assert.deepEqual(pp_ff('[]'), [])\n    // Explicit maps inside lists still work\n    assert.deepEqual(pp_ff('[{a:1}]'), [{ a: 1 }])\n    assert.deepEqual(pp_ff('[{a:1},{b:2}]'), [{ a: 1 }, { b: 2 }])\n\n    // === pair=true, property=true ===\n    // pair takes precedence: pairs become elements, not properties\n    let pp_tt = j.make({ list: { pair: true, property: true } })\n    assert.deepEqual(pp_tt('[a:1]'), [{ a: 1 }])\n    assert.deepEqual(pp_tt('[a:1,b:2]'), [{ a: 1 }, { b: 2 }])\n    assert.deepEqual(pp_tt('[a:1,2,b:3]'), [{ a: 1 }, 2, { b: 3 }])\n    assert.deepEqual(pp_tt('[a:1,a:2]'), [{ a: 1 }, { a: 2 }])\n    // Verify elements exist at numeric indices\n    assert.deepEqual(pp_tt('[a:1]')[0], { a: 1 })\n    assert.deepEqual(JS(pp_tt('[a:1]')), '[{\"a\":1}]')\n\n    // === pair=true, property=false ===\n    // pair takes precedence: pairs become elements (property disabled doesn't matter)\n    let pp_tf = j.make({ list: { pair: true, property: false } })\n    assert.deepEqual(pp_tf('[a:1]'), [{ a: 1 }])\n    assert.deepEqual(pp_tf('[a:1,b:2]'), [{ a: 1 }, { b: 2 }])\n    assert.deepEqual(pp_tf('[a:1,2,b:3]'), [{ a: 1 }, 2, { b: 3 }])\n    assert.deepEqual(pp_tf('[a:b:c]'), [{ a: { b: 'c' } }])\n\n    // === Braces: explicit maps interact correctly with list.pair ===\n    let lp = j.make({ list: { pair: true } })\n    // Explicit map as list element (no pairs involved)\n    assert.deepEqual(lp('[{a:1}]'), [{ a: 1 }])\n    assert.deepEqual(lp('[{a:1,b:2}]'), [{ a: 1, b: 2 }])\n    // Mix of explicit maps and implicit pair objects\n    assert.deepEqual(lp('[{a:1},b:2]'), [{ a: 1 }, { b: 2 }])\n    assert.deepEqual(lp('[a:1,{b:2}]'), [{ a: 1 }, { b: 2 }])\n    assert.deepEqual(lp('[{a:1},b:2,{c:3}]'), [{ a: 1 }, { b: 2 }, { c: 3 }])\n    // Pair value is an explicit map\n    assert.deepEqual(lp('[a:{x:1,y:2}]'), [{ a: { x: 1, y: 2 } }])\n    assert.deepEqual(lp('[a:{x:{y:1}}]'), [{ a: { x: { y: 1 } } }])\n    // Nested map with pair inside\n    assert.deepEqual(lp('[{a:{b:1,c:2}},d:3]'), [{ a: { b: 1, c: 2 } }, { d: 3 }])\n\n    // === Square brackets: nested lists interact correctly with list.pair ===\n    // Pair value is a list\n    assert.deepEqual(lp('[a:[1,2,3]]'), [{ a: [1, 2, 3] }])\n    assert.deepEqual(lp('[a:[[1],[2]]]'), [{ a: [[1], [2]] }])\n    // List element is a list (no pairs)\n    assert.deepEqual(lp('[[1,2],a:3]'), [[1, 2], { a: 3 }])\n    assert.deepEqual(lp('[a:1,[2,3],b:4]'), [{ a: 1 }, [2, 3], { b: 4 }])\n    // Nested list.pair applies recursively to inner lists\n    assert.deepEqual(lp('[a:[b:1]]'), [{ a: [{ b: 1 }] }])\n    assert.deepEqual(lp('[a:[b:1,c:2]]'), [{ a: [{ b: 1 }, { c: 2 }] }])\n    assert.deepEqual(lp('[a:{b:[c:1,d:2]}]'), [{ a: { b: [{ c: 1 }, { d: 2 }] } }])\n    // Deeply nested\n    assert.deepEqual(lp('[a:[b:[c:1]]]'), [{ a: [{ b: [{ c: 1 }] }] }])\n\n    // === Maps outside lists are unaffected by list.pair ===\n    assert.deepEqual(lp('{a:1}'), { a: 1 })\n    assert.deepEqual(lp('{a:1,b:2}'), { a: 1, b: 2 })\n    assert.deepEqual(lp('a:1'), { a: 1 })\n    assert.deepEqual(lp('a:1,b:2'), { a: 1, b: 2 })\n    assert.deepEqual(lp('a:b:c'), { a: { b: 'c' } })\n\n    // === Plain values in lists still work with list.pair ===\n    assert.deepEqual(lp('[1,2,3]'), [1, 2, 3])\n    assert.deepEqual(lp('[]'), [])\n    assert.deepEqual(lp('[true,false,null]'), [true, false, null])\n    assert.deepEqual(lp('[\"a\",\"b\"]'), ['a', 'b'])\n  })\n\n  it('list-child', () => {\n    tsvTestListChild('feature-list-child', { list: { child: true } })\n  })\n\n  it('list-child-pair', () => {\n    tsvTestListChild('feature-list-child-pair', { list: { child: true, pair: true } })\n  })\n\n  it('list-child-interaction', () => {\n    // === child=false (default): bare colon in list is an error ===\n    assert.throws(() => j('[:1]'), /unexpected/)\n    assert.throws(() => j('[1,:2]'), /unexpected/)\n\n    // === child=true, property=true (default): both work independently ===\n    let lc = j.make({ list: { child: true } })\n    // child$ is set as property, key:val is set as property\n    assert.deepEqual(JS(lc('[a:1,:2]')), '[]')\n    assert.deepEqual(lc('[a:1,:2]').a, 1)\n    assert.deepEqual(lc('[a:1,:2]')['child$'], 2)\n\n    // === child=true, property=false: child works, key:val errors ===\n    let lc_noprop = j.make({ list: { child: true, property: false } })\n    assert.deepEqual(lc_noprop('[:1]')['child$'], 1)\n    assert.throws(() => lc_noprop('[a:1]'), /unexpected/)\n\n    // === child=true, pair=true: pairs become elements, child$ still property ===\n    let lc_pair = j.make({ list: { child: true, pair: true } })\n    assert.deepEqual(JP(lc_pair('[a:1,:2]')), [{ a: 1 }])\n    assert.deepEqual(lc_pair('[a:1,:2]')['child$'], 2)\n    assert.deepEqual(JP(lc_pair('[:1,a:2]')), [{ a: 2 }])\n    assert.deepEqual(lc_pair('[:1,a:2]')['child$'], 1)\n\n    // === child=true, pair=true, property=false: all three options ===\n    let lc_all = j.make({ list: { child: true, pair: true, property: false } })\n    assert.deepEqual(JP(lc_all('[a:1,:2]')), [{ a: 1 }])\n    assert.deepEqual(lc_all('[a:1,:2]')['child$'], 2)\n\n    // === Nested lists: inner list child$ is independent ===\n    assert.deepEqual(JS(lc('[[:1]]')), '[[]]')\n    assert.deepEqual(lc('[[:1]]')[0]['child$'], 1)\n    assert.deepEqual(lc('[[:1]]')['child$'], undefined)\n\n    // === child$ merges with map.extend (default) ===\n    assert.deepEqual(lc('[:{a:1},:{b:2}]')['child$'], { a: 1, b: 2 })\n    assert.deepEqual(lc('[:{a:{x:1}},:{a:{y:2}}]')['child$'], { a: { x: 1, y: 2 } })\n\n    // === child$ without map.extend: last value wins ===\n    let lc_noext = j.make({ list: { child: true }, map: { extend: false } })\n    assert.deepEqual(lc_noext('[:{a:1},:{b:2}]')['child$'], { b: 2 })\n    assert.deepEqual(lc_noext('[:1,:2]')['child$'], 2)\n\n    // === Maps outside lists are unaffected by list.child ===\n    assert.deepEqual(lc('{a:1}'), { a: 1 })\n    assert.deepEqual(lc('a:1,b:2'), { a: 1, b: 2 })\n  })\n\n  it('list-child-deep', () => {\n    tsvTestListChild('feature-list-child-deep', { list: { child: true } })\n  })\n\n  it('list-child-pair-deep', () => {\n    tsvTestListChild('feature-list-child-pair-deep', { list: { child: true, pair: true } })\n  })\n\n  it('list-child-deep-multilevel', () => {\n    let lc = j.make({ list: { child: true } })\n    let lcp = j.make({ list: { child: true, pair: true } })\n\n    // === 2-level nesting: child at inner level only ===\n    let r1 = lc('[[:1]]')\n    assert.deepEqual(JS(r1), '[[]]')\n    assert.deepEqual(r1['child$'], undefined)\n    assert.deepEqual(r1[0]['child$'], 1)\n\n    // === 2-level: sibling lists with different child values ===\n    let r2 = lc('[[:1],[:2]]')\n    assert.deepEqual(JS(r2), '[[],[]]')\n    assert.deepEqual(r2['child$'], undefined)\n    assert.deepEqual(r2[0]['child$'], 1)\n    assert.deepEqual(r2[1]['child$'], 2)\n\n    // === 3-level nesting: child only at deepest level ===\n    let r3 = lc('[[[:1]]]')\n    assert.deepEqual(JS(r3), '[[[]]]')\n    assert.deepEqual(r3['child$'], undefined)\n    assert.deepEqual(r3[0]['child$'], undefined)\n    assert.deepEqual(r3[0][0]['child$'], 1)\n\n    // === 2-level: child at both levels ===\n    let r4 = lc('[[:1],:2]')\n    assert.deepEqual(JS(r4), '[[]]')\n    assert.deepEqual(r4['child$'], 2)\n    assert.deepEqual(r4[0]['child$'], 1)\n\n    // === 3-level: child at every level ===\n    let r5 = lc('[[[:1],:2],:3]')\n    assert.deepEqual(JS(r5), '[[[]]]')\n    assert.deepEqual(r5['child$'], 3)\n    assert.deepEqual(r5[0]['child$'], 2)\n    assert.deepEqual(r5[0][0]['child$'], 1)\n\n    // === child value is a list which itself has child ===\n    let r6 = lc('[:[:1]]')\n    assert.deepEqual(JS(r6), '[]')\n    assert.deepEqual(JS(r6['child$']), '[]')\n    assert.deepEqual(r6['child$']['child$'], 1)\n\n    // === 3 levels deep via child-as-value chaining ===\n    let r7 = lc('[:[:[:1]]]')\n    assert.deepEqual(JS(r7), '[]')\n    assert.deepEqual(JS(r7['child$']), '[]')\n    assert.deepEqual(JS(r7['child$']['child$']), '[]')\n    assert.deepEqual(r7['child$']['child$']['child$'], 1)\n\n    // === child value is list with elements and child ===\n    let r8 = lc('[1,:[:2,3]]')\n    assert.deepEqual(JS(r8), '[1]')\n    assert.deepEqual(JS(r8['child$']), '[3]')\n    assert.deepEqual(r8['child$']['child$'], 2)\n\n    // === Mixed elements with deep child ===\n    let r9 = lc('[1,[2,[3,:4]]]')\n    assert.deepEqual(JS(r9), '[1,[2,[3]]]')\n    assert.deepEqual(r9['child$'], undefined)\n    assert.deepEqual(r9[1]['child$'], undefined)\n    assert.deepEqual(r9[1][1]['child$'], 4)\n\n    // === Deep with multiple children at inner level (last wins) ===\n    let r10 = lc('[1,[:2,:3],4,:5]')\n    assert.deepEqual(JS(r10), '[1,[],4]')\n    assert.deepEqual(r10['child$'], 5)\n    assert.deepEqual(r10[1]['child$'], 3)\n\n    // === Sibling lists: one with child, one without ===\n    let r11 = lc('[[1,2],[:3]]')\n    assert.deepEqual(JS(r11), '[[1,2],[]]')\n    assert.deepEqual(r11['child$'], undefined)\n    assert.deepEqual(r11[0]['child$'], undefined)\n    assert.deepEqual(r11[1]['child$'], 3)\n\n    // === Inner child$ merges objects ===\n    let r12 = lc('[[:{a:1},:{b:2}]]')\n    assert.deepEqual(JS(r12), '[[]]')\n    assert.deepEqual(r12['child$'], undefined)\n    assert.deepEqual(r12[0]['child$'], { a: 1, b: 2 })\n\n    // === Map containing list with child$ ===\n    let r13 = lc('{x:[:1,2]}')\n    assert.deepEqual(JS(r13), '{\"x\":[2]}')\n    assert.deepEqual(r13.x['child$'], 1)\n\n    // === Nested maps containing lists with child$ ===\n    let r14 = lc('{x:{y:[:1,2]}}')\n    assert.deepEqual(JS(r14), '{\"x\":{\"y\":[2]}}')\n    assert.deepEqual(r14.x.y['child$'], 1)\n\n    // === Array of maps each containing lists with child$ ===\n    let r15 = lc('[{a:[:1]},{b:[:2]}]')\n    assert.deepEqual(JS(r15), '[{\"a\":[]},{\"b\":[]}]')\n    assert.deepEqual(r15[0].a['child$'], 1)\n    assert.deepEqual(r15[1].b['child$'], 2)\n\n    // === pair+child at multiple levels ===\n    let r16 = lcp('[[a:1,:2]]')\n    assert.deepEqual(JS(r16), '[[{\"a\":1}]]')\n    assert.deepEqual(r16['child$'], undefined)\n    assert.deepEqual(r16[0]['child$'], 2)\n\n    // === pair+child: child at both levels ===\n    let r17 = lcp('[a:1,[b:2,:3],:4]')\n    assert.deepEqual(JS(r17), '[{\"a\":1},[{\"b\":2}]]')\n    assert.deepEqual(r17['child$'], 4)\n    assert.deepEqual(r17[1]['child$'], 3)\n\n    // === pair+child: 3-level child at every level ===\n    let r18 = lcp('[[[:5],:6],:7]')\n    assert.deepEqual(JS(r18), '[[[]]]')\n    assert.deepEqual(r18['child$'], 7)\n    assert.deepEqual(r18[0]['child$'], 6)\n    assert.deepEqual(r18[0][0]['child$'], 5)\n\n    // === pair+child: sibling inner lists with independent child values ===\n    let r19 = lcp('[[a:1,:2],[b:3,:4]]')\n    assert.deepEqual(JS(r19), '[[{\"a\":1}],[{\"b\":3}]]')\n    assert.deepEqual(r19['child$'], undefined)\n    assert.deepEqual(r19[0]['child$'], 2)\n    assert.deepEqual(r19[1]['child$'], 4)\n\n    // === pair+child: inner list with multiple pairs and child ===\n    let r20 = lcp('[a:1,[b:2,c:3,:4]]')\n    assert.deepEqual(JS(r20), '[{\"a\":1},[{\"b\":2},{\"c\":3}]]')\n    assert.deepEqual(r20['child$'], undefined)\n    assert.deepEqual(r20[1]['child$'], 4)\n  })\n\n  it('map-child', () => {\n    tsvTestWith('feature-map-child', { map: { child: true } })\n  })\n\n  it('map-child-deep', () => {\n    tsvTestWith('feature-map-child-deep', { map: { child: true }, list: { child: true } })\n  })\n\n  it('map-child-interaction', () => {\n    // === child=false (default): bare colon in map is an error ===\n    assert.throws(() => j('{:1}'), /unexpected/)\n    assert.throws(() => j('{:1,a:2}'), /unexpected/)\n\n    // === child=true: bare colon stores child$ ===\n    let mc = j.make({ map: { child: true } })\n    assert.deepEqual(mc('{:1,a:2}'), { child$: 1, a: 2 })\n    assert.deepEqual(mc('{:1,a:2}')['child$'], 1)\n\n    // === child$ merge: objects deep-merge, primitives last-wins ===\n    assert.deepEqual(mc('{:1,:2}')['child$'], 2)\n    assert.deepEqual(mc('{:{a:1},:{b:2}}')['child$'], { a: 1, b: 2 })\n    assert.deepEqual(mc('{:{a:{x:1}},:{a:{y:2}}}')['child$'], { a: { x: 1, y: 2 } })\n\n    // === child$ without map.extend: last value wins ===\n    let mc_noext = j.make({ map: { child: true, extend: false } })\n    assert.deepEqual(mc_noext('{:{a:1},:{b:2}}')['child$'], { b: 2 })\n    assert.deepEqual(mc_noext('{:1,:2}')['child$'], 2)\n\n    // === Lists outside maps are unaffected by map.child ===\n    assert.deepEqual(mc('[1,2,3]'), [1, 2, 3])\n\n    // === Nested maps: child$ at each level ===\n    let r1 = mc('{:1,a:{:2,b:{:3}}}')\n    assert.deepEqual(r1['child$'], 1)\n    assert.deepEqual(r1.a['child$'], 2)\n    assert.deepEqual(r1.a.b['child$'], 3)\n\n    // === map.child + list.child both enabled ===\n    let both = j.make({ map: { child: true }, list: { child: true } })\n\n    // Map with child, containing list with child\n    let r2 = both('{a:[:1,2],:3}')\n    assert.deepEqual(r2['child$'], 3)\n    assert.deepEqual(r2.a['child$'], 1)\n    assert.deepEqual(JS(r2.a), '[2]')\n\n    // List with child, containing map with child\n    let r3 = both('[{:1,a:2},:3]')\n    assert.deepEqual(r3['child$'], 3)\n    assert.deepEqual(r3[0]['child$'], 1)\n    assert.deepEqual(JS(r3[0]), '{\"child$\":1,\"a\":2}')\n\n    // Deep: map -> list -> map, each with child$\n    let r4 = both('{a:[{:1}],:2}')\n    assert.deepEqual(r4['child$'], 2)\n    assert.deepEqual(r4.a[0]['child$'], 1)\n\n    // List child value is a map with child$\n    let r5 = both('[:{ a: 1 }]')\n    assert.deepEqual(JS(r5), '[]')\n    assert.deepEqual(r5['child$'], { a: 1 })\n\n    // 3-level: map -> map -> list -> map with child at each\n    let r6 = both('{:1,x:{:2,y:[{:3}]}}')\n    assert.deepEqual(r6['child$'], 1)\n    assert.deepEqual(r6.x['child$'], 2)\n    assert.deepEqual(r6.x.y[0]['child$'], 3)\n\n    // Array of maps, each with their own child$\n    let r7 = both('[{:1,a:10},{:2,b:20}]')\n    assert.deepEqual(r7[0]['child$'], 1)\n    assert.deepEqual(r7[1]['child$'], 2)\n\n    // map.child only (no list.child): list bare colon still errors\n    assert.throws(() => mc('[:1]'), /unexpected/)\n\n    // list.child only (no map.child): map bare colon still errors\n    let lc = j.make({ list: { child: true } })\n    assert.throws(() => lc('{:1}'), /unexpected/)\n\n    // Implicit map with child$\n    assert.deepEqual(mc('a:1,:2'), { a: 1, child$: 2 })\n    assert.deepEqual(mc('a:1,:2,b:3'), { a: 1, child$: 2, b: 3 })\n  })\n\n  // Test derived from debug sessions using quick.js\n  it('debug-cases', () => {\n    tsvTest('feature-debug-cases')\n  })\n})\n\nfunction match(src, pat, ctx) {\n  ctx = ctx || {}\n  ctx.loc = ctx.loc || '$'\n\n  if (src === pat) return\n  if (false !== ctx.miss && undefined === pat) return\n\n  if (Array.isArray(src) && Array.isArray(pat)) {\n    if (false === ctx.miss && src.length !== pat.length) {\n      return ctx.loc + '/len:' + src.length + '!=' + pat.length\n    }\n\n    let m = undefined\n    for (let i = 0; i < pat.length; i++) {\n      m = match(src[i], pat[i], { ...ctx, loc: ctx.loc + '[' + i + ']' })\n      if (m) {\n        return m\n      }\n    }\n\n    return\n  } else if ('object' === typeof src && 'object' === typeof pat) {\n    let ksrc = Object.keys(src).sort()\n    let kpat = Object.keys(pat).sort()\n\n    if (false === ctx.miss && ksrc.length !== kpat.length) {\n      return ctx.loc + '/key:{' + ksrc + '}!={' + kpat + '}'\n    }\n\n    for (let i = 0; i < kpat.length; i++) {\n      if (false === ctx.miss && ksrc[i] !== kpat[i])\n        return ctx.loc + '/key:' + kpat[i]\n\n      let m = match(src[kpat[i]], pat[kpat[i]], {\n        ...ctx,\n        loc: ctx.loc + '.' + kpat[i],\n      })\n      if (m) {\n        return m\n      }\n    }\n\n    return\n  }\n\n  return ctx.loc + '/val:' + src + '!=' + pat\n}\n"
  },
  {
    "path": "test/first-version-perf.js",
    "content": "// NOTE: the perf test used in the first version, reused against this version.\n// *Not* a test of the perf of the first verison!\n\nvar j = require('..').Jsonic\n\nfunction pv_perf(dur) {\n  var input =\n    'int:100,dec:9.9,t:true,f:false,qs:' +\n    '\"a\\\\\"a\\'a\",as:\\'a\"a\\\\\\'a\\',a:{b:{c:1}}'\n\n  // warm up\n  var start = Date.now(),\n    count = 0\n  while (Date.now() - start < dur) {\n    j(input)\n  }\n\n  ;(start = Date.now()), (count = 0)\n  while (Date.now() - start < dur) {\n    j(input)\n    count++\n  }\n\n  console.log('parse/sec: ' + count * (1000 / dur))\n}\n\nif (require.main === module) {\n  pv_perf(1000)\n}\n\nmodule.exports = pv_perf\n"
  },
  {
    "path": "test/first-version.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, Lexer } = require('..')\nconst { loadTSV } = require('./utility')\nconst pv_perf = require('./first-version-perf')\n\nlet j = Jsonic\n\nfunction tsvTest(name) {\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    try {\n      assert.deepEqual(Jsonic(input), JSON.parse(expected))\n    } catch (err) {\n      err.message = `${name} row ${row}: input=${input} expected=${expected}\\n${err.message}`\n      throw err\n    }\n  }\n}\n\n// All the tests from the first version.\ndescribe('first-version', function () {\n  it('fv-works', function () {\n    tsvTest('fv-works')\n  })\n\n  it('fv-funky-input', function () {\n    // Object values are just returned\n    assert.deepEqual('{\"foo\":1,\"bar\":\"zed\"}', \n      JSON.stringify(j({ foo: 1, bar: 'zed' })),\n    )\n\n    assert.deepEqual('[\"a\",\"b\"]', JSON.stringify(j(['a', 'b'])))\n\n    // DIFF a: -> {a:null}\n    assert.deepEqual(j('a:'), { a: null })\n\n    // DIFF b:\\n -> {b:null}\n    assert.deepEqual(j('b:\\n'), { b: null })\n\n    // DIFF c:\\r => {c:null}\n    assert.deepEqual(j('c:\\r'), { c: null })\n  })\n\n  it('fv-types', function () {\n    tsvTest('fv-types')\n  })\n\n  it('fv-subobj', function () {\n    tsvTest('fv-subobj')\n  })\n\n  it('fv-comma', function () {\n    tsvTest('fv-comma')\n  })\n\n  it('fv-empty', function () {\n    // DIFF expect(j(\"\")).equal('{}')\n  })\n\n  it('fv-arrays', function () {\n    tsvTest('fv-arrays')\n  })\n\n  it('fv-deep', function () {\n    tsvTest('fv-deep')\n  })\n\n  it('fv-strings', function () {\n    assert.deepEqual(j('a:\\'\\',b:\"\"'), { a: '', b: '' })\n\n    assert.deepEqual(\n      j(\n        \"a:'x', aa: 'x' , b:'y\\\"z', bb: 'y\\\"z' ,bbb:\\\"y'z\\\", bbbb: \\\"y'z\\\", c:\\\"\\\\n\\\", d:'\\\\n'\",\n      ), {\n      a: 'x',\n      aa: 'x',\n      b: 'y\"z',\n      bb: 'y\"z',\n      bbb: \"y'z\",\n      bbbb: \"y'z\",\n      c: '\\n',\n      d: '\\n',\n    })\n  })\n\n  it('fv-numbers', function () {\n    tsvTest('fv-numbers')\n  })\n\n  it('fv-drop-outs', function () {\n    tsvTest('fv-drop-outs')\n  })\n\n  /*\n  it( 'pv-bad', function(){\n    try { jsonic('{');\n          expect('bad-{').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('}');\n          expect('bad-}').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('a');\n          expect('bad-a').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('!');\n          expect('bad-!').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('0');\n          expect('bad-0').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('a:,');\n          expect('bad-a:,').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('\\\\');\n          expect('bad-\\\\').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('\"');\n          expect('bad-\"').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('\"\"');\n          expect('bad-\"\"').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('a:{,');\n          expect('bad-a:{,').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('a:,}');\n          expect('bad-a:,}').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('a:');\n          expect('bad-a:,}').toBe('FAIL') } catch(e) {}\n\n    try { jsonic('a:\"\\\"\"');\n          expect('bad-a:\"\\\"\"').toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:'\\''\");\n          expect(\"bad-a:'\\''\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{{}}\");\n          expect(\"bad-a:{{}}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{}}\");\n          expect(\"bad-a:{}}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{[]}\");\n          expect(\"bad-a:{[]}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{[}\");\n          expect(\"bad-a:{[}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{]}\");\n          expect(\"bad-a:{]}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{a}\");\n          expect(\"bad-a:{a}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{a,b}\");\n          expect(\"bad-a:{a,b}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{a:1,b}\");\n          expect(\"bad-a:{a:1,b}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{a:1,b:}\");\n          expect(\"bad-a:{a:1,b:}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{a:1,b:,}\");\n          expect(\"bad-a:{a:1,b:,}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"a:{a:1,b:]}\");\n          expect(\"bad-a:{a:1,b:]}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"[\");\n          expect(\"bad-[\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"{\");\n          expect(\"bad-{\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"}\");\n          expect(\"bad-}\").toBe('FAIL') } catch(e) {}\n\n    try { jsonic(\"]\");\n          expect(\"bad-]\").toBe('FAIL') } catch(e) {}\n\n\n  })\n*/\n\n  it('fv-json', function () {\n    var js = JSON.stringify\n    var jp = JSON.parse\n    var x, g\n\n    x = '{}'\n    g = js(jp(x))\n    assert.deepEqual(js(j(x)), g)\n\n    x = ' \\r\\n\\t{ \\r\\n\\t} \\r\\n\\t'\n    g = js(jp(x))\n    assert.deepEqual(js(j(x)), g)\n\n    x = ' \\r\\n\\t{ \\r\\n\\t\"a\":1 \\r\\n\\t} \\r\\n\\t'\n    g = js(jp(x))\n    assert.deepEqual(js(j(x)), g)\n\n    x = '{\"a\":[[{\"b\":1}],{\"c\":[{\"d\":1}]}]}'\n    g = js(jp(x))\n    assert.deepEqual(js(j(x)), g)\n\n    x = '[' + x + ']'\n    g = js(jp(x))\n    assert.deepEqual(js(j(x)), g)\n  })\n\n  // NOTE: coverage tracing slows this down - a lot!\n  it('fv-performance', function () {\n    // {timeout:3333},\n    if (null == process.env.JSONIC_TEST_SKIP_PERF) {\n      pv_perf(200)\n    }\n  })\n})\n"
  },
  {
    "path": "test/foo.jsonic",
    "content": "bar:1"
  },
  {
    "path": "test/grammar/arith-leftrec.bnf",
    "content": "; Same arithmetic language as arith.bnf, in the natural\n; left-recursive form. The converter rewrites direct left recursion\n; automatically (P = P a / b  =>  P = b *(a)).\nexpr   = expr \"+\" term / expr \"-\" term / term\nterm   = term \"*\" factor / term \"/\" factor / factor\nfactor = \"(\" expr \")\" / number\nnumber = 1*DIGIT\n"
  },
  {
    "path": "test/grammar/arith.bnf",
    "content": "; Arithmetic grammar, stratified to avoid left recursion.\n; Uses the RFC 5234 core rule DIGIT — auto-included by the\n; converter when referenced but not locally defined.\nexpr   = term *(\"+\" term / \"-\" term)\nterm   = factor *(\"*\" factor / \"/\" factor)\nfactor = \"(\" expr \")\" / number\nnumber = 1*DIGIT\n"
  },
  {
    "path": "test/grammar/greet.bnf",
    "content": "; Simple BNF fixture: alternation of terminal strings.\ngreet = \"hi\" / \"hello\"\n"
  },
  {
    "path": "test/grammar/json-subset.bnf",
    "content": "; Minimal JSON-like subset: digits, nested objects, arrays.\n; Names / strings are simplified to single-letter terminals until\n; ABNF %x ranges arrive.\nvalue  = digit / letter / object / array\nobject = \"{\" [ member *(\",\" member) ] \"}\"\nmember = letter \":\" value\narray  = \"[\" [ value *(\",\" value) ] \"]\"\ndigit  = \"0\" / \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\"\nletter = \"a\" / \"b\" / \"c\"\n"
  },
  {
    "path": "test/grammar/pair.bnf",
    "content": "; Simple BNF fixture: two-terminal sequence.\npair = \"a\" \"b\"\n"
  },
  {
    "path": "test/grammar/rfc3986-uri.abnf",
    "content": "; RFC 3986 Appendix A — Collected ABNF for URI\n; https://www.rfc-editor.org/rfc/rfc3986.txt\n;\n; Notes on differences from the RFC text:\n;   - `path-empty = 0<pchar>` in the RFC is replaced by the\n;     equivalent empty sequence (0*pchar), since <pchar> is RFC 5234\n;     prose-val notation that has no direct equivalent here.\n;   - `path = path-abempty / path-absolute / …` moved to not be\n;     re-declared as an intermediate rule — URI itself picks one of\n;     the hier-part / relative-part alternatives already.\n;   - `absolute-URI`, `URI-reference`, `relative-ref`,\n;     `relative-part` omitted; we only exercise `URI`.\n\nURI           = scheme \":\" hier-part [ \"?\" query ] [ \"#\" fragment ]\n\nhier-part     = \"//\" authority path-abempty\n              / path-absolute\n              / path-rootless\n              / path-empty\n\nscheme        = ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" )\n\nauthority     = [ userinfo \"@\" ] host [ \":\" port ]\nuserinfo      = *( unreserved / pct-encoded / sub-delims / \":\" )\nhost          = IP-literal / IPv4address / reg-name\nport          = *DIGIT\n\nIP-literal    = \"[\" ( IPv6address / IPvFuture ) \"]\"\n\nIPvFuture     = \"v\" 1*HEXDIG \".\" 1*( unreserved / sub-delims / \":\" )\n\nIPv6address   =                              6( h16 \":\" ) ls32\n              /                         \"::\" 5( h16 \":\" ) ls32\n              / [               h16 ] \"::\" 4( h16 \":\" ) ls32\n              / [ *1( h16 \":\" ) h16 ] \"::\" 3( h16 \":\" ) ls32\n              / [ *2( h16 \":\" ) h16 ] \"::\" 2( h16 \":\" ) ls32\n              / [ *3( h16 \":\" ) h16 ] \"::\"    h16 \":\"   ls32\n              / [ *4( h16 \":\" ) h16 ] \"::\"                ls32\n              / [ *5( h16 \":\" ) h16 ] \"::\"                h16\n              / [ *6( h16 \":\" ) h16 ] \"::\"\n\nh16           = 1*4HEXDIG\nls32          = ( h16 \":\" h16 ) / IPv4address\nIPv4address   = dec-octet \".\" dec-octet \".\" dec-octet \".\" dec-octet\n\ndec-octet     = DIGIT                 ; 0-9\n              / %x31-39 DIGIT         ; 10-99\n              / \"1\" 2DIGIT            ; 100-199\n              / \"2\" %x30-34 DIGIT     ; 200-249\n              / \"25\" %x30-35          ; 250-255\n\nreg-name      = *( unreserved / pct-encoded / sub-delims )\n\npath-abempty  = *( \"/\" segment )\npath-absolute = \"/\" [ segment-nz *( \"/\" segment ) ]\npath-rootless = segment-nz *( \"/\" segment )\npath-empty    = \"\"\n\nsegment       = *pchar\nsegment-nz    = 1*pchar\n\npchar         = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\n\nquery         = *( pchar / \"/\" / \"?\" )\n\nfragment      = *( pchar / \"/\" / \"?\" )\n\npct-encoded   = \"%\" HEXDIG HEXDIG\n\nunreserved    = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\nsub-delims    = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\"\n              / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n"
  },
  {
    "path": "test/grammar.test.js",
    "content": "/* Copyright (c) 2013-2024 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\n\ndescribe('grammar-options', () => {\n\n  it('plain-options-string-escape', () => {\n    // Grammar spec with options that configure string escape sequences.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        string: {\n          escape: { z: 'Z' },\n        },\n      },\n    })\n\n    // Custom escape \\z should produce Z.\n    assert.deepEqual(j('a:\"x\\\\zy\"'), { a: 'xZy' })\n    // Standard escapes still work.\n    assert.deepEqual(j('a:\"x\\\\ny\"'), { a: 'x\\ny' })\n  })\n\n\n  it('plain-options-comment-lex', () => {\n    // Disable comment lexing via grammar options.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        comment: { lex: false },\n      },\n    })\n\n    // Hash is no longer a comment, so it becomes part of text.\n    assert.deepEqual(j('a:1,b:2'), { a: 1, b: 2 })\n  })\n\n\n  it('plain-options-value-def', () => {\n    // Define custom value literals.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        value: {\n          def: {\n            yes: { val: true },\n            no: { val: false },\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:yes,b:no,c:1'), { a: true, b: false, c: 1 })\n  })\n\n\n  it('plain-options-map-extend', () => {\n    // Disable map extend (duplicate keys overwrite).\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        map: { extend: false },\n      },\n    })\n\n    assert.deepEqual(j('a:1,a:2'), { a: 2 })\n  })\n\n\n  it('options-with-funcref-map-merge', () => {\n    // Use FuncRef for the map.merge function.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@addMerge': (prev, curr) => {\n          if ('number' === typeof prev && 'number' === typeof curr) {\n            return prev + curr\n          }\n          return curr\n        },\n      },\n      options: {\n        map: { merge: '@addMerge' },\n      },\n    })\n\n    // Duplicate numeric keys should be summed.\n    assert.deepEqual(j('a:1,a:2'), { a: 3 })\n    // Non-numeric values still overwrite.\n    assert.deepEqual(j('b:x,b:y'), { b: 'y' })\n  })\n\n\n  it('options-with-funcref-parse-prepare', () => {\n    // Use FuncRef for parse.prepare to inject context metadata.\n    let j = Jsonic.make()\n    let preparedWith = null\n\n    j.grammar({\n      ref: {\n        '@tracker': (_jsonic, ctx, _meta) => {\n          preparedWith = ctx.meta.tag || 'default'\n        },\n      },\n      options: {\n        parse: {\n          prepare: {\n            tracker: '@tracker',\n          },\n        },\n      },\n    })\n\n    j('a:1')\n    assert.equal(preparedWith, 'default')\n\n    j('b:2', { tag: 'test-run' })\n    assert.equal(preparedWith, 'test-run')\n  })\n\n\n  it('options-and-rules-combined', () => {\n    // Grammar spec with both options and rules in a single call.\n    let j = Jsonic.make()\n\n    j.grammar({\n      ref: {\n        '@upper': (r) => {\n          if ('string' === typeof r.node) {\n            r.node = r.node.toUpperCase()\n          }\n        },\n      },\n      options: {\n        value: {\n          def: {\n            on: { val: true },\n            off: { val: false },\n          },\n        },\n      },\n      rule: {\n        val: {\n          close: [\n            { a: '@upper', g: 'custom' },\n          ],\n        },\n      },\n    })\n\n    // Custom values from options should work.\n    assert.deepEqual(j('a:on'), { a: true })\n    assert.deepEqual(j('a:off'), { a: false })\n    // String values get uppercased by the rule action.\n    assert.deepEqual(j('a:hello'), { a: 'HELLO' })\n  })\n\n\n  it('options-fixed-tokens', () => {\n    // Add custom fixed tokens via grammar options.\n    let j = Jsonic.make()\n\n    j.grammar({\n      options: {\n        fixed: {\n          token: {\n            '#ARROW': '=>',\n          },\n        },\n      },\n    })\n\n    let ARROW = j.token('#ARROW')\n    assert.equal('number', typeof ARROW)\n\n    // The fixed token should be recognized by the lexer.\n    j.rule('val', (rs) => {\n      rs.open([\n        { s: [ARROW], a: (r) => { r.node = '<arrow>' } },\n      ])\n    })\n\n    assert.deepEqual(j('a:=>'), { a: '<arrow>' })\n  })\n\n\n  it('options-number-hex', () => {\n    // Enable hex number parsing via grammar options.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        number: { hex: true },\n      },\n    })\n\n    assert.deepEqual(j('a:0xFF'), { a: 255 })\n  })\n\n\n  it('options-safe-key', () => {\n    // Disable safe key mode via grammar options.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        safe: { key: false },\n      },\n    })\n\n    // With safe key disabled, __proto__ should be a regular key.\n    let r = j('__proto__:1')\n    assert.equal(r.__proto__, 1)\n  })\n\n\n  it('options-rule-start', () => {\n    // Set rule.start to change the starting rule via grammar options.\n    let j = Jsonic.make()\n\n    j.rule('myval', (rs) => {\n      rs.open([\n        { s: '#NR', a: (r) => { r.node = r.o0.val * 10 } },\n      ]).close([\n        { s: '#ZZ' },\n      ])\n    })\n\n    j.grammar({\n      options: {\n        rule: { start: 'myval' },\n      },\n    })\n\n    assert.equal(j('5'), 50)\n  })\n\n\n  it('options-tokenset', () => {\n    // Configure tokenSet via grammar options to restrict KEY to strings only.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        tokenSet: {\n          KEY: ['#ST', null, null, null],\n        },\n      },\n    })\n\n    // String key should work.\n    assert.deepEqual(j('\"a\":1'), { a: 1 })\n    // Unquoted text key should fail since #TX is no longer in KEY.\n    assert.throws(() => j('a:1'), /unexpected/)\n  })\n\n\n  it('options-funcref-unresolved-passthrough', () => {\n    // FuncRef strings that don't match any ref entry pass through as-is.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        tag: 'test-tag',\n      },\n    })\n\n    assert.equal(j.options.tag, 'test-tag')\n  })\n\n\n  it('options-nested-funcref', () => {\n    // FuncRef resolution should work at any nesting depth.\n    let j = Jsonic.make()\n    let checkCalled = false\n\n    j.grammar({\n      ref: {\n        '@myCheck': (tkn) => {\n          checkCalled = true\n          return tkn\n        },\n      },\n      options: {\n        text: {\n          check: '@myCheck',\n        },\n      },\n    })\n\n    j('a:1')\n    assert.equal(checkCalled, true)\n  })\n\n\n  it('options-string-replace-char', () => {\n    // Configure single-character string replacement via grammar options.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        string: {\n          replace: {\n            X: 'Y',\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:\"aXb\"'), { a: 'aYb' })\n  })\n\n\n  it('options-multiple-grammar-calls', () => {\n    // Multiple grammar calls should layer options.\n    let j = Jsonic.make()\n\n    j.grammar({\n      options: {\n        value: {\n          def: {\n            yes: { val: true },\n          },\n        },\n      },\n    })\n\n    j.grammar({\n      options: {\n        value: {\n          def: {\n            no: { val: false },\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:yes,b:no'), { a: true, b: false })\n  })\n\n\n  it('options-only-no-rules', () => {\n    // Grammar spec with only options and no rules should work.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        number: { sep: '_' },\n      },\n    })\n\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n  })\n\n\n  it('options-funcref-in-array', () => {\n    // FuncRef resolution should work inside arrays.\n    let modified = false\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@mod': (val, _lex, _rule) => {\n          modified = true\n          return val\n        },\n      },\n      options: {\n        text: {\n          modify: '@mod',\n        },\n      },\n    })\n\n    j('a:hello')\n    assert.equal(modified, true)\n  })\n\n\n  it('options-regex-match-token', () => {\n    // Use @/…/ to specify a RegExp for a custom match token.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        match: {\n          token: {\n            '#ID': '@/^[a-zA-Z_][a-zA-Z_0-9]*/',\n          },\n        },\n        tokenSet: {\n          KEY: ['#ST', '#ID', null, null],\n          VAL: [, , , , '#ID'],\n        },\n      },\n    })\n\n    // 'a' matches #ID and #ID is in KEY, so a:1 parses as a pair.\n    assert.deepEqual(j('a:1'), { a: 1 })\n    assert.deepEqual(j('foo:bar'), { foo: 'bar' })\n    // 'a*' does not match #ID, and #TX is not in KEY, so this throws.\n    assert.throws(() => j('a*:1'), /unexpected/)\n  })\n\n\n  it('options-regex-number-exclude', () => {\n    // Use @/…/ for number.exclude to reject leading-zero numbers.\n    // Note: ^0[0-9]+$ properly matches \"01\", \"023\" etc. but not \"0\" alone.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        number: {\n          exclude: '@/^0[0-9]+$/',\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:0'), { a: 0 })\n    assert.deepEqual(j('a:01'), { a: '01' })\n    assert.deepEqual(j('a:123'), { a: 123 })\n  })\n\n\n  it('options-regex-value-match', () => {\n    // Use @/…/ in value.def with a FuncRef val for regex-matched values.\n    // When value.def has a `match` RegExp, `val` must be a function.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@valOn': () => true,\n        '@valOff': () => false,\n      },\n      options: {\n        value: {\n          def: {\n            on: { val: '@valOn', match: '@/^on$/i' },\n            off: { val: '@valOff', match: '@/^off$/i' },\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:ON,b:Off,c:1'), { a: true, b: false, c: 1 })\n    assert.deepEqual(j('a:on,b:OFF'), { a: true, b: false })\n  })\n\n\n  it('options-regex-with-flags', () => {\n    // Verify regex flags are passed through correctly.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@valYes': () => 'YES!',\n      },\n      options: {\n        value: {\n          def: {\n            yes: { val: '@valYes', match: '@/^yes$/i' },\n          },\n        },\n      },\n    })\n\n    // The /i flag makes it case-insensitive.\n    assert.deepEqual(j('a:YES'), { a: 'YES!' })\n    assert.deepEqual(j('a:Yes'), { a: 'YES!' })\n    assert.deepEqual(j('a:yes'), { a: 'YES!' })\n  })\n\n\n  it('options-regex-no-flags', () => {\n    // Verify @/…/ without flags produces a flagless (case-sensitive) RegExp.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        number: {\n          exclude: '@/^0[0-9]+$/',\n        },\n      },\n    })\n\n    // Without flags, the regex is case-sensitive (irrelevant for numbers,\n    // but tests the no-flags code path).\n    assert.deepEqual(j('a:0'), { a: 0 })\n    assert.deepEqual(j('a:42'), { a: 42 })\n    assert.deepEqual(j('a:01'), { a: '01' })\n  })\n\n\n  it('options-regex-mixed-with-funcref', () => {\n    // Both @/…/ regex and @name FuncRef in the same grammar spec.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@prepend': (prev, curr) => {\n          if ('string' === typeof prev && 'string' === typeof curr) {\n            return prev + curr\n          }\n          return curr\n        },\n      },\n      options: {\n        map: { merge: '@prepend' },\n        number: {\n          exclude: '@/^0[0-9]+/',\n        },\n      },\n    })\n\n    // FuncRef merge: duplicate keys concatenate.\n    assert.deepEqual(j('a:x,a:y'), { a: 'xy' })\n    // RegExp exclude: leading-zero numbers become text.\n    assert.deepEqual(j('a:007'), { a: '007' })\n    assert.deepEqual(j('a:42'), { a: 42 })\n  })\n\n\n  it('options-regex-in-array', () => {\n    // @/…/ resolution should work inside arrays.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@valT': () => true,\n        '@valF': () => false,\n      },\n      options: {\n        value: {\n          def: {\n            t: { val: '@valT', match: '@/^t$/i' },\n            f: { val: '@valF', match: '@/^f$/i' },\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(j('[T, F, 1]'), [true, false, 1])\n  })\n\n\n  it('options-regex-match-value', () => {\n    // Use @/…/ in match.value for regexp-based value matching.\n    // match.value runs against the forward source, so no $ anchor.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@valOn': () => true,\n        '@valOff': () => false,\n      },\n      options: {\n        match: {\n          value: {\n            on: { match: '@/^on/i', val: '@valOn' },\n            off: { match: '@/^off/i', val: '@valOff' },\n          },\n        },\n      },\n    })\n\n    assert.deepEqual(j('a:ON,b:off'), { a: true, b: false })\n    assert.deepEqual(j('a:on'), { a: true })\n  })\n\n\n  it('options-regex-resolve-nested', () => {\n    // @/…/ resolution works inside nested option objects.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@valYes': () => 'yes!',\n      },\n      options: {\n        value: {\n          def: {\n            y: { val: '@valYes', match: '@/^y$/i' },\n          },\n        },\n        number: {\n          exclude: '@/^0[0-9]+$/',\n        },\n      },\n    })\n\n    // Both nested @/…/ patterns resolve correctly.\n    assert.deepEqual(j('a:Y'), { a: 'yes!' })\n    assert.deepEqual(j('a:01'), { a: '01' })\n    assert.deepEqual(j('a:42'), { a: 42 })\n  })\n\n\n  it('options-escape-at-prefix', () => {\n    // @@ escapes to a literal @ string.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        tag: '@@my-tag',\n      },\n    })\n\n    assert.equal(j.options.tag, '@my-tag')\n  })\n\n\n  it('options-escape-at-regex-like', () => {\n    // @@ prevents @/…/ from being interpreted as a regex.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        tag: '@@/not-a-regex/',\n      },\n    })\n\n    assert.equal(j.options.tag, '@/not-a-regex/')\n  })\n\n\n  it('options-escape-at-funcref-like', () => {\n    // @@ prevents @name from being interpreted as a FuncRef.\n    let j = Jsonic.make()\n    j.grammar({\n      ref: {\n        '@myFunc': () => 'resolved',\n      },\n      options: {\n        tag: '@@myFunc',\n      },\n    })\n\n    assert.equal(j.options.tag, '@myFunc')\n  })\n\n\n  it('options-escape-at-nested', () => {\n    // @@ escape works inside nested objects and arrays.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        error: {\n          my_error: '@@special: something went wrong',\n        },\n      },\n    })\n\n    assert.equal(j.options.error.my_error, '@special: something went wrong')\n  })\n\n\n  it('grammar-text-string', () => {\n    // grammar() accepts a string, parsing it internally.\n    let j = Jsonic.make()\n    j.grammar('options: { number: { sep: \"_\" } }')\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n  })\n\n\n  it('grammar-text-string-number-exclude', () => {\n    // grammar() with string can use @/<regexp>/ declarative form.\n    let j = Jsonic.make()\n    j.grammar(`options: { number: { exclude: '@/^0[0-9]+$/' } }`)\n    assert.deepEqual(j('a:01'), { a: '01' })\n    assert.deepEqual(j('a:42'), { a: 42 })\n  })\n\n\n  it('grammar-text-string-empty', () => {\n    // Empty/whitespace grammar text is a no-op, not a crash.\n    let j = Jsonic.make()\n    j.grammar('')\n    j.grammar('  ')\n    j.grammar('# just a comment')\n    assert.deepEqual(j('a:1'), { a: 1 })\n  })\n\n\n  it('grammar-text-string-options-and-rules', () => {\n    // grammar(string) processes both options and rule definitions.\n    let j = Jsonic.make()\n    j.grammar(`\n      options: { number: { sep: \"_\" } },\n      rule: {\n        val: {\n          close: [\n            { s: \"#ZZ\", g: \"test,jsonic\" }\n          ]\n        }\n      }\n    `)\n    // Options took effect.\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n  })\n\n\n  it('grammar-text-then-options-preserved', () => {\n    // grammar(string) sets options and rules. A subsequent options() call\n    // must preserve both (options via deep merge, rules via RSM preservation).\n    let j = Jsonic.make()\n    j.grammar(`\n      options: { number: { sep: \"_\" } },\n      rule: {\n        val: {\n          close: [\n            { s: \"#ZZ\", g: \"from-text,jsonic\" }\n          ]\n        }\n      }\n    `)\n\n    // Now call options() with an unrelated change.\n    j.options({ number: { hex: true } })\n\n    // Options from grammar(string) should still be in effect.\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n  })\n\n\n  it('exclude-jsonic-imp-no-residual-alts', () => {\n    // Excluding \"jsonic\" and \"imp\" should remove ALL jsonic/imp-tagged alts.\n    // Regression: a typo \"elem.jsonic\" (dot) instead of \"elem,jsonic\" (comma)\n    // left a list alt active that broke string matching in strict JSON mode.\n    let j = Jsonic.make({ rule: { exclude: 'jsonic,imp' } })\n    let rules = j.internal().parser.rule()\n    for (let [name, rs] of Object.entries(rules)) {\n      for (let alt of [...(rs.def.open || []), ...(rs.def.close || [])]) {\n        if (alt.g) {\n          let gstr = typeof alt.g === 'string' ? alt.g : String(alt.g)\n          let tags = gstr.split(',')\n          for (let tag of tags) {\n            tag = tag.trim()\n            assert.ok(\n              tag !== 'jsonic' && tag !== 'imp',\n              `rule ${name} still has \"${tag}\" tag in g=\"${gstr}\" after exclude`\n            )\n          }\n        }\n      }\n    }\n\n    // Strict JSON parsing should work correctly.\n    assert.deepEqual(j('{\"a\":\"hello\",\"b\":1}'), { a: 'hello', b: 1 })\n    assert.deepEqual(j('[1,\"two\",3]'), [1, 'two', 3])\n  })\n\n\n  it('group-tags-valid-format', () => {\n    // All group tags (g fields) in grammar alts must be comma-separated\n    // lowercase identifiers [a-z] only. No dots, spaces, or other chars.\n    // Regression guard for the \"elem.jsonic\" typo.\n    let j = Jsonic.make()\n    let rules = j.internal().parser.rule()\n    let tagRe = /^[a-z]+$/\n    for (let [name, rs] of Object.entries(rules)) {\n      for (let alt of [...(rs.def.open || []), ...(rs.def.close || [])]) {\n        let gstr = alt.g ? (typeof alt.g === 'string' ? alt.g : String(alt.g)) : ''\n        if (gstr.length > 0) {\n          let tags = gstr.split(',')\n          for (let tag of tags) {\n            assert.ok(\n              tagRe.test(tag),\n              `rule ${name}: invalid group tag \"${tag}\" in g=\"${gstr}\" ` +\n              `(must be comma-separated lowercase [a-z] identifiers)`\n            )\n          }\n        }\n      }\n    }\n  })\n\n\n  it('skip-sentinel-exported', () => {\n    // SKIP is available on the Jsonic object as an immutable symbol.\n    assert.equal(typeof Jsonic.SKIP, 'symbol')\n    assert.equal(Jsonic.SKIP, Symbol.for('jsonic.SKIP'))\n  })\n\n\n  it('skip-in-deep-merge', () => {\n    // SKIP acts like undefined in deep merge — the base value is preserved.\n    let { deep } = Jsonic.util\n    let SKIP = Jsonic.SKIP\n\n    assert.deepEqual(deep({ a: 1 }, { a: SKIP }), { a: 1 })\n    assert.deepEqual(deep({ a: 1, b: 2 }, { a: SKIP, b: 3 }), { a: 1, b: 3 })\n    assert.deepEqual(deep({ a: { x: 1 } }, { a: SKIP }), { a: { x: 1 } })\n  })\n\n\n  it('skip-in-grammar-options-tokenset', () => {\n    // @SKIP in tokenSet arrays preserves defaults at those positions,\n    // acting like undefined (sparse array holes).\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        tokenSet: {\n          // Default KEY is ['#TX', '#NR', '#ST', '#VL'].\n          // @SKIP preserves positions 0-3, adds '#ID' at position 4.\n          // This would not work with null (null overwrites in deep merge).\n          KEY: ['@SKIP', '@SKIP', '@SKIP', '@SKIP', '#ST'],\n        },\n      },\n    })\n\n    // #TX is still in KEY (preserved by @SKIP), so text keys work.\n    assert.deepEqual(j('a:1'), { a: 1 })\n    // #ST is added to KEY.\n    assert.deepEqual(j('\"b\":2'), { b: 2 })\n  })\n\n\n  it('skip-in-grammar-options-value-def', () => {\n    // @SKIP can preserve existing value definitions when merging.\n    let j = Jsonic.make()\n\n    // First, add a custom value.\n    j.grammar({\n      options: {\n        value: { def: { yes: { val: true } } },\n      },\n    })\n    assert.deepEqual(j('a:yes'), { a: true })\n\n    // Second grammar call: use @SKIP to avoid overwriting 'yes'.\n    j.grammar({\n      options: {\n        value: { def: { yes: '@SKIP', no: { val: false } } },\n      },\n    })\n    // 'yes' is preserved, 'no' is added.\n    assert.deepEqual(j('a:yes,b:no'), { a: true, b: false })\n  })\n\n\n  it('skip-does-not-resolve-as-funcref', () => {\n    // @SKIP is a built-in sentinel, not a FuncRef lookup.\n    // Even if ref contains '@SKIP', the sentinel takes precedence.\n    let j = Jsonic.make()\n    j.grammar({\n      options: {\n        tag: 'original',\n      },\n    })\n    assert.equal(j.options.tag, 'original')\n\n    j.grammar({\n      ref: {\n        '@SKIP': () => 'should-not-resolve',\n      },\n      options: {\n        tag: '@SKIP',\n      },\n    })\n\n    // @SKIP resolves to the SKIP symbol (not the function in ref),\n    // and deep merge preserves the existing value.\n    assert.equal(j.options.tag, 'original')\n  })\n\n\n  it('alt-condition-funcref', () => {\n    // The `c` property of an alt can be a FuncRef that resolves to a\n    // condition function: (rule, ctx, altmatch) => boolean.\n    // When the condition returns false the alt is skipped.\n    let j = Jsonic.make()\n\n    let condCalls = 0\n\n    j.grammar({\n      ref: {\n        // Condition: only match when depth is 0 (top-level value).\n        '@topOnly': (rule) => {\n          condCalls++\n          return rule.d === 0\n        },\n        // Action: wrap the value in an array.\n        '@wrapArr': (rule) => {\n          rule.node = [rule.node]\n        },\n      },\n      rule: {\n        val: {\n          close: [\n            {\n              c: '@topOnly',\n              a: '@wrapArr',\n              g: 'custom',\n            },\n          ],\n        },\n      },\n    })\n\n    // Top-level value is wrapped in an array by the conditioned alt.\n    assert.deepEqual(j('a:1'), [{ a: 1 }])\n    assert.ok(condCalls > 0, 'condition function was called')\n\n    // The inner value 1 is at depth > 0, so the condition returns false\n    // for it — only the top-level map gets wrapped.\n    assert.deepEqual(j('a:1,b:2'), [{ a: 1, b: 2 }])\n  })\n\n\n  it('alt-condition-funcref-false-skips', () => {\n    // A condition FuncRef that always returns false causes the alt\n    // to never match, so it has no effect.\n    let j = Jsonic.make()\n\n    j.grammar({\n      ref: {\n        '@never': () => false,\n        '@boom': () => { throw new Error('should not fire') },\n      },\n      rule: {\n        val: {\n          close: [\n            {\n              c: '@never',\n              a: '@boom',\n              g: 'custom',\n            },\n          ],\n        },\n      },\n    })\n\n    // The @boom action never fires because @never blocks the alt.\n    assert.deepEqual(j('a:1'), { a: 1 })\n    assert.deepEqual(j('[1,2]'), [1, 2])\n  })\n\n\n  // --- grammar(gs, setting) — rule.alt.g append --------------------------\n\n  function altGTags(j, rulename, state) {\n    // Flatten every alt's g array for the named rule/state.\n    const rs = j.internal().parser.rule()[rulename]\n    const alts = state === 'open' ? rs.def.open : rs.def.close\n    return alts.map((a) => [...(a.g || [])].sort())\n  }\n\n\n  it('grammar-setting-alt-g-string', () => {\n    // A comma-separated string value for setting.rule.alt.g is split and\n    // appended to every alt's g array in the grammar.\n    let j = Jsonic.make()\n    j.grammar(\n      {\n        rule: {\n          val: {\n            close: [\n              { s: '#ZZ', g: 'alpha' },\n              { s: '#CA', g: 'beta,gamma' },\n            ],\n          },\n        },\n      },\n      { rule: { alt: { g: 'extraa,extrab' } } }\n    )\n\n    const tags = altGTags(j, 'val', 'close')\n    // Last two alts are ours (prepended by default).\n    assert.deepEqual(tags[0], ['alpha', 'extraa', 'extrab'])\n    assert.deepEqual(tags[1], ['beta', 'extraa', 'extrab', 'gamma'])\n  })\n\n\n  it('grammar-setting-alt-g-array', () => {\n    // An array value for setting.rule.alt.g is appended verbatim.\n    let j = Jsonic.make()\n    j.grammar(\n      {\n        rule: {\n          val: {\n            close: [\n              { s: '#ZZ', g: 'alpha' },\n              { s: '#CA' },\n            ],\n          },\n        },\n      },\n      { rule: { alt: { g: ['p1', 'p2'] } } }\n    )\n\n    const tags = altGTags(j, 'val', 'close')\n    assert.deepEqual(tags[0], ['alpha', 'p1', 'p2'])\n    assert.deepEqual(tags[1], ['p1', 'p2'])\n  })\n\n\n  it('grammar-setting-alt-g-absent-is-noop', () => {\n    // Omitting the setting leaves the grammar's g tags untouched.\n    let j = Jsonic.make()\n    j.grammar({\n      rule: {\n        val: {\n          close: [{ s: '#ZZ', g: 'alpha,beta' }],\n        },\n      },\n    })\n    const tags = altGTags(j, 'val', 'close')\n    assert.deepEqual(tags[0], ['alpha', 'beta'])\n  })\n\n\n  it('grammar-setting-alt-g-empty-string', () => {\n    // An empty g string in the setting is a no-op (no empty tags appended).\n    let j = Jsonic.make()\n    j.grammar(\n      { rule: { val: { close: [{ s: '#ZZ', g: 'alpha' }] } } },\n      { rule: { alt: { g: '' } } }\n    )\n    const tags = altGTags(j, 'val', 'close')\n    assert.deepEqual(tags[0], ['alpha'])\n  })\n\n\n  it('grammar-setting-alt-g-preserves-input', () => {\n    // The grammar spec passed in must not be mutated by the append logic.\n    const gs = {\n      rule: {\n        val: {\n          close: [{ s: '#ZZ', g: 'alpha' }],\n        },\n      },\n    }\n    let j = Jsonic.make()\n    j.grammar(gs, { rule: { alt: { g: 'extra' } } })\n\n    // Caller's alt object still has its original g value.\n    assert.strictEqual(gs.rule.val.close[0].g, 'alpha')\n  })\n\n\n  it('grammar-setting-alt-g-on-text-grammar', () => {\n    // Setting works when gs is a jsonic text string too.\n    let j = Jsonic.make()\n    j.grammar(\n      `\n        rule: {\n          val: {\n            close: [\n              { s: \"#ZZ\", g: \"first\" },\n              { s: \"#CA\", g: \"second\" }\n            ]\n          }\n        }\n      `,\n      { rule: { alt: { g: 'common' } } }\n    )\n\n    const tags = altGTags(j, 'val', 'close')\n    assert.deepEqual(tags[0], ['common', 'first'])\n    assert.deepEqual(tags[1], ['common', 'second'])\n  })\n\n\n  it('grammar-setting-alt-g-inject-form', () => {\n    // The setting is also applied when the rule uses the {alts, inject} form.\n    let j = Jsonic.make()\n    j.grammar(\n      {\n        rule: {\n          val: {\n            close: {\n              alts: [{ s: '#ZZ', g: 'solo' }],\n              inject: { append: true },\n            },\n          },\n        },\n      },\n      { rule: { alt: { g: 'shared' } } }\n    )\n\n    // Our alt is now at the tail (append:true).\n    const all = altGTags(j, 'val', 'close')\n    const last = all[all.length - 1]\n    assert.ok(last.includes('solo'))\n    assert.ok(last.includes('shared'))\n  })\n\n\n  // --- normalt g tag validation -----------------------------------------\n\n  it('grammar-g-tag-invalid-rejected', () => {\n    // Invalid group tags (single-char, uppercase, digit-leading,\n    // punctuation, spaces) are rejected by normalt().\n    let bad = [\n      'a', 'Foo', '1foo', '', 'foo!', 'foo bar', 'FOO',\n    ]\n    for (let tag of bad) {\n      let j = Jsonic.make()\n      assert.throws(\n        () => j.grammar({\n          rule: { val: { close: [{ s: '#ZZ', g: tag }] } },\n        }),\n        /invalid group tag/,\n        `expected rejection of tag \"${tag}\"`\n      )\n    }\n  })\n\n\n  it('grammar-g-tag-valid-accepted', () => {\n    // Valid tags: letter + one or more letters/digits/hyphens.\n    let good = [\n      'ab', 'a1', 'abc', 'a1b2', 'foo', 'z99',\n      'fo-o', 'custom-from-text', 'alpha-beta',\n    ]\n    for (let tag of good) {\n      let j = Jsonic.make()\n      j.grammar({\n        rule: { val: { close: [{ s: '#ZZ', g: tag }] } },\n      })\n    }\n  })\n\n\n  it('grammar-setting-invalid-tag-rejected', () => {\n    // A setting-supplied tag is also validated by normalt().\n    let j = Jsonic.make()\n    assert.throws(\n      () => j.grammar(\n        { rule: { val: { close: [{ s: '#ZZ', g: 'ok' }] } } },\n        { rule: { alt: { g: 'BAD' } } }\n      ),\n      /invalid group tag \"BAD\"/\n    )\n  })\n\n\n  // --- options() accepts a jsonic-format string --------------------------\n\n  it('options-as-string-basic', () => {\n    let j = Jsonic.make()\n    j.options('number: { sep: \"_\" }')\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n  })\n\n\n  it('options-as-string-deep-merge', () => {\n    let j = Jsonic.make()\n    // First apply one option via string, then another via object — both\n    // survive because options() deep-merges.\n    j.options('number: { sep: \"_\" }')\n    j.options({ number: { hex: true } })\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n    assert.deepEqual(j('b:0xff'), { b: 255 })\n  })\n\n\n  it('options-as-string-empty-noop', () => {\n    let j = Jsonic.make()\n    // Empty string parses to null — should be a no-op, not an error.\n    j.options('')\n    assert.deepEqual(j('a:1'), { a: 1 })\n  })\n\n\n  it('options-string-via-grammar-still-works', () => {\n    // grammar(string) continues to accept options + rules in text form;\n    // this exercises the shared code path.\n    let j = Jsonic.make()\n    j.grammar(`options: { number: { sep: \"_\" } }`)\n    assert.deepEqual(j('a:1_000'), { a: 1000 })\n  })\n\n})\n\n\ndescribe('info-marker', () => {\n  const { Jsonic } = require('..')\n\n  it('info-default-off', () => {\n    // By default, no marker is attached.\n    let j = Jsonic.make()\n    let r = j('a:1')\n    assert.equal(r.__info__, undefined)\n\n    let arr = j('[1,2]')\n    assert.equal(arr.__info__, undefined)\n  })\n\n\n  it('info-map-explicit', () => {\n    let j = Jsonic.make()\n    j.options({ info: { map: true } })\n    let r = j('{a:1}')\n    assert.equal(r.__info__.implicit, false)\n    assert.deepEqual(r.__info__.meta, {})\n  })\n\n\n  it('info-map-implicit', () => {\n    let j = Jsonic.make()\n    j.options({ info: { map: true } })\n    let r = j('a:1')\n    assert.equal(r.__info__.implicit, true)\n    assert.deepEqual(r.__info__.meta, {})\n  })\n\n\n  it('info-list-explicit', () => {\n    let j = Jsonic.make()\n    j.options({ info: { list: true } })\n    let r = j('[1,2]')\n    assert.equal(r.__info__.implicit, false)\n  })\n\n\n  it('info-list-implicit', () => {\n    let j = Jsonic.make()\n    j.options({ info: { list: true } })\n    let r = j('1,2')\n    assert.equal(r.__info__.implicit, true)\n  })\n\n\n  it('info-map-only', () => {\n    // info.map without info.list: list nodes have no marker.\n    let j = Jsonic.make()\n    j.options({ info: { map: true } })\n    let r = j('a:[1,2]')\n    assert.notEqual(r.__info__, undefined)\n    assert.equal(r.a.__info__, undefined)\n  })\n\n\n  it('info-list-only', () => {\n    // info.list without info.map: map nodes have no marker.\n    let j = Jsonic.make()\n    j.options({ info: { list: true } })\n    let r = j('[{a:1}]')\n    assert.notEqual(r.__info__, undefined)\n    assert.equal(r[0].__info__, undefined)\n  })\n\n\n  it('info-non-enumerable', () => {\n    let j = Jsonic.make()\n    j.options({ info: { map: true, list: true } })\n    let r = j('a:1,b:2')\n    // __info__ should not appear in Object.keys\n    assert.ok(!Object.keys(r).includes('__info__'))\n    // __info__ should not appear in JSON.stringify\n    assert.ok(!JSON.stringify(r).includes('__info__'))\n    // __info__ should not appear in for-in\n    let keys = []\n    for (let k in r) { keys.push(k) }\n    assert.ok(!keys.includes('__info__'))\n  })\n\n\n  it('info-meta-bag', () => {\n    let j = Jsonic.make()\n    j.options({ info: { map: true } })\n    let r = j('a:1')\n    assert.deepEqual(r.__info__.meta, {})\n    // Should be writable.\n    r.__info__.meta.custom = 'test'\n    assert.equal(r.__info__.meta.custom, 'test')\n  })\n\n\n  it('info-text-quoted', () => {\n    let j = Jsonic.make()\n    j.options({ info: { text: true } })\n    let r = j('\"hello\"')\n    assert.equal(r.__info__.quote, '\"')\n    assert.equal(r + '', 'hello')\n    assert.equal(r.valueOf(), 'hello')\n  })\n\n\n  it('info-text-single-quoted', () => {\n    let j = Jsonic.make()\n    j.options({ info: { text: true } })\n    let r = j(\"'hello'\")\n    assert.equal(r.__info__.quote, \"'\")\n    assert.equal(r + '', 'hello')\n  })\n\n\n  it('info-text-unquoted', () => {\n    let j = Jsonic.make()\n    j.options({ info: { text: true } })\n    let r = j('hello')\n    assert.equal(r.__info__.quote, '')\n    assert.equal(r + '', 'hello')\n  })\n\n\n  it('info-text-off-by-default', () => {\n    // Strings remain primitives when only map/list are enabled.\n    let j = Jsonic.make()\n    j.options({ info: { map: true, list: true } })\n    let r = j('a:hello')\n    assert.equal(typeof r.a, 'string')\n    assert.equal(r.a, 'hello')\n  })\n\n\n  it('info-custom-marker', () => {\n    let j = Jsonic.make()\n    j.options({ info: { map: true, marker: '__meta__' } })\n    let r = j('a:1')\n    assert.equal(r.__info__, undefined)\n    assert.notEqual(r.__meta__, undefined)\n    assert.equal(r.__meta__.implicit, true)\n  })\n\n\n  it('info-nested', () => {\n    let j = Jsonic.make()\n    j.options({ info: { map: true, list: true } })\n    let r = j('a:[1,2],b:{c:3}')\n    // Top-level map is implicit.\n    assert.equal(r.__info__.implicit, true)\n    // Nested list is explicit.\n    assert.equal(r.a.__info__.implicit, false)\n    // Nested map is explicit.\n    assert.equal(r.b.__info__.implicit, false)\n  })\n\n\n  it('info-marker-key-dropped', () => {\n    // User keys matching the info marker are silently dropped.\n    let j = Jsonic.make()\n    j.options({ info: { map: true } })\n    let r = j('a:1,__info__:2,b:3')\n    assert.deepEqual(Object.keys(r).sort(), ['a', 'b'])\n    assert.equal(r.a, 1)\n    assert.equal(r.b, 3)\n    // The marker is still the metadata, not the user value.\n    assert.equal(r.__info__.implicit, true)\n  })\n\n\n  it('info-marker-key-dropped-json', () => {\n    // Also works in strict JSON syntax path.\n    let j = Jsonic.make()\n    j.options({ info: { map: true } })\n    let r = j('{\"a\":1,\"__info__\":2}')\n    assert.deepEqual(Object.keys(r), ['a'])\n    assert.equal(r.__info__.implicit, false)\n  })\n\n\n  it('info-marker-key-dropped-custom', () => {\n    // Custom marker name is also protected.\n    let j = Jsonic.make()\n    j.options({ info: { map: true, marker: '__meta__' } })\n    let r = j('a:1,__meta__:2')\n    assert.deepEqual(Object.keys(r).sort(), ['a'])\n    assert.equal(r.__meta__.implicit, true)\n  })\n\n\n  it('info-marker-key-not-dropped-when-off', () => {\n    // When info.map is off, the key is NOT dropped.\n    let j = Jsonic.make()\n    let r = j('a:1,__info__:2')\n    assert.equal(r.__info__, 2)\n  })\n\n\n  it('info-child$-unchanged', () => {\n    // list.child behavior is unaffected by info.list.\n    let j = Jsonic.make({ list: { child: true }, info: { list: true } })\n    let r = j('[:1,a,b]')\n    assert.equal(r['child$'], 1)\n    assert.deepEqual(Array.from(r), ['a', 'b'])\n    assert.notEqual(r.__info__, undefined)\n  })\n\n\n  it('fnref-no-reinstall-on-subsequent-call', () => {\n    // Regression: a second fnref call that registers an unrelated reserved\n    // handler (e.g. @pair-ao) must not re-install handlers from earlier\n    // fnref calls (e.g. @pair-bc). Previously the auto-install loop scanned\n    // the accumulated fnref map, so every fnref call added another copy of\n    // every already-registered reserved handler.\n    let mergeCalls = 0\n    let j = Jsonic.make({\n      map: {\n        merge: (prev, curr) => {\n          mergeCalls++\n          return Array.isArray(prev) ? [...prev, curr] : [prev, curr]\n        },\n      },\n    })\n\n    const baseBcCount = j.rule('pair').def.bc.length\n\n    // Installing a rule with an unrelated @pair-ao ref must not touch @pair-bc.\n    j.grammar({\n      rule: { pair: {} },\n      ref: { '@pair-ao': (_r) => { } },\n    })\n\n    assert.equal(j.rule('pair').def.bc.length, baseBcCount,\n      'pair bc actions unchanged by unrelated @pair-ao registration')\n\n    mergeCalls = 0\n    j('a:{x:1},a:{y:2},a:{z:3}')\n    assert.equal(mergeCalls, 2,\n      'merge called once per duplicate key (not twice due to duplicate bc actions)')\n  })\n\n})\n"
  },
  {
    "path": "test/happy.js",
    "content": "let { Jsonic } = require('..')\n\nconsole.log(Jsonic('a:1'))\n"
  },
  {
    "path": "test/json-standard.js",
    "content": "/* Copyright (c) 2021 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst assert = require('node:assert')\n\n// \nconst P = JSON.parse\n\nmodule.exports = function json(j, E) {\n  let s\n\n  // V\n  s = '{}'\n  assert.deepEqual(j(s), P(s))\n  s = '[]'\n  assert.deepEqual(j(s), P(s))\n  s = '0'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\"'\n  assert.deepEqual(j(s), P(s))\n  s = 'true'\n  assert.deepEqual(j(s), P(s))\n  s = 'false'\n  assert.deepEqual(j(s), P(s))\n  s = 'null'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:V}\n  s = '{\"a\":{}}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":true}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":false}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":null}'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:V,K:V}\n  s = '{\"a\":{},\"b\":{}}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[],\"b\":[]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":0,\"b\":0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\",\"b\":\"\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":true,\"b\":true}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":false,\"b\":false}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":null,\"b\":null}'\n  assert.deepEqual(j(s), P(s))\n\n  // [V]\n  s = '[{}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[[]]'\n  assert.deepEqual(j(s), P(s))\n  s = '[0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[true]'\n  assert.deepEqual(j(s), P(s))\n  s = '[false]'\n  assert.deepEqual(j(s), P(s))\n  s = '[null]'\n  assert.deepEqual(j(s), P(s))\n\n  // [V,V]\n  s = '[{},{}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[[],[]]'\n  assert.deepEqual(j(s), P(s))\n  s = '[0,0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\",\"\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[true,true]'\n  assert.deepEqual(j(s), P(s))\n  s = '[false,false]'\n  assert.deepEqual(j(s), P(s))\n  s = '[null,null]'\n  assert.deepEqual(j(s), P(s))\n\n  // N\n  s = '1'\n  assert.deepEqual(j(s), P(s))\n  s = '-2'\n  assert.deepEqual(j(s), P(s))\n  s = '51'\n  assert.deepEqual(j(s), P(s))\n  s = '-62'\n  assert.deepEqual(j(s), P(s))\n  s = '0.3'\n  assert.deepEqual(j(s), P(s))\n  s = '-0.4'\n  assert.deepEqual(j(s), P(s))\n  s = '0.37'\n  assert.deepEqual(j(s), P(s))\n  s = '-0.48'\n  assert.deepEqual(j(s), P(s))\n  s = '9.3'\n  assert.deepEqual(j(s), P(s))\n  s = '-1.4'\n  assert.deepEqual(j(s), P(s))\n  s = '2.37'\n  assert.deepEqual(j(s), P(s))\n  s = '-3.48'\n  assert.deepEqual(j(s), P(s))\n  s = '94.3'\n  assert.deepEqual(j(s), P(s))\n  s = '-15.4'\n  assert.deepEqual(j(s), P(s))\n  s = '26.37'\n  assert.deepEqual(j(s), P(s))\n  s = '-37.48'\n  assert.deepEqual(j(s), P(s))\n\n  s = '1e8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e9'\n  assert.deepEqual(j(s), P(s))\n  s = '51e0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e1'\n  assert.deepEqual(j(s), P(s))\n  s = '1e82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e93'\n  assert.deepEqual(j(s), P(s))\n  s = '51e04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e15'\n  assert.deepEqual(j(s), P(s))\n  s = '1E8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E9'\n  assert.deepEqual(j(s), P(s))\n  s = '51E0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E1'\n  assert.deepEqual(j(s), P(s))\n  s = '1E82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E93'\n  assert.deepEqual(j(s), P(s))\n  s = '51E04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E15'\n  assert.deepEqual(j(s), P(s))\n\n  s = '1e-8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e-9'\n  assert.deepEqual(j(s), P(s))\n  s = '51e-0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e-1'\n  assert.deepEqual(j(s), P(s))\n  s = '1e-82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e-93'\n  assert.deepEqual(j(s), P(s))\n  s = '51e-04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e-15'\n  assert.deepEqual(j(s), P(s))\n  s = '1E-8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E-9'\n  assert.deepEqual(j(s), P(s))\n  s = '51E-0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E-1'\n  assert.deepEqual(j(s), P(s))\n  s = '1E-82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E-93'\n  assert.deepEqual(j(s), P(s))\n  s = '51E-04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E-15'\n  assert.deepEqual(j(s), P(s))\n\n  s = '1e+8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e+9'\n  assert.deepEqual(j(s), P(s))\n  s = '51e+0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e+1'\n  assert.deepEqual(j(s), P(s))\n  s = '1e+82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e+93'\n  assert.deepEqual(j(s), P(s))\n  s = '51e+04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e+15'\n  assert.deepEqual(j(s), P(s))\n  s = '1E+8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E+9'\n  assert.deepEqual(j(s), P(s))\n  s = '51E+0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E+1'\n  assert.deepEqual(j(s), P(s))\n  s = '1E+82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E+93'\n  assert.deepEqual(j(s), P(s))\n  s = '51E+04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E+15'\n  assert.deepEqual(j(s), P(s))\n\n  // S\n  s = '\"a\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"aa\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\\"\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\\\\\\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\/\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\b\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\f\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\n\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\r\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\t\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\u0000\"'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:N}\n  s = '{\"a\":1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":0.3}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-0.4}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":0.37}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-0.48}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":9.3}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-1.4}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":2.37}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-3.48}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":94.3}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-15.4}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":26.37}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-37.48}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":1e8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1e82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e15}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E15}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":1e-8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e-9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e-0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e-1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1e-82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e-93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e-04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e-15}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E-8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E-9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E-0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E-1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E-82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E-93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E-04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E-15}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":1e+8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e+9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e+0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e+1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1e+82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e+93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e+04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e+15}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E+8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E+9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E+0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E+1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E+82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E+93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E+04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E+15}'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:S}\n  s = '{\"a\":\"a\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"aa\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\\"\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\\\\\\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\/\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\b\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\f\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\n\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\r\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\t\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\u0000\"}'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:B}\n  s = '{\"a\":true}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":false}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":null}'\n  assert.deepEqual(j(s), P(s))\n\n  // [N]\n  s = '[1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62]'\n  assert.deepEqual(j(s), P(s))\n  s = '[0.3]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-0.4]'\n  assert.deepEqual(j(s), P(s))\n  s = '[0.37]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-0.48]'\n  assert.deepEqual(j(s), P(s))\n  s = '[9.3]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-1.4]'\n  assert.deepEqual(j(s), P(s))\n  s = '[2.37]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-3.48]'\n  assert.deepEqual(j(s), P(s))\n  s = '[94.3]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-15.4]'\n  assert.deepEqual(j(s), P(s))\n  s = '[26.37]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-37.48]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[1e8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1e82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e15]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E15]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[1e-8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e-9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e-0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e-1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1e-82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e-93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e-04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e-15]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E-8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E-9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E-0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E-1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E-82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E-93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E-04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E-15]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[1e+8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e+9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e+0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e+1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1e+82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e+93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e+04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e+15]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E+8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E+9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E+0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E+1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E+82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E+93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E+04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E+15]'\n  assert.deepEqual(j(s), P(s))\n\n  // [S]\n  s = '[\"a\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"aa\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\\"\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\\\\\\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\/\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\b\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\f\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\n\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\r\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\t\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\u0000\"]'\n  assert.deepEqual(j(s), P(s))\n\n  // [B]\n  s = '[true]'\n  assert.deepEqual(j(s), P(s))\n  s = '[false]'\n  assert.deepEqual(j(s), P(s))\n  s = '[null]'\n  assert.deepEqual(j(s), P(s))\n\n  // [{K:V}]\n  s = '[{\"a\":1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":0.3}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-0.4}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":0.37}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-0.48}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":9.3}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-1.4}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":2.37}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-3.48}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":94.3}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-15.4}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":26.37}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-37.48}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":1e8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1e82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e15}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E15}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":1e-8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e-9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e-0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e-1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1e-82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e-93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e-04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e-15}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E-8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E-9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E-0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E-1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E-82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E-93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E-04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E-15}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":1e+8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e+9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e+0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e+1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1e+82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e+93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e+04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e+15}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E+8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E+9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E+0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E+1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E+82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E+93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E+04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E+15}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":\"a\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"aa\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\\"\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\\\\\\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\/\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\b\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\f\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\n\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\r\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\t\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\u0000\"}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":true}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":false}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":null}]'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:[V]}\n  s = '{\"a\":[1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[0.3]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-0.4]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[0.37]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-0.48]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[9.3]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-1.4]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[2.37]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-3.48]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[94.3]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-15.4]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[26.37]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-37.48]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[1e8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1e82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e15]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E15]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[1e-8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e-9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e-0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e-1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1e-82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e-93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e-04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e-15]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E-8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E-9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E-0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E-1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E-82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E-93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E-04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E-15]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[1e+8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e+9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e+0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e+1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1e+82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e+93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e+04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e+15]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E+8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E+9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E+0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E+1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E+82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E+93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E+04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E+15]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[true]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[false]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[null]}'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:N,K:N}\n  s = '{\"a\":1,\"b\":1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2,\"b\":-2}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51,\"b\":51}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62,\"b\":-62}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":0.3,\"b\":0.3}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-0.4,\"b\":-0.4}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":0.37,\"b\":0.37}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-0.48,\"b\":-0.48}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":9.3,\"b\":9.3}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-1.4,\"b\":-1.4}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":2.37,\"b\":2.37}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-3.48,\"b\":-3.48}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":94.3,\"b\":94.3}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-15.4,\"b\":-15.4}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":26.37,\"b\":26.37}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-37.48,\"b\":-37.48}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":1e8,\"b\":1e8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e9,\"b\":-2e9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e0,\"b\":51e0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e1,\"b\":-62e1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1e82,\"b\":1e82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e93,\"b\":-2e93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e04,\"b\":51e04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e15,\"b\":-62e15}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E8,\"b\":1E8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E9,\"b\":-2E9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E0,\"b\":51E0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E1,\"b\":-62E1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E82,\"b\":1E82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E93,\"b\":-2E93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E04,\"b\":51E04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E15,\"b\":-62E15}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":1e-8,\"b\":1e-8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e-9,\"b\":-2e-9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e-0,\"b\":51e-0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e-1,\"b\":-62e-1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1e-82,\"b\":1e-82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e-93,\"b\":-2e-93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e-04,\"b\":51e-04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e-15,\"b\":-62e-15}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E-8,\"b\":1E-8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E-9,\"b\":-2E-9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E-0,\"b\":51E-0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E-1,\"b\":-62E-1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E-82,\"b\":1E-82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E-93,\"b\":-2E-93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E-04,\"b\":51E-04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E-15,\"b\":-62E-15}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":1e+8,\"b\":1e+8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e+9,\"b\":-2e+9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e+0,\"b\":51e+0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e+1,\"b\":-62e+1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1e+82,\"b\":1e+82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2e+93,\"b\":-2e+93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51e+04,\"b\":51e+04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62e+15,\"b\":-62e+15}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E+8,\"b\":1E+8}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E+9,\"b\":-2E+9}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E+0,\"b\":51E+0}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E+1,\"b\":-62E+1}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":1E+82,\"b\":1E+82}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-2E+93,\"b\":-2E+93}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":51E+04,\"b\":51E+04}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":-62E+15,\"b\":-62E+15}'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:S,K:S}\n  s = '{\"a\":\"A\",\"b\":\"B\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"AA\",\"b\":\"BB\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\\"\",\"b\":\"\\\\\"\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\\\\\\",\"b\":\"\\\\\\\\\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\/\",\"b\":\"\\\\/\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\b\",\"b\":\"\\\\b\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\f\",\"b\":\"\\\\f\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\n\",\"b\":\"\\\\n\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\r\",\"b\":\"\\\\r\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\t\",\"b\":\"\\\\t\"}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":\"\\\\u0000\",\"b\":\"\\\\u0000\"}'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:B,K:B}\n  s = '{\"a\":true,\"b\":true}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":false,\"b\":false}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":null,\"b\":null}'\n  assert.deepEqual(j(s), P(s))\n\n  // [N,N]\n  s = '[1,1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2,-2]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51,51]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62,-62]'\n  assert.deepEqual(j(s), P(s))\n  s = '[0.3,0.3]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-0.4,-0.4]'\n  assert.deepEqual(j(s), P(s))\n  s = '[0.37,0.37]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-0.48,-0.48]'\n  assert.deepEqual(j(s), P(s))\n  s = '[9.3,9.3]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-1.4,-1.4]'\n  assert.deepEqual(j(s), P(s))\n  s = '[2.37,2.37]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-3.48,-3.48]'\n  assert.deepEqual(j(s), P(s))\n  s = '[94.3,94.3]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-15.4,-15.4]'\n  assert.deepEqual(j(s), P(s))\n  s = '[26.37,26.37]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-37.48,-37.48]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[1e8,1e8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e9,-2e9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e0,51e0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e1,-62e1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1e82,1e82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e93,-2e93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e04,51e04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e15,-62e15]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E8,1E8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E9,-2E9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E0,51E0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E1,-62E1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E82,1E82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E93,-2E93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E04,51E04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E15,-62E15]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[1e-8,1e-8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e-9,-2e-9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e-0,51e-0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e-1,-62e-1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1e-82,1e-82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e-93,-2e-93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e-04,51e-04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e-15,-62e-15]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E-8,1E-8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E-9,-2E-9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E-0,51E-0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E-1,-62E-1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E-82,1E-82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E-93,-2E-93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E-04,51E-04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E-15,-62E-15]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[1e+8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e+9,-2e+9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e+0,51e+0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e+1,-62e+1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1e+82,1e+82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2e+93,-2e+93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51e+04,51e+04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62e+15,-62e+15]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E+8,1E+8]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E+9,-2E+9]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E+0,51E+0]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E+1,-62E+1]'\n  assert.deepEqual(j(s), P(s))\n  s = '[1E+82,1E+82]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-2E+93,-2E+93]'\n  assert.deepEqual(j(s), P(s))\n  s = '[51E+04,51E+04]'\n  assert.deepEqual(j(s), P(s))\n  s = '[-62E+15,-62E+15]'\n  assert.deepEqual(j(s), P(s))\n\n  // [S,S]\n  s = '[\"a\",\"a\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"aa\",\"aa\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\\"\",\"\\\\\"\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\\\\\\",\"\\\\\\\\\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\/\",\"\\\\/\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\b\",\"\\\\b\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\f\",\"\\\\f\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\n\",\"\\\\n\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\r\",\"\\\\r\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\t\",\"\\\\t\"]'\n  assert.deepEqual(j(s), P(s))\n  s = '[\"\\\\u0000\",\"\\\\u0000\"]'\n  assert.deepEqual(j(s), P(s))\n\n  // [B,B]\n  s = '[true,true]'\n  assert.deepEqual(j(s), P(s))\n  s = '[false,false]'\n  assert.deepEqual(j(s), P(s))\n  s = '[null,null]'\n  assert.deepEqual(j(s), P(s))\n\n  // [{K:V},{K:V}]\n  s = '[{\"a\":1},{\"a\":1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2},{\"a\":-2}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51},{\"a\":51}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62},{\"a\":-62}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":0.3},{\"a\":0.3}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-0.4},{\"a\":-0.4}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":0.37},{\"a\":0.37}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-0.48},{\"a\":-0.48}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":9.3},{\"a\":9.3}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-1.4},{\"a\":-1.4}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":2.37},{\"a\":2.37}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-3.48},{\"a\":-3.48}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":94.3},{\"a\":94.3}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-15.4},{\"a\":-15.4}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":26.37},{\"a\":26.37}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-37.48},{\"a\":-37.48}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":1e8},{\"a\":1e8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e9},{\"a\":-2e9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e0},{\"a\":51e0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e1},{\"a\":-62e1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1e82},{\"a\":1e82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e93},{\"a\":-2e93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e04},{\"a\":51e04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e15},{\"a\":-62e15}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E8},{\"a\":1E8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E9},{\"a\":-2E9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E0},{\"a\":51E0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E1},{\"a\":-62E1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E82},{\"a\":1E82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E93},{\"a\":-2E93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E04},{\"a\":51E04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E15},{\"a\":-62E15}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":1e-8},{\"a\":1e-8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e-9},{\"a\":-2e-9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e-0},{\"a\":51e-0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e-1},{\"a\":-62e-1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1e-82},{\"a\":1e-82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e-93},{\"a\":-2e-93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e-04},{\"a\":51e-04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e-15},{\"a\":-62e-15}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E-8},{\"a\":1E-8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E-9},{\"a\":-2E-9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E-0},{\"a\":51E-0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E-1},{\"a\":-62E-1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E-82},{\"a\":1E-82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E-93},{\"a\":-2E-93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E-04},{\"a\":51E-04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E-15},{\"a\":-62E-15}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":1e+8},{\"a\":1e+8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e+9},{\"a\":-2e+9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e+0},{\"a\":51e+0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e+1},{\"a\":-62e+1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1e+82},{\"a\":1e+82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2e+93},{\"a\":-2e+93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51e+04},{\"a\":51e+04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62e+15},{\"a\":-62e+15}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E+8},{\"a\":1E+8}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E+9},{\"a\":-2E+9}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E+0},{\"a\":51E+0}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E+1},{\"a\":-62E+1}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":1E+82},{\"a\":1E+82}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-2E+93},{\"a\":-2E+93}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":51E+04},{\"a\":51E+04}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":-62E+15},{\"a\":-62E+15}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":\"a\"},{\"a\":\"a\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"aa\"},{\"a\":\"aa\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\\"\"},{\"a\":\"\\\\\"\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\\\\\\"},{\"a\":\"\\\\\\\\\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\/\"},{\"a\":\"\\\\/\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\b\"},{\"a\":\"\\\\b\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\f\"},{\"a\":\"\\\\f\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\n\"},{\"a\":\"\\\\n\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\r\"},{\"a\":\"\\\\r\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\t\"},{\"a\":\"\\\\t\"}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":\"\\\\u0000\"},{\"a\":\"\\\\u0000\"}]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '[{\"a\":true},{\"a\":true}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":false},{\"a\":false}]'\n  assert.deepEqual(j(s), P(s))\n  s = '[{\"a\":null},{\"a\":null}]'\n  assert.deepEqual(j(s), P(s))\n\n  // {K:[V],K:[V]}\n  s = '{\"a\":[1],\"a\":[1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2],\"b\":[-2]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51],\"b\":[51]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62],\"b\":[-62]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[0.3],\"b\":[0.3]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-0.4],\"b\":[-0.4]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[0.37],\"b\":[0.37]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-0.48],\"b\":[-0.48]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[9.3],\"b\":[9.3]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-1.4],\"b\":[-1.4]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[2.37],\"b\":[2.37]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-3.48],\"b\":[-3.48]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[94.3],\"b\":[94.3]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-15.4],\"b\":[-15.4]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[26.37],\"b\":[26.37]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-37.48],\"b\":[-37.48]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[1e8],\"b\":[1e8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e9],\"b\":[-2e9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e0],\"b\":[51e0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e1],\"b\":[-62e1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1e82],\"b\":[1e82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e93],\"b\":[-2e93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e04],\"b\":[51e04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e15],\"b\":[-62e15]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E8],\"b\":[1E8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E9],\"b\":[-2E9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E0],\"b\":[51E0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E1],\"b\":[-62E1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E82],\"b\":[1E82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E93],\"b\":[-2E93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E04],\"b\":[51E04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E15],\"b\":[-62E15]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[1e-8],\"b\":[1e-8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e-9],\"b\":[-2e-9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e-0],\"b\":[51e-0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e-1],\"b\":[-62e-1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1e-82],\"b\":[1e-82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e-93],\"b\":[-2e-93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e-04],\"b\":[51e-04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e-15],\"b\":[-62e-15]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E-8],\"b\":[1E-8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E-9],\"b\":[-2E-9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E-0],\"b\":[51E-0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E-1],\"b\":[-62E-1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E-82],\"b\":[1E-82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E-93],\"b\":[-2E-93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E-04],\"b\":[51E-04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E-15],\"b\":[-62E-15]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[1e+8],\"b\":[1e+8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e+9],\"b\":[-2e+9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e+0],\"b\":[51e+0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e+1],\"b\":[-62e+1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1e+82],\"b\":[1e+82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2e+93],\"b\":[-2e+93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51e+04],\"b\":[51e+04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62e+15],\"b\":[-62e+15]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E+8],\"b\":[1E+8]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E+9],\"b\":[-2E+9]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E+0],\"b\":[51E+0]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E+1],\"b\":[-62E+1]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[1E+82],\"b\":[1E+82]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-2E+93],\"b\":[-2E+93]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[51E+04],\"b\":[51E+04]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[-62E+15],\"b\":[-62E+15]}'\n  assert.deepEqual(j(s), P(s))\n\n  s = '{\"a\":[true],\"b\":[true]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[false],\"b\":[false]}'\n  assert.deepEqual(j(s), P(s))\n  s = '{\"a\":[null],\"b\":[null]}'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE\n\n  // SPACE V\n  s = ' {\\n\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '0'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\"'\n  assert.deepEqual(j(s), P(s))\n  s = 'true'\n  assert.deepEqual(j(s), P(s))\n  s = 'false'\n  assert.deepEqual(j(s), P(s))\n  s = 'null'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:V\\n }\n  s = ' {\\n\"a\": {\\n\\n } \\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":true\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":false\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":null\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:V   ,\\t \\t\\t  \\r\\r\\n\\nK:V\\n }\n  s = ' {\\n\"a\": {\\n\\n }    ,\\t \\t\\t  \\r\\r\\n\\n\"b\": {\\n\\n } \\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":true   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":true\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":false   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":false\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":null   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":null\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tV\\r\\n]\n  s = '  [\\t {\\n\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t  [\\t\\r\\n]\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\ttrue\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tfalse\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tnull\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tV   ,\\t \\t\\t  \\r\\r\\n\\nV\\r\\n]\n  s = '  [\\t {\\n\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t  [\\t\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n  [\\t\\r\\n]\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t0   ,\\t \\t\\t  \\r\\r\\n\\n0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\ttrue   ,\\t \\t\\t  \\r\\r\\n\\ntrue\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tfalse   ,\\t \\t\\t  \\r\\r\\n\\nfalse\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tnull   ,\\t \\t\\t  \\r\\r\\n\\nnull\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE N\n  s = '1'\n  assert.deepEqual(j(s), P(s))\n  s = '-2'\n  assert.deepEqual(j(s), P(s))\n  s = '51'\n  assert.deepEqual(j(s), P(s))\n  s = '-62'\n  assert.deepEqual(j(s), P(s))\n  s = '0.3'\n  assert.deepEqual(j(s), P(s))\n  s = '-0.4'\n  assert.deepEqual(j(s), P(s))\n  s = '0.37'\n  assert.deepEqual(j(s), P(s))\n  s = '-0.48'\n  assert.deepEqual(j(s), P(s))\n  s = '9.3'\n  assert.deepEqual(j(s), P(s))\n  s = '-1.4'\n  assert.deepEqual(j(s), P(s))\n  s = '2.37'\n  assert.deepEqual(j(s), P(s))\n  s = '-3.48'\n  assert.deepEqual(j(s), P(s))\n  s = '94.3'\n  assert.deepEqual(j(s), P(s))\n  s = '-15.4'\n  assert.deepEqual(j(s), P(s))\n  s = '26.37'\n  assert.deepEqual(j(s), P(s))\n  s = '-37.48'\n  assert.deepEqual(j(s), P(s))\n\n  s = '1e8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e9'\n  assert.deepEqual(j(s), P(s))\n  s = '51e0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e1'\n  assert.deepEqual(j(s), P(s))\n  s = '1e82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e93'\n  assert.deepEqual(j(s), P(s))\n  s = '51e04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e15'\n  assert.deepEqual(j(s), P(s))\n  s = '1E8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E9'\n  assert.deepEqual(j(s), P(s))\n  s = '51E0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E1'\n  assert.deepEqual(j(s), P(s))\n  s = '1E82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E93'\n  assert.deepEqual(j(s), P(s))\n  s = '51E04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E15'\n  assert.deepEqual(j(s), P(s))\n\n  s = '1e-8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e-9'\n  assert.deepEqual(j(s), P(s))\n  s = '51e-0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e-1'\n  assert.deepEqual(j(s), P(s))\n  s = '1e-82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e-93'\n  assert.deepEqual(j(s), P(s))\n  s = '51e-04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e-15'\n  assert.deepEqual(j(s), P(s))\n  s = '1E-8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E-9'\n  assert.deepEqual(j(s), P(s))\n  s = '51E-0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E-1'\n  assert.deepEqual(j(s), P(s))\n  s = '1E-82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E-93'\n  assert.deepEqual(j(s), P(s))\n  s = '51E-04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E-15'\n  assert.deepEqual(j(s), P(s))\n\n  s = '1e+8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e+9'\n  assert.deepEqual(j(s), P(s))\n  s = '51e+0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e+1'\n  assert.deepEqual(j(s), P(s))\n  s = '1e+82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2e+93'\n  assert.deepEqual(j(s), P(s))\n  s = '51e+04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62e+15'\n  assert.deepEqual(j(s), P(s))\n  s = '1E+8'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E+9'\n  assert.deepEqual(j(s), P(s))\n  s = '51E+0'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E+1'\n  assert.deepEqual(j(s), P(s))\n  s = '1E+82'\n  assert.deepEqual(j(s), P(s))\n  s = '-2E+93'\n  assert.deepEqual(j(s), P(s))\n  s = '51E+04'\n  assert.deepEqual(j(s), P(s))\n  s = '-62E+15'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE S\n  s = '\"a\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"aa\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\\"\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\\\\\\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\/\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\b\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\f\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\n\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\r\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\t\"'\n  assert.deepEqual(j(s), P(s))\n  s = '\"\\\\u0000\"'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:N\\n }\n  s = ' {\\n\"a\":1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":0.3\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-0.4\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":0.37\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-0.48\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":9.3\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-1.4\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":2.37\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-3.48\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":94.3\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-15.4\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":26.37\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-37.48\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":1e8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1e82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e15\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E15\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":1e-8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e-9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e-0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e-1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1e-82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e-93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e-04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e-15\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E-8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E-9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E-0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E-1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E-82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E-93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E-04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E-15\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":1e+8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e+9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e+0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e+1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1e+82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e+93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e+04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e+15\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E+8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E+9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E+0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E+1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E+82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E+93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E+04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E+15\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:S\\n }\n  s = ' {\\n\"a\":\"a\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"aa\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\\"\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\\\\\\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\/\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\b\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\f\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\n\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\r\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\t\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\u0000\"\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:B\\n }\n  s = ' {\\n\"a\":true\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":false\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":null\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tN\\r\\n]\n  s = '  [\\t1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t0.3\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-0.4\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t0.37\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-0.48\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t9.3\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-1.4\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t2.37\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-3.48\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t94.3\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-15.4\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t26.37\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-37.48\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t1e8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1e82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t1e-8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e-9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e-0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e-1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1e-82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e-93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e-04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e-15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E-8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E-9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E-0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E-1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E-82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E-93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E-04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E-15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t1e+8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e+9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e+0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e+1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1e+82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e+93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e+04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e+15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E+8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E+9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E+0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E+1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E+82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E+93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E+04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E+15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tS\\r\\n]\n  s = '  [\\t\"a\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"aa\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\\"\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\\\\\\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\/\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\b\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\f\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\n\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\r\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\t\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\u0000\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tB\\r\\n]\n  s = '  [\\ttrue\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tfalse\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tnull\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\t {\\nK:V\\n } \\r\\n]\n  s = '  [\\t {\\n\"a\":1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":0.3\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-0.4\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":0.37\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-0.48\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":9.3\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-1.4\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":2.37\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-3.48\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":94.3\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-15.4\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":26.37\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-37.48\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":1e8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1e82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":1e-8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e-9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e-0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e-1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1e-82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e-93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e-04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e-15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E-8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E-9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E-0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E-1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E-82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E-93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E-04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E-15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":1e+8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e+9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e+0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e+1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1e+82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e+93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e+04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e+15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E+8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E+9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E+0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E+1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E+82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E+93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E+04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E+15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":\"a\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"aa\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\\"\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\\\\\\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\/\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\b\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\f\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\n\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\r\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\t\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\u0000\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":true\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":false\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":null\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:  [\\tV\\r\\n]\\n }\n  s = ' {\\n\"a\":  [\\t1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t0.3\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-0.4\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t0.37\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-0.48\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t9.3\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-1.4\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t2.37\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-3.48\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t94.3\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-15.4\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t26.37\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-37.48\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\t1e8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1e82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\t1e-8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e-9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e-0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e-1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1e-82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e-93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e-04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e-15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E-8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E-9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E-0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E-1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E-82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E-93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E-04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E-15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\t1e+8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e+9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e+0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e+1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1e+82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e+93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e+04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e+15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E+8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E+9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E+0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E+1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E+82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E+93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E+04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E+15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\ttrue\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\tfalse\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\tnull\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:N   ,\\t \\t\\t  \\r\\r\\n\\nK:N\\n }\n  s = ' {\\n\"a\":1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":0.3   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":0.3\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-0.4   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-0.4\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":0.37   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":0.37\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-0.48   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-0.48\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":9.3   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":9.3\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-1.4   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-1.4\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":2.37   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":2.37\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-3.48   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-3.48\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":94.3   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":94.3\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-15.4   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-15.4\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":26.37   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":26.37\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-37.48   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-37.48\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":1e8   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1e8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e9   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2e9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51e0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62e1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1e82   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1e82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e93   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2e93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e04   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51e04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e15   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62e15\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E8   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1E8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E9   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2E9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51E0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62E1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E82   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1E82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E93   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2E93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E04   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51E04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E15   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62E15\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":1e-8   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1e-8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e-9   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2e-9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e-0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51e-0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e-1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62e-1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1e-82   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1e-82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e-93   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2e-93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e-04   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51e-04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e-15   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62e-15\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E-8   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1E-8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E-9   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2E-9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E-0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51E-0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E-1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62E-1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E-82   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1E-82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E-93   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2E-93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E-04   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51E-04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E-15   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62E-15\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":1e+8   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1e+8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e+9   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2e+9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e+0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51e+0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e+1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62e+1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1e+82   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1e+82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2e+93   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2e+93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51e+04   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51e+04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62e+15   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62e+15\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E+8   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1E+8\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E+9   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2E+9\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E+0   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51E+0\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E+1   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62E+1\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":1E+82   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":1E+82\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-2E+93   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-2E+93\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":51E+04   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":51E+04\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":-62E+15   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":-62E+15\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:S   ,\\t \\t\\t  \\r\\r\\n\\nK:S\\n }\n  s = ' {\\n\"a\":\"A\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"B\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"AA\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"BB\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\\"\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\\"\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\\\\\\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\\\\\\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\/\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\/\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\b\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\b\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\f\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\f\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\n\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\n\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\r\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\r\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\t\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\t\"\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":\"\\\\u0000\"   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":\"\\\\u0000\"\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:B   ,\\t \\t\\t  \\r\\r\\n\\nK:B\\n }\n  s = ' {\\n\"a\":true   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":true\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":false   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":false\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":null   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":null\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tN   ,\\t \\t\\t  \\r\\r\\n\\nN\\r\\n]\n  s = '  [\\t1   ,\\t \\t\\t  \\r\\r\\n\\n1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2   ,\\t \\t\\t  \\r\\r\\n\\n-2\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51   ,\\t \\t\\t  \\r\\r\\n\\n51\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62   ,\\t \\t\\t  \\r\\r\\n\\n-62\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t0.3   ,\\t \\t\\t  \\r\\r\\n\\n0.3\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-0.4   ,\\t \\t\\t  \\r\\r\\n\\n-0.4\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t0.37   ,\\t \\t\\t  \\r\\r\\n\\n0.37\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-0.48   ,\\t \\t\\t  \\r\\r\\n\\n-0.48\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t9.3   ,\\t \\t\\t  \\r\\r\\n\\n9.3\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-1.4   ,\\t \\t\\t  \\r\\r\\n\\n-1.4\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t2.37   ,\\t \\t\\t  \\r\\r\\n\\n2.37\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-3.48   ,\\t \\t\\t  \\r\\r\\n\\n-3.48\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t94.3   ,\\t \\t\\t  \\r\\r\\n\\n94.3\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-15.4   ,\\t \\t\\t  \\r\\r\\n\\n-15.4\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t26.37   ,\\t \\t\\t  \\r\\r\\n\\n26.37\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-37.48   ,\\t \\t\\t  \\r\\r\\n\\n-37.48\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t1e8   ,\\t \\t\\t  \\r\\r\\n\\n1e8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e9   ,\\t \\t\\t  \\r\\r\\n\\n-2e9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e0   ,\\t \\t\\t  \\r\\r\\n\\n51e0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e1   ,\\t \\t\\t  \\r\\r\\n\\n-62e1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1e82   ,\\t \\t\\t  \\r\\r\\n\\n1e82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e93   ,\\t \\t\\t  \\r\\r\\n\\n-2e93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e04   ,\\t \\t\\t  \\r\\r\\n\\n51e04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e15   ,\\t \\t\\t  \\r\\r\\n\\n-62e15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E8   ,\\t \\t\\t  \\r\\r\\n\\n1E8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E9   ,\\t \\t\\t  \\r\\r\\n\\n-2E9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E0   ,\\t \\t\\t  \\r\\r\\n\\n51E0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E1   ,\\t \\t\\t  \\r\\r\\n\\n-62E1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E82   ,\\t \\t\\t  \\r\\r\\n\\n1E82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E93   ,\\t \\t\\t  \\r\\r\\n\\n-2E93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E04   ,\\t \\t\\t  \\r\\r\\n\\n51E04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E15   ,\\t \\t\\t  \\r\\r\\n\\n-62E15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t1e-8   ,\\t \\t\\t  \\r\\r\\n\\n1e-8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e-9   ,\\t \\t\\t  \\r\\r\\n\\n-2e-9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e-0   ,\\t \\t\\t  \\r\\r\\n\\n51e-0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e-1   ,\\t \\t\\t  \\r\\r\\n\\n-62e-1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1e-82   ,\\t \\t\\t  \\r\\r\\n\\n1e-82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e-93   ,\\t \\t\\t  \\r\\r\\n\\n-2e-93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e-04   ,\\t \\t\\t  \\r\\r\\n\\n51e-04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e-15   ,\\t \\t\\t  \\r\\r\\n\\n-62e-15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E-8   ,\\t \\t\\t  \\r\\r\\n\\n1E-8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E-9   ,\\t \\t\\t  \\r\\r\\n\\n-2E-9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E-0   ,\\t \\t\\t  \\r\\r\\n\\n51E-0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E-1   ,\\t \\t\\t  \\r\\r\\n\\n-62E-1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E-82   ,\\t \\t\\t  \\r\\r\\n\\n1E-82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E-93   ,\\t \\t\\t  \\r\\r\\n\\n-2E-93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E-04   ,\\t \\t\\t  \\r\\r\\n\\n51E-04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E-15   ,\\t \\t\\t  \\r\\r\\n\\n-62E-15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t1e+8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e+9   ,\\t \\t\\t  \\r\\r\\n\\n-2e+9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e+0   ,\\t \\t\\t  \\r\\r\\n\\n51e+0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e+1   ,\\t \\t\\t  \\r\\r\\n\\n-62e+1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1e+82   ,\\t \\t\\t  \\r\\r\\n\\n1e+82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2e+93   ,\\t \\t\\t  \\r\\r\\n\\n-2e+93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51e+04   ,\\t \\t\\t  \\r\\r\\n\\n51e+04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62e+15   ,\\t \\t\\t  \\r\\r\\n\\n-62e+15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E+8   ,\\t \\t\\t  \\r\\r\\n\\n1E+8\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E+9   ,\\t \\t\\t  \\r\\r\\n\\n-2E+9\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E+0   ,\\t \\t\\t  \\r\\r\\n\\n51E+0\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E+1   ,\\t \\t\\t  \\r\\r\\n\\n-62E+1\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t1E+82   ,\\t \\t\\t  \\r\\r\\n\\n1E+82\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-2E+93   ,\\t \\t\\t  \\r\\r\\n\\n-2E+93\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t51E+04   ,\\t \\t\\t  \\r\\r\\n\\n51E+04\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t-62E+15   ,\\t \\t\\t  \\r\\r\\n\\n-62E+15\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tS   ,\\t \\t\\t  \\r\\r\\n\\nS\\r\\n]\n  s = '  [\\t\"a\"   ,\\t \\t\\t  \\r\\r\\n\\n\"a\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"aa\"   ,\\t \\t\\t  \\r\\r\\n\\n\"aa\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\\"\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\\"\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\\\\\\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\\\\\\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\/\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\/\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\b\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\b\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\f\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\f\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\n\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\n\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\r\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\r\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\t\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\t\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t\"\\\\u0000\"   ,\\t \\t\\t  \\r\\r\\n\\n\"\\\\u0000\"\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\tB   ,\\t \\t\\t  \\r\\r\\n\\nB\\r\\n]\n  s = '  [\\ttrue   ,\\t \\t\\t  \\r\\r\\n\\ntrue\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tfalse   ,\\t \\t\\t  \\r\\r\\n\\nfalse\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\tnull   ,\\t \\t\\t  \\r\\r\\n\\nnull\\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE   [\\t {\\nK:V\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\nK:V\\n } \\r\\n]\n  s = '  [\\t {\\n\"a\":1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":0.3\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":0.3\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-0.4\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-0.4\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":0.37\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":0.37\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-0.48\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-0.48\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":9.3\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":9.3\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-1.4\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-1.4\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":2.37\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":2.37\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-3.48\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-3.48\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":94.3\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":94.3\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-15.4\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-15.4\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":26.37\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":26.37\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-37.48\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-37.48\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":1e8\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1e8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e9\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2e9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e0\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51e0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62e1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1e82\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1e82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e93\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2e93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e04\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51e04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e15\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62e15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E8\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1E8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E9\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2E9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E0\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51E0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62E1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E82\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1E82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E93\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2E93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E04\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51E04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E15\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62E15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":1e-8\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1e-8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e-9\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2e-9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e-0\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51e-0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e-1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62e-1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1e-82\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1e-82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e-93\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2e-93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e-04\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51e-04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e-15\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62e-15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E-8\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1E-8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E-9\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2E-9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E-0\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51E-0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E-1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62E-1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E-82\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1E-82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E-93\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2E-93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E-04\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51E-04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E-15\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62E-15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":1e+8\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1e+8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e+9\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2e+9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e+0\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51e+0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e+1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62e+1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1e+82\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1e+82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2e+93\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2e+93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51e+04\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51e+04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62e+15\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62e+15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E+8\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1E+8\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E+9\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2E+9\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E+0\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51E+0\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E+1\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62E+1\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":1E+82\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":1E+82\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-2E+93\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-2E+93\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":51E+04\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":51E+04\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":-62E+15\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":-62E+15\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":\"a\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"a\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"aa\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"aa\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\\"\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\\"\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\\\\\\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\\\\\\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\/\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\/\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\b\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\b\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\f\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\f\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\n\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\n\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\r\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\r\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":\"\\\\t\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\t\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s =\n    '  [\\t {\\n\"a\":\"\\\\u0000\"\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":\"\\\\u0000\"\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  s = '  [\\t {\\n\"a\":true\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":true\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":false\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":false\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n  s = '  [\\t {\\n\"a\":null\\n }    ,\\t \\t\\t  \\r\\r\\n\\n {\\n\"a\":null\\n } \\r\\n]'\n  assert.deepEqual(j(s), P(s))\n\n  // SPACE  {\\nK:  [\\tV\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\nK:  [\\tV\\r\\n]\\n }\n  s = ' {\\n\"a\":  [\\t1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"a\":  [\\t1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t0.3\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t0.3\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-0.4\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-0.4\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t0.37\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t0.37\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-0.48\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-0.48\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t9.3\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t9.3\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-1.4\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-1.4\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t2.37\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t2.37\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-3.48\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-3.48\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t94.3\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t94.3\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-15.4\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-15.4\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t26.37\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t26.37\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-37.48\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-37.48\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\t1e8\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1e8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e9\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2e9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e0\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51e0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62e1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1e82\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1e82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e93\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2e93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e04\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51e04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e15\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62e15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E8\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1E8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E9\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2E9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E0\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51E0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62E1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E82\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1E82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E93\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2E93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E04\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51E04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E15\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62E15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\t1e-8\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1e-8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e-9\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2e-9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e-0\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51e-0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e-1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62e-1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1e-82\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1e-82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e-93\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2e-93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e-04\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51e-04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e-15\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62e-15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E-8\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1E-8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E-9\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2E-9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E-0\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51E-0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E-1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62E-1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E-82\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1E-82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E-93\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2E-93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E-04\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51E-04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E-15\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62E-15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\t1e+8\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1e+8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e+9\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2e+9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e+0\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51e+0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e+1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62e+1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1e+82\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1e+82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2e+93\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2e+93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51e+04\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51e+04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62e+15\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62e+15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E+8\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1E+8\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E+9\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2E+9\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E+0\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51E+0\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E+1\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62E+1\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t1E+82\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t1E+82\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-2E+93\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-2E+93\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t51E+04\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t51E+04\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\t-62E+15\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\t-62E+15\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n\n  s = ' {\\n\"a\":  [\\ttrue\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\ttrue\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\tfalse\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\tfalse\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n  s = ' {\\n\"a\":  [\\tnull\\r\\n]   ,\\t \\t\\t  \\r\\r\\n\\n\"b\":  [\\tnull\\r\\n]\\n } '\n  assert.deepEqual(j(s), P(s))\n}\n"
  },
  {
    "path": "test/jsonic.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\n// const Util = require('util')\n\n// let Lab = require('@hapi/lab')\n// Lab = null != Lab.script ? Lab : require('hapi-lab-shim')\n\n// const lab = (exports.lab = Lab.script())\n// const describe = lab.describe\n// const it = lab.it\n\n// const I = Util.inspect\n\nconst { Jsonic, JsonicError, makeRule, makeRuleSpec } = require('..')\nconst { loadTSV } = require('./utility')\nconst Exhaust = require('./exhaust')\nconst Large = require('./large')\nconst JsonStandard = require('./json-standard')\n\nlet j = Jsonic\n\nfunction tsvTest(name) {\n  const entries = loadTSV(name)\n  for (const { cols: [input, expected], row } of entries) {\n    try {\n      assert.deepEqual(Jsonic(input), JSON.parse(expected))\n    } catch (err) {\n      err.message = `${name} row ${row}: input=${input} expected=${expected}\\n${err.message}`\n      throw err\n    }\n  }\n}\n\ndescribe('jsonic', function () {\n  it('happy', () => {\n    tsvTest('happy')\n  })\n\n  it('options', () => {\n    let j = Jsonic.make({ x: 1 })\n\n    assert.deepEqual(j.options.x, 1)\n    assert.deepEqual(Object.keys({ x: 1 }).reduce((a,k)=>(a[k]=({ ...j.options })[k],a),{}), { x: 1 })\n\n    j.options({ x: 2 })\n    assert.deepEqual(j.options.x, 2)\n    assert.deepEqual(Object.keys({ x: 2 }).reduce((a,k)=>(a[k]=({ ...j.options })[k],a),{}), { x: 2 })\n\n    j.options()\n    assert.deepEqual(j.options.x, 2)\n\n    j.options(null)\n    assert.deepEqual(j.options.x, 2)\n\n    j.options('ignored')\n    assert.deepEqual(j.options.x, 2)\n\n    assert.deepEqual(j.options.comment.lex, true)\n    assert.deepEqual(j.options().comment.lex, true)\n    assert.deepEqual(j.internal().config.comment.lex, true)\n    j.options({ comment: { lex: false } })\n    assert.deepEqual(j.options.comment.lex, false)\n    assert.deepEqual(j.options().comment.lex, false)\n    assert.deepEqual(j.internal().config.comment.lex, false)\n\n    let k = Jsonic.make()\n    assert.deepEqual(k.options.comment.lex, true)\n    assert.deepEqual(k.options().comment.lex, true)\n    assert.deepEqual(k.internal().config.comment.lex, true)\n    assert.deepEqual(k.rule().val.def.open.length > 4, true)\n    k.use((jsonic) => {\n      jsonic.options({\n        comment: { lex: false },\n        rule: { include: 'json' },\n      })\n    })\n\n    assert.deepEqual(k.options.comment.lex, false)\n    assert.deepEqual(k.options().comment.lex, false)\n    assert.deepEqual(k.internal().config.comment.lex, false)\n    assert.deepEqual(k.rule().val.def.open.length, 3)\n\n    let k1 = Jsonic.make()\n    k1.use((jsonic) => {\n      jsonic.options({\n        rule: { exclude: 'json' },\n      })\n    })\n    // console.log(k1.rule().val.def.open)\n    assert.deepEqual(k1.rule().val.def.open.length, 6)\n  })\n\n  it('token-gen', () => {\n    let j = Jsonic.make()\n\n    let suffix = Math.random()\n    let s = j.token('__' + suffix)\n\n    let s1 = j.token('AA' + suffix)\n    assert.deepEqual(s1, s + 1)\n    assert.deepEqual(j.token['AA' + suffix], s + 1)\n    assert.deepEqual(j.token[s + 1], 'AA' + suffix)\n    assert.deepEqual(j.token('AA' + suffix), s + 1)\n    assert.deepEqual(j.token(s + 1), 'AA' + suffix)\n\n    let s1a = j.token('AA' + suffix)\n    assert.deepEqual(s1a, s + 1)\n    assert.deepEqual(j.token['AA' + suffix], s + 1)\n    assert.deepEqual(j.token[s + 1], 'AA' + suffix)\n    assert.deepEqual(j.token('AA' + suffix), s + 1)\n    assert.deepEqual(j.token(s + 1), 'AA' + suffix)\n\n    let s2 = j.token('BB' + suffix)\n    assert.deepEqual(s2, s + 2)\n    assert.deepEqual(j.token['BB' + suffix], s + 2)\n    assert.deepEqual(j.token[s + 2], 'BB' + suffix)\n    assert.deepEqual(j.token('BB' + suffix), s + 2)\n    assert.deepEqual(j.token(s + 2), 'BB' + suffix)\n  })\n\n  it('token-fixed', () => {\n    let j = Jsonic.make()\n\n    assert.deepEqual({ ...j.fixed }, {\n      12: '{',\n      13: '}',\n      14: '[',\n      15: ']',\n      16: ':',\n      17: ',',\n      '{': 12,\n      '}': 13,\n      '[': 14,\n      ']': 15,\n      ':': 16,\n      ',': 17,\n    })\n\n    assert.deepEqual(j.fixed('{'), 12)\n    assert.deepEqual(j.fixed('}'), 13)\n    assert.deepEqual(j.fixed('['), 14)\n    assert.deepEqual(j.fixed(']'), 15)\n    assert.deepEqual(j.fixed(':'), 16)\n    assert.deepEqual(j.fixed(','), 17)\n\n    assert.deepEqual(j.fixed(12), '{')\n    assert.deepEqual(j.fixed(13), '}')\n    assert.deepEqual(j.fixed(14), '[')\n    assert.deepEqual(j.fixed(15), ']')\n    assert.deepEqual(j.fixed(16), ':')\n    assert.deepEqual(j.fixed(17), ',')\n\n    j.options({\n      fixed: {\n        token: {\n          '#A': 'a',\n          '#BB': 'bb',\n        },\n      },\n    })\n\n    assert.deepEqual({ ...j.fixed }, {\n      12: '{',\n      13: '}',\n      14: '[',\n      15: ']',\n      16: ':',\n      17: ',',\n      18: 'a',\n      19: 'bb',\n      '{': 12,\n      '}': 13,\n      '[': 14,\n      ']': 15,\n      ':': 16,\n      ',': 17,\n      a: 18,\n      bb: 19,\n    })\n\n    assert.deepEqual(j.fixed('{'), 12)\n    assert.deepEqual(j.fixed('}'), 13)\n    assert.deepEqual(j.fixed('['), 14)\n    assert.deepEqual(j.fixed(']'), 15)\n    assert.deepEqual(j.fixed(':'), 16)\n    assert.deepEqual(j.fixed(','), 17)\n    assert.deepEqual(j.fixed('a'), 18)\n    assert.deepEqual(j.fixed('bb'), 19)\n\n    assert.deepEqual(j.fixed(12), '{')\n    assert.deepEqual(j.fixed(13), '}')\n    assert.deepEqual(j.fixed(14), '[')\n    assert.deepEqual(j.fixed(15), ']')\n    assert.deepEqual(j.fixed(16), ':')\n    assert.deepEqual(j.fixed(17), ',')\n    assert.deepEqual(j.fixed(18), 'a')\n    assert.deepEqual(j.fixed(19), 'bb')\n  })\n\n  it('basic-json', () => {\n    tsvTest('jsonic-basic-json')\n  })\n\n  it('basic-object-tree', () => {\n    tsvTest('jsonic-basic-object-tree')\n  })\n\n  it('basic-array-tree', () => {\n    tsvTest('jsonic-basic-array-tree')\n  })\n\n  it('basic-mixed-tree', () => {\n    tsvTest('jsonic-basic-mixed-tree')\n  })\n\n  it('syntax-errors', () => {\n    // bad close\n    assert.throws(() => j('}'))\n    assert.throws(() => j(']'))\n\n    // top level already is a map\n    assert.throws(() => j('a:1,2'))\n\n    // values not valid inside map\n    assert.throws(() => j('x:{1,2}'))\n  })\n\n  it('process-scalars', () => {\n    tsvTest('jsonic-process-scalars')\n  })\n\n  it('process-text', () => {\n    tsvTest('jsonic-process-text')\n  })\n\n  it('process-implicit-object', () => {\n    tsvTest('jsonic-process-implicit-object')\n  })\n\n  it('process-object-tree', () => {\n    tsvTest('jsonic-process-object-tree')\n  })\n\n  it('process-array', () => {\n    tsvTest('jsonic-process-array')\n  })\n\n  it('process-mixed-nodes', () => {\n    tsvTest('jsonic-process-mixed-nodes')\n  })\n\n  it('process-comment', () => {\n    assert.deepEqual(j('a:q\\nb:w #X\\nc:r \\n\\nd:t\\n\\n#'), {\n      a: 'q',\n      b: 'w',\n      c: 'r',\n      d: 't',\n    })\n\n    let jm = j.make({ comment: { lex: false } })\n    assert.deepEqual(jm('a:q\\nb:w#X\\nc:r \\n\\nd:t'), {\n      a: 'q',\n      b: 'w#X',\n      c: 'r',\n      d: 't',\n    })\n  })\n\n  it('process-whitespace', () => {\n    tsvTest('jsonic-process-whitespace')\n  })\n\n  it('funky-keys', () => {\n    tsvTest('jsonic-funky-keys')\n  })\n\n  it('api', () => {\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n    assert.deepEqual(Jsonic.parse('a:1'), { a: 1 })\n  })\n\n  it('rule-spec', () => {\n    let cfg = {}\n\n    let rs0 = j.makeRuleSpec({}, cfg, {})\n    assert.deepEqual(rs0.name, '')\n    assert.deepEqual(rs0.def.open, [])\n    assert.deepEqual(rs0.def.close, [])\n\n    let rs1 = j.makeRuleSpec({}, cfg, {\n      open: [\n        {},\n        { c: () => true },\n        { c: (r) => r.lte() },\n        { c: {} },\n      ],\n    })\n    \n    assert.deepEqual(rs1.def.open[0].c, undefined)\n    assert.deepEqual(typeof rs1.def.open[1].c === 'function', true)\n    assert.deepEqual(typeof rs1.def.open[2].c === 'function', true)\n\n    let rs2 = j.makeRuleSpec({}, cfg, {\n      open: [\n        { c: (r) => r.lte('a', 10) && r.lte('b', 20) },\n      ],\n    })\n    let c0 = rs2.def.open[0].c\n    let mr = (n) => {\n      let r = makeRule({ name: '', def: {} }, { uI: 0 })\n      r.n = n\n      return r\n    }\n    assert.deepEqual(c0(mr({})), true)\n    assert.deepEqual(c0(mr({ a: 5 })), true)\n    assert.deepEqual(c0(mr({ a: 10 })), true)\n    assert.deepEqual(c0(mr({ a: 15 })), false)\n    assert.deepEqual(c0(mr({ b: 19 })), true)\n    assert.deepEqual(c0(mr({ b: 20 })), true)\n    assert.deepEqual(c0(mr({ b: 21 })), false)\n\n    assert.deepEqual(c0(mr({ a: 10, b: 20 })), true)\n    assert.deepEqual(c0(mr({ a: 10, b: 21 })), false)\n    assert.deepEqual(c0(mr({ a: 11, b: 21 })), false)\n    assert.deepEqual(c0(mr({ a: 11, b: 20 })), false)\n  })\n\n  it('id-string', function () {\n    let s0 = '' + Jsonic\n    assert.ok(s0.match(/Jsonic.*/) != null)\n    assert.deepEqual('' + Jsonic, s0)\n    assert.deepEqual('' + Jsonic, '' + Jsonic)\n\n    let j1 = Jsonic.make()\n    let s1 = '' + j1\n    assert.ok(s1.match(/Jsonic.*/) != null)\n    assert.deepEqual('' + j1, s1)\n    assert.deepEqual('' + j1, '' + j1)\n    assert.notDeepEqual(s0, s1)\n\n    let j2 = Jsonic.make({ tag: 'foo' })\n    let s2 = '' + j2\n    assert.ok(s2.match(/Jsonic.*foo/) != null)\n    assert.deepEqual('' + j2, s2)\n    assert.deepEqual('' + j2, '' + j2)\n    assert.notDeepEqual(s0, s2)\n    assert.notDeepEqual(s1, s2)\n  })\n\n  // Test against all combinations of chars up to `len`\n  // NOTE: coverage tracing slows this down - a lot!\n  it('exhaust-perf', function () {\n    let len = 2\n\n    // Use this env var for debug-code-test loop to avoid\n    // slowing things down. Do run this test for builds!\n    if (null == process.env.JSONIC_TEST_SKIP_PERF) {\n      let out = Exhaust(len)\n\n      // NOTE: if parse algo changes then these may change.\n      // But if *not intended* changes here indicate unexpected effects.\n      assert.deepEqual(Object.keys({\n        rmc: 62734,\n        emc: 2292,\n        ecc: {\n          unprintable: 91,\n          unexpected: 1508,\n          unterminated_string: 692,\n          unterminated_comment: 1,\n        },\n      }).reduce((a,k)=>(a[k]=(out)[k],a),{}), {\n        rmc: 62734,\n        emc: 2292,\n        ecc: {\n          unprintable: 91,\n          unexpected: 1508,\n          unterminated_string: 692,\n          unterminated_comment: 1,\n        },\n      })\n    }\n  })\n\n  it('large-perf', function () {\n    let len = 12345 // Coverage really nerfs this test sadly\n    // let len = 520000 // Pretty much the V8 string length limit\n\n    // Use this env var for debug-code-test loop to avoid\n    // slowing things down. Do run this test for builds!\n    if (null == process.env.JSONIC_TEST_SKIP_PERF) {\n      let out = Large(len)\n\n      // NOTE: if parse algo changes then these may change.\n      // But if *not intended* changes here indicate unexpected effects.\n      assert.deepEqual(Object.keys({\n        ok: true,\n        len: len * 1000,\n      }).reduce((a,k)=>(a[k]=(out)[k],a),{}), {\n        ok: true,\n        len: len * 1000,\n      })\n    }\n  })\n\n  // Validate pure JSON to ensure Jsonic is always a superset.\n  it('json-standard', function () {\n    JsonStandard(Jsonic)\n  })\n\n  it('src-not-string', () => {\n    assert.deepEqual(Jsonic({}), {})\n    assert.deepEqual(Jsonic([]), [])\n    assert.deepEqual(Jsonic(true), true)\n    assert.deepEqual(Jsonic(false), false)\n    assert.deepEqual(Jsonic(null), null)\n    assert.deepEqual(Jsonic(undefined), undefined)\n    assert.deepEqual(Jsonic(1), 1)\n    assert.deepEqual(Jsonic(/a/), /a/)\n\n    let sa = Symbol('a')\n    assert.deepEqual(Jsonic(sa), sa)\n  })\n\n  it('src-empty-string', () => {\n    assert.deepEqual(Jsonic(''), undefined)\n\n    assert.throws(() => Jsonic.make({ lex: { empty: false } }).parse(''), /unexpected.*:1:1/s,)\n  })\n})\n\nfunction make_empty(opts) {\n  let j = Jsonic.make(opts)\n  let rns = j.rule()\n  Object.keys(rns).map((rn) => j.rule(rn, null))\n  return j\n}\n"
  },
  {
    "path": "test/justjson.js",
    "content": "const Fs = require('fs')\n\nconst Jsonic = require('..')\n\n// Validate that Jsonic plain JSON matches system JSON.parse\nlet json = Jsonic.make('json')\n\nlet all = ' {}[]:,SNV'\nlet same = {\n  '{': '{',\n  '}': '}',\n  '[': '[',\n  ']': ']',\n  ':': ':',\n  ',': ',',\n  ' ': ' ',\n}\nlet replace = { S: '\"s\"', N: 0, V: true }\nlet len = 8\n\nlet mismatch = []\n// let c = Array(all.length+1).fill(0) // +1 for empty\nlet c = Array(all.length).fill(-1) // +1 for empty\nlet clen = c.length\nlet start = parseInt(process.argv[2]) || 0\nlet end = parseInt(process.argv[3]) || Math.pow(clen, len)\n\n// console.log(clen,c,end)\n\n// Count and carry\n// c[0]=-1\nlet i = start\n\nFs.writeFileSync('./mismatch.txt', '')\nlet fd = Fs.openSync('./mismatch.txt', 'a')\n\nlet d = 0\nlet startTime = Date.now()\nfor (; i < end; i++) {\n  d++\n  if (0 === i % 1e5) {\n    let now = Date.now()\n    console.log(\n      'COUNT',\n      i,\n      'PERCENT',\n      Math.round((1000 * i) / end) / 10,\n      'RATE',\n      Math.round(d / ((now - startTime) / 1000)),\n      'DUR',\n      (now - startTime) / 1000,\n      'MATCH',\n      Math.round((10000000 * (d - mismatch.length)) / d) / 100000,\n    )\n  }\n\n  let j = 0\n  while (j < clen) {\n    c[j]++\n    if (clen <= c[j]) {\n      c[j] = 0\n      j++\n    } else {\n      j = clen\n    }\n  }\n\n  // console.log(c)\n\n  // undefined gets left out by join\n  // thus we get all strings up to length len\n  let s = c\n    .map((n) => all[n])\n    .map((nc) => same[nc] || replace[nc])\n    .join('')\n  // console.log(s)\n\n  let sysVal = undefined\n  let sysErr = undefined\n\n  try {\n    sysVal = JSON.parse(s)\n  } catch (e) {\n    sysErr = e\n  }\n\n  let jscVal = undefined\n  let jscErr = undefined\n\n  try {\n    jscVal = json(s)\n  } catch (e) {\n    jscErr = e\n  }\n\n  if (!!jscErr === !!sysErr) {\n    if (null == jscErr && JSON.stringify(sysVal) !== JSON.stringify(jscVal)) {\n      let entry =\n        'VAL MISMATCH: ' +\n        i +\n        ':' +\n        c +\n        ' <<' +\n        s +\n        '>>' +\n        JSON.stringify(sysVal) +\n        ' !==  ' +\n        JSON.stringify(jscVal) +\n        '\\n\\n'\n\n      mismatch.push(entry)\n      Fs.writeSync(fd, entry)\n    }\n  } else {\n    let entry =\n      'ERR MISMATCH: ' +\n      i +\n      ':' +\n      c +\n      ' <<' +\n      s +\n      '>>' +\n      '\\nERRORS:' +\n      sysErr?.message +\n      ' ~ ' +\n      jscErr?.message +\n      '\\nVALUES:' +\n      JSON.stringify(sysVal) +\n      ' ~ ' +\n      JSON.stringify(jscVal) +\n      '\\n\\n'\n    mismatch.push(entry)\n    Fs.writeSync(fd, entry)\n  }\n}\n\nlet endTime = Date.now()\nconsole.log(\n  'TOTAL: ',\n  end,\n  'DONE: ',\n  i,\n  'MISMATCH',\n  mismatch.length,\n  'RATE',\n  Math.round(d / ((endTime - startTime) / 1000)),\n  'DUR',\n  (endTime - startTime) / 1000,\n  'MATCH',\n  Math.round((10000000 * (d - mismatch.length)) / d) / 100000,\n)\n\nFs.closeSync(fd)\n"
  },
  {
    "path": "test/large.js",
    "content": "// Test very large sources.\n\nconst Util = require('util')\nconst I = Util.inspect\n\nconst { Jsonic } = require('..')\n\nmodule.exports = large\n\nif (require.main === module) {\n  large(parseInt(process.argv[2] || 3), true)\n}\n\nfunction large(size, print) {\n  const j01 = Jsonic.make()\n\n  const v0 = 'a'.repeat(1000 * size)\n  const s0 = '\"' + v0 + '\"'\n\n  print && console.log('LEN:', v0.length)\n  const start = Date.now()\n  const o0 = Jsonic(s0)\n  const ok = v0 == o0\n  print && console.log('EQL:', ok)\n  print && console.log('DUR:', Date.now() - start)\n\n  return {\n    ok: v0 == o0,\n    len: v0.length,\n  }\n}\n"
  },
  {
    "path": "test/lex.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, makeLex, JsonicError } = require('..')\n\ndescribe('lex', function () {\n  let j, t, config\n\n  function lexall(src) {\n    let lex = lexstart(src)\n    let out = []\n    do {\n      // console.log(out[out.length-1])\n      out.push({ ...lex() })\n    } while (t.ZZ != out[out.length - 1].tin && t.BD != out[out.length - 1].tin)\n    return out.map((t) => st(t))\n  }\n\n  function alleq(ta) {\n    for (let i = 0; i < ta.length; i += 2) {\n      let suffix = ' CASE:' + i / 2 + ' [' + ta[i] + ']'\n      assert.deepEqual(lexall(ta[i]) + suffix, ta[i + 1] + suffix)\n    }\n  }\n\n  function lexstart(src) {\n    j = Jsonic.make()\n    config = j.internal().config\n    t = j.token\n\n    let lex = makeLex({ src: () => src, cfg: config, opts: j.options, sub: {} })\n    return lex.next.bind(lex)\n  }\n\n  it('jsonic-token', () => {\n    lexstart('')\n    assert.ok(j.token.OB != null)\n    assert.ok(t.CB != null)\n  })\n\n  it('specials', () => {\n    let lex0 = lexstart(' {123 ')\n    assert.deepEqual('' + lex0(), 'Token[#SP=5   0,1,1]')\n    assert.deepEqual('' + lex0(), 'Token[#OB=12 { 1,1,2]')\n    assert.deepEqual('' + lex0(), 'Token[#NR=8 123=123 2,1,3]')\n    assert.deepEqual('' + lex0(), 'Token[#SP=5   5,1,6]')\n    assert.deepEqual('' + lex0(), 'Token[#ZZ=2  6,1,7]')\n    assert.deepEqual('' + lex0(), 'Token[#ZZ=2  6,1,7]')\n    assert.deepEqual('' + lex0(), 'Token[#ZZ=2  6,1,7]')\n\n    let lex1 = lexstart('\"\\\\u0040\\\\u{012345}\"')\n    let t0 = lex1()\n    assert.deepEqual(t0.val, '\\u0040\\u{012345}')\n    assert.deepEqual(t0.len, 18)\n    assert.deepEqual('' + t0, 'Token[#ST=9 \"\\\\u00 0,1,1]') // NOTE: truncated!\n\n    assert.deepEqual(lexall(' {123'), [\n      '#SP;0;1;1x1',\n      '#OB;1;1;1x2',\n      '#NR;2;3;1x3;123',\n      '#ZZ;5;0;1x6',\n    ])\n\n    assert.deepEqual(lexall(' {123%'), [\n      '#SP;0;1;1x1',\n      '#OB;1;1;1x2',\n      '#TX;2;4;1x3;123%',\n      '#ZZ;6;0;1x7',\n    ])\n\n    alleq(['', ['#ZZ;0;0;1x1'], '0', ['#NR;0;1;1x1;0', '#ZZ;1;0;1x2']])\n  })\n\n  it('space', () => {\n    let lex0 = lexstart(' \\t')\n    assert.deepEqual('' + lex0(), 'Token[#SP=5  . 0,1,1]')\n\n    alleq([\n      ' ',\n      ['#SP;0;1;1x1', '#ZZ;1;0;1x2'],\n      '  ',\n      ['#SP;0;2;1x1', '#ZZ;2;0;1x3'],\n      ' \\t',\n      ['#SP;0;2;1x1', '#ZZ;2;0;1x3'],\n      ' \\t ',\n      ['#SP;0;3;1x1', '#ZZ;3;0;1x4'],\n      '\\t \\t',\n      ['#SP;0;3;1x1', '#ZZ;3;0;1x4'],\n      '\\t ',\n      ['#SP;0;2;1x1', '#ZZ;2;0;1x3'],\n      '\\t\\t',\n      ['#SP;0;2;1x1', '#ZZ;2;0;1x3'],\n      '\\t',\n      ['#SP;0;1;1x1', '#ZZ;1;0;1x2'],\n    ])\n  })\n\n  it('brace', () => {\n    alleq([\n      '{',\n      ['#OB;0;1;1x1', '#ZZ;1;0;1x2'],\n      '{{',\n      ['#OB;0;1;1x1', '#OB;1;1;1x2', '#ZZ;2;0;1x3'],\n      '}',\n      ['#CB;0;1;1x1', '#ZZ;1;0;1x2'],\n      '}}',\n      ['#CB;0;1;1x1', '#CB;1;1;1x2', '#ZZ;2;0;1x3'],\n    ])\n  })\n\n  it('square', () => {\n    alleq([\n      '[',\n      ['#OS;0;1;1x1', '#ZZ;1;0;1x2'],\n      '[[',\n      ['#OS;0;1;1x1', '#OS;1;1;1x2', '#ZZ;2;0;1x3'],\n      ']',\n      ['#CS;0;1;1x1', '#ZZ;1;0;1x2'],\n      ']]',\n      ['#CS;0;1;1x1', '#CS;1;1;1x2', '#ZZ;2;0;1x3'],\n    ])\n  })\n\n  it('colon', () => {\n    alleq([\n      ':',\n      ['#CL;0;1;1x1', '#ZZ;1;0;1x2'],\n      '::',\n      ['#CL;0;1;1x1', '#CL;1;1;1x2', '#ZZ;2;0;1x3'],\n    ])\n  })\n\n  it('comma', () => {\n    alleq([\n      ',',\n      ['#CA;0;1;1x1', '#ZZ;1;0;1x2'],\n      ',,',\n      ['#CA;0;1;1x1', '#CA;1;1;1x2', '#ZZ;2;0;1x3'],\n    ])\n  })\n\n  it('comment', () => {\n    alleq([\n      'a#b',\n      ['#TX;0;1;1x1;a', '#CM;1;2;1x2', '#ZZ;3;0;1x4'],\n      'a/*x*/b',\n      ['#TX;0;1;1x1;a', '#CM;1;5;1x2', '#TX;6;1;1x7;b', '#ZZ;7;0;1x8'],\n      'a#b\\nc',\n      [\n        '#TX;0;1;1x1;a',\n        '#CM;1;2;1x2',\n        '#LN;3;1;1x4',\n        '#TX;4;1;2x1;c',\n        '#ZZ;5;0;2x2',\n      ],\n      'a#b\\r\\nc',\n      [\n        '#TX;0;1;1x1;a',\n        '#CM;1;2;1x2',\n        '#LN;3;2;1x4',\n        '#TX;5;1;2x1;c',\n        '#ZZ;6;0;2x2',\n      ],\n    ])\n  })\n\n  it('boolean', () => {\n    alleq([\n      'true',\n      ['#VL;0;4;1x1;true', '#ZZ;4;0;1x5'],\n      'true ',\n      ['#VL;0;4;1x1;true', '#SP;4;1;1x5', '#ZZ;5;0;1x6'],\n      ' true',\n      ['#SP;0;1;1x1', '#VL;1;4;1x2;true', '#ZZ;5;0;1x6'],\n      'truex',\n      ['#TX;0;5;1x1;truex', '#ZZ;5;0;1x6'],\n      'truex ',\n      ['#TX;0;5;1x1;truex', '#SP;5;1;1x6', '#ZZ;6;0;1x7'],\n      'false',\n      ['#VL;0;5;1x1;false', '#ZZ;5;0;1x6'],\n      'false ',\n      ['#VL;0;5;1x1;false', '#SP;5;1;1x6', '#ZZ;6;0;1x7'],\n      ' false',\n      ['#SP;0;1;1x1', '#VL;1;5;1x2;false', '#ZZ;6;0;1x7'],\n      'falsex',\n      ['#TX;0;6;1x1;falsex', '#ZZ;6;0;1x7'],\n      'falsex ',\n      ['#TX;0;6;1x1;falsex', '#SP;6;1;1x7', '#ZZ;7;0;1x8'],\n    ])\n  })\n\n  it('null', () => {\n    alleq([\n      'null',\n      ['#VL;0;4;1x1;null', '#ZZ;4;0;1x5'],\n      'null ',\n      ['#VL;0;4;1x1;null', '#SP;4;1;1x5', '#ZZ;5;0;1x6'],\n      ' null',\n      ['#SP;0;1;1x1', '#VL;1;4;1x2;null', '#ZZ;5;0;1x6'],\n      'nullx',\n      ['#TX;0;5;1x1;nullx', '#ZZ;5;0;1x6'],\n      'nullx ',\n      ['#TX;0;5;1x1;nullx', '#SP;5;1;1x6', '#ZZ;6;0;1x7'],\n      'nulx ',\n      ['#TX;0;4;1x1;nulx', '#SP;4;1;1x5', '#ZZ;5;0;1x6'],\n      'nulx',\n      ['#TX;0;4;1x1;nulx', '#ZZ;4;0;1x5'],\n    ])\n  })\n\n  it('number', () => {\n    let lex0 = lexstart('321')\n    assert.deepEqual('' + lex0(), 'Token[#NR=8 321=321 0,1,1]')\n\n    alleq([\n      '0',\n      ['#NR;0;1;1x1;0', '#ZZ;1;0;1x2'],\n      '0.',\n      ['#NR;0;2;1x1;0', '#ZZ;2;0;1x3'],\n      '.0',\n      ['#NR;0;2;1x1;0', '#ZZ;2;0;1x3'],\n      '-0',\n      ['#NR;0;2;1x1;0', '#ZZ;2;0;1x3'],\n      '-.0',\n      ['#NR;0;3;1x1;0', '#ZZ;3;0;1x4'],\n      '1.2',\n      ['#NR;0;3;1x1;1.2', '#ZZ;3;0;1x4'],\n      '-1.2',\n      ['#NR;0;4;1x1;-1.2', '#ZZ;4;0;1x5'],\n      '0xA',\n      ['#NR;0;3;1x1;10', '#ZZ;3;0;1x4'],\n      '1e2',\n      ['#NR;0;3;1x1;100', '#ZZ;3;0;1x4'],\n      '0e0',\n      ['#NR;0;3;1x1;0', '#ZZ;3;0;1x4'],\n      '-1.5E2',\n      ['#NR;0;6;1x1;-150', '#ZZ;6;0;1x7'],\n      '0x',\n      ['#TX;0;2;1x1;0x', '#ZZ;2;0;1x3'],\n      '-0xA',\n      ['#NR;0;4;1x1;-10', '#ZZ;4;0;1x5'],\n      '01',\n      ['#NR;0;2;1x1;1', '#ZZ;2;0;1x3'],\n      '1x',\n      ['#TX;0;2;1x1;1x', '#ZZ;2;0;1x3'],\n      '12x',\n      ['#TX;0;3;1x1;12x', '#ZZ;3;0;1x4'],\n      '1%',\n      ['#TX;0;2;1x1;1%', '#ZZ;2;0;1x3'],\n      '12%',\n      ['#TX;0;3;1x1;12%', '#ZZ;3;0;1x4'],\n      '123%',\n      ['#TX;0;4;1x1;123%', '#ZZ;4;0;1x5'],\n      '1_0_0',\n      ['#NR;0;5;1x1;100', '#ZZ;5;0;1x6'],\n    ])\n  })\n\n  it('double-quote', () => {\n    // NOTE: col for unterminated is final col\n    alleq([\n      '\"\"',\n      ['#ST;0;2;1x1;', '#ZZ;2;0;1x3'],\n      '\"a\"',\n      ['#ST;0;3;1x1;a', '#ZZ;3;0;1x4'],\n      '\"ab\"',\n      ['#ST;0;4;1x1;ab', '#ZZ;4;0;1x5'],\n      '\"abc\"',\n      ['#ST;0;5;1x1;abc', '#ZZ;5;0;1x6'],\n      '\"a b\"',\n      ['#ST;0;5;1x1;a b', '#ZZ;5;0;1x6'],\n      ' \"a\"',\n      ['#SP;0;1;1x1', '#ST;1;3;1x2;a', '#ZZ;4;0;1x5'],\n      '\"a\" ',\n      ['#ST;0;3;1x1;a', '#SP;3;1;1x4', '#ZZ;4;0;1x5'],\n      ' \"a\" ',\n      ['#SP;0;1;1x1', '#ST;1;3;1x2;a', '#SP;4;1;1x5', '#ZZ;5;0;1x6'],\n      '\"',\n      ['#BD;0;1;1x1;\"~unterminated_string'],\n      '\"a',\n      ['#BD;0;2;1x1;\"a~unterminated_string'],\n      '\"ab',\n      ['#BD;0;3;1x1;\"ab~unterminated_string'],\n      ' \"',\n      ['#SP;0;1;1x1', '#BD;1;1;1x2;\"~unterminated_string'],\n      ' \"a',\n      ['#SP;0;1;1x1', '#BD;1;2;1x2;\"a~unterminated_string'],\n      ' \"ab',\n      ['#SP;0;1;1x1', '#BD;1;3;1x2;\"ab~unterminated_string'],\n      '\"a\\'b\"',\n      [\"#ST;0;5;1x1;a'b\", '#ZZ;5;0;1x6'],\n      '\"\\'a\\'b\"',\n      [\"#ST;0;6;1x1;'a'b\", '#ZZ;6;0;1x7'],\n      \"\\\"'a'b'\\\"\",\n      [\"#ST;0;7;1x1;'a'b'\", '#ZZ;7;0;1x8'],\n      '\"\\\\t\"',\n      ['#ST;0;4;1x1;\\t', '#ZZ;4;0;1x5'],\n      '\"\\\\r\"',\n      ['#ST;0;4;1x1;\\r', '#ZZ;4;0;1x5'],\n      '\"\\\\n\"',\n      ['#ST;0;4;1x1;\\n', '#ZZ;4;0;1x5'],\n      '\"\\\\\"\"',\n      ['#ST;0;4;1x1;\"', '#ZZ;4;0;1x5'],\n      '\"\\\\\\'\"',\n      [\"#ST;0;4;1x1;'\", '#ZZ;4;0;1x5'],\n      '\"\\\\q\"',\n      ['#ST;0;4;1x1;q', '#ZZ;4;0;1x5'],\n      '\"\\\\\\'\"',\n      [\"#ST;0;4;1x1;'\", '#ZZ;4;0;1x5'],\n      '\"\\\\\\\\\"',\n      ['#ST;0;4;1x1;\\\\', '#ZZ;4;0;1x5'],\n      '\"\\\\u0040\"',\n      ['#ST;0;8;1x1;@', '#ZZ;8;0;1x9'],\n      '\"\\\\uQQQQ\"',\n      ['#BD;1;6;1x2;\\\\uQQQQ~invalid_unicode'],\n      '\"\\\\u{QQQQQQ}\"',\n      ['#BD;1;10;1x2;\\\\u{QQQQQQ}~invalid_unicode'],\n      '\"\\\\xQQ\"',\n      ['#BD;1;4;1x2;\\\\xQQ~invalid_ascii'],\n      '\"[{}]:,\"',\n      ['#ST;0;8;1x1;[{}]:,', '#ZZ;8;0;1x9'],\n      '\"a\\\\\"\"',\n      ['#ST;0;5;1x1;a\"', '#ZZ;5;0;1x6'],\n      '\"a\\\\\"a\"',\n      ['#ST;0;6;1x1;a\"a', '#ZZ;6;0;1x7'],\n      '\"a\\\\\"a\\'a\"',\n      ['#ST;0;8;1x1;a\"a\\'a', '#ZZ;8;0;1x9'],\n    ])\n  })\n\n  it('single-quote', () => {\n    alleq([\n      \"''\",\n      ['#ST;0;2;1x1;', '#ZZ;2;0;1x3'],\n      \"'a'\",\n      ['#ST;0;3;1x1;a', '#ZZ;3;0;1x4'],\n      \"'ab'\",\n      ['#ST;0;4;1x1;ab', '#ZZ;4;0;1x5'],\n      \"'abc'\",\n      ['#ST;0;5;1x1;abc', '#ZZ;5;0;1x6'],\n      \"'a b'\",\n      ['#ST;0;5;1x1;a b', '#ZZ;5;0;1x6'],\n      \" 'a'\",\n      ['#SP;0;1;1x1', '#ST;1;3;1x2;a', '#ZZ;4;0;1x5'],\n      \"'a' \",\n      ['#ST;0;3;1x1;a', '#SP;3;1;1x4', '#ZZ;4;0;1x5'],\n      \" 'a' \",\n      ['#SP;0;1;1x1', '#ST;1;3;1x2;a', '#SP;4;1;1x5', '#ZZ;5;0;1x6'],\n      \"'\",\n      [\"#BD;0;1;1x1;'~unterminated_string\"],\n      \"'a\",\n      [\"#BD;0;2;1x1;'a~unterminated_string\"],\n      \"'ab\",\n      [\"#BD;0;3;1x1;'ab~unterminated_string\"],\n      \" '\",\n      ['#SP;0;1;1x1', \"#BD;1;1;1x2;'~unterminated_string\"],\n      \" 'a\",\n      ['#SP;0;1;1x1', \"#BD;1;2;1x2;'a~unterminated_string\"],\n      \" 'ab\",\n      ['#SP;0;1;1x1', \"#BD;1;3;1x2;'ab~unterminated_string\"],\n      \"'a\\\"b'\",\n      ['#ST;0;5;1x1;a\"b', '#ZZ;5;0;1x6'],\n      '\\'\"a\"b\\'',\n      ['#ST;0;6;1x1;\"a\"b', '#ZZ;6;0;1x7'],\n      '\\'\"a\"b\"\\'',\n      ['#ST;0;7;1x1;\"a\"b\"', '#ZZ;7;0;1x8'],\n      \"'\\\\t'\",\n      ['#ST;0;4;1x1;\\t', '#ZZ;4;0;1x5'],\n      \"'\\\\r'\",\n      ['#ST;0;4;1x1;\\r', '#ZZ;4;0;1x5'],\n      \"'\\\\n'\",\n      ['#ST;0;4;1x1;\\n', '#ZZ;4;0;1x5'],\n      \"'\\\\''\",\n      [\"#ST;0;4;1x1;'\", '#ZZ;4;0;1x5'],\n      \"'\\\\\\\"'\",\n      ['#ST;0;4;1x1;\"', '#ZZ;4;0;1x5'],\n      \"'\\\\q'\",\n      ['#ST;0;4;1x1;q', '#ZZ;4;0;1x5'],\n      \"'\\\\\\\"'\",\n      ['#ST;0;4;1x1;\"', '#ZZ;4;0;1x5'],\n      \"'\\\\\\\\'\",\n      ['#ST;0;4;1x1;\\\\', '#ZZ;4;0;1x5'],\n      \"'\\\\u0040'\",\n      ['#ST;0;8;1x1;@', '#ZZ;8;0;1x9'],\n      \"'\\\\uQQQQ'\",\n      ['#BD;1;6;1x2;\\\\uQQQQ~invalid_unicode'],\n      \"'\\\\u{QQQQQQ}'\",\n      ['#BD;1;10;1x2;\\\\u{QQQQQQ}~invalid_unicode'],\n      \"'\\\\xQQ'\",\n      ['#BD;1;4;1x2;\\\\xQQ~invalid_ascii'],\n      \"'[{}]:,'\",\n      ['#ST;0;8;1x1;[{}]:,', '#ZZ;8;0;1x9'],\n      \"'a\\\\''\",\n      [\"#ST;0;5;1x1;a'\", '#ZZ;5;0;1x6'],\n      \"'a\\\\'a'\",\n      [\"#ST;0;6;1x1;a'a\", '#ZZ;6;0;1x7'],\n      \"'a\\\\'a\\\"a'\",\n      ['#ST;0;8;1x1;a\\'a\"a', '#ZZ;8;0;1x9'],\n    ])\n  })\n\n  it('text', () => {\n    alleq([\n      'a-b',\n      ['#TX;0;3;1x1;a-b', '#ZZ;3;0;1x4'],\n      '$a_',\n      ['#TX;0;3;1x1;$a_', '#ZZ;3;0;1x4'],\n      '!%~',\n      ['#TX;0;3;1x1;!%~', '#ZZ;3;0;1x4'],\n      'a\"b',\n      ['#TX;0;3;1x1;a\"b', '#ZZ;3;0;1x4'],\n      \"a'b\",\n      [\"#TX;0;3;1x1;a'b\", '#ZZ;3;0;1x4'],\n      ' a b ',\n      [\n        '#SP;0;1;1x1',\n        '#TX;1;1;1x2;a',\n        '#SP;2;1;1x3',\n        '#TX;3;1;1x4;b',\n        '#SP;4;1;1x5',\n        '#ZZ;5;0;1x6',\n      ],\n      'a:',\n      ['#TX;0;1;1x1;a', '#CL;1;1;1x2', '#ZZ;2;0;1x3'],\n    ])\n  })\n\n  it('line', () => {\n    alleq([\n      '{a:1,\\nb:2}',\n      [\n        '#OB;0;1;1x1',\n\n        '#TX;1;1;1x2;a',\n        '#CL;2;1;1x3',\n        '#NR;3;1;1x4;1',\n\n        '#CA;4;1;1x5',\n        '#LN;5;1;1x6',\n\n        '#TX;6;1;2x1;b',\n        '#CL;7;1;2x2',\n        '#NR;8;1;2x3;2',\n\n        '#CB;9;1;2x4',\n        '#ZZ;10;0;2x5',\n      ],\n    ])\n  })\n\n  it('lex-flags', () => {\n    let no_comment = Jsonic.make({ comment: { lex: false } })\n    assert.deepEqual(Jsonic('a:1#b'), { a: 1 })\n    assert.deepEqual(Jsonic('a,1#b'), ['a', 1])\n    assert.deepEqual(no_comment('a:1#b'), { a: '1#b' })\n    assert.deepEqual(no_comment('a,1#b'), ['a', '1#b'])\n\n    // space becomes text if turned off\n    let no_space = Jsonic.make({ space: { lex: false } })\n    assert.deepEqual(Jsonic('a : 1'), { a: 1 })\n    assert.deepEqual(Jsonic('a , 1'), ['a', 1])\n    assert.deepEqual(no_space('a :1'), { 'a ': 1 })\n    assert.deepEqual(no_space('a ,1'), ['a ', 1])\n    assert.deepEqual(no_space('a: 1'), { a: ' 1' })\n    assert.deepEqual(no_space('a, 1'), ['a', ' 1'])\n\n    let no_number = Jsonic.make({ number: { lex: false } })\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n    assert.deepEqual(Jsonic('a,1'), ['a', 1])\n    assert.deepEqual(no_number('a:1'), { a: '1' })\n    assert.deepEqual(no_number('a,1'), ['a', '1'])\n\n    let no_string = Jsonic.make({ string: { lex: false } })\n    assert.deepEqual(Jsonic('a:1'), { a: 1 })\n    assert.deepEqual(Jsonic('a,1'), ['a', 1])\n    assert.deepEqual(Jsonic('a:\"a\"'), { a: 'a' })\n    assert.deepEqual(Jsonic('\"a\",1'), ['a', 1])\n    assert.deepEqual(no_string('a:1'), { a: 1 })\n    assert.deepEqual(no_string('a,1'), ['a', 1])\n    assert.deepEqual(no_string('a:\"a\"'), { a: '\"a\"' })\n    assert.deepEqual(no_string('\"a\",1'), ['\"a\"', 1])\n\n    let no_text = Jsonic.make({ text: { lex: false } })\n    assert.deepEqual(Jsonic('a:b'), { a: 'b' })\n    assert.deepEqual(Jsonic('a, b '), ['a', 'b'])\n    assert.throws(() => no_text('a:b c'), /unexpected/)\n    assert.throws(() => no_text('a,b c'), /unexpected/)\n\n    let no_value = Jsonic.make({ value: { lex: false } })\n    assert.deepEqual(Jsonic('a:true'), { a: true })\n    assert.deepEqual(Jsonic('a,null'), ['a', null])\n    assert.deepEqual(no_value('a:true'), { a: 'true' })\n    assert.deepEqual(no_value('a,null'), ['a', 'null'])\n\n    // line becomes text if turned off\n    let no_line = Jsonic.make({ line: { lex: false } })\n    assert.deepEqual(Jsonic('a:\\n1'), { a: 1 })\n    assert.deepEqual(Jsonic('a,\\n1'), ['a', 1])\n    assert.deepEqual(no_line('a:\\n1'), { a: '\\n1' })\n    assert.deepEqual(no_line('a,\\n1'), ['a', '\\n1'])\n  })\n\n  it('custom-matcher', () => {\n    let tens = Jsonic.make()\n\n    tens.options({\n      lex: {\n        match: {\n          x10: {\n            order: 9e5,\n            make: () => (lex) => {\n              let pnt = lex.pnt\n              let marks = lex.src.substring(pnt.sI).match(/^%+/)\n              if (marks) {\n                let len = marks[0].length\n                let tkn = lex.token('#VL', 10 * len, marks, lex.pnt)\n                pnt.sI += len\n                pnt.cI += len\n                return tkn\n              }\n            },\n          },\n        },\n      },\n    })\n\n    // console.dir(tens.internal().config.lex)\n\n    assert.deepEqual(tens('a:1,b:%%,c:[%%%%]'), { a: 1, b: 20, c: [40] })\n  })\n\n  function st(tkn) {\n    let out = []\n\n    function m(s, v, t) {\n      return [\n        s.substring(0, 3),\n        t.sI,\n        t.len,\n        t.rI + 'x' + t.cI,\n        v ? '' + t.val : null,\n      ]\n    }\n\n    switch (tkn.tin) {\n      case t.SP:\n        out = m('#SP', 0, tkn)\n        break\n\n      case t.LN:\n        out = m('#LN', 0, tkn)\n        break\n\n      case t.OB:\n        out = m('#OB{', 0, tkn)\n        break\n\n      case t.CB:\n        out = m('#CB}', 0, tkn)\n        break\n\n      case t.OS:\n        out = m('#OS[', 0, tkn)\n        break\n\n      case t.CS:\n        out = m('#CS]', 0, tkn)\n        break\n\n      case t.CL:\n        out = m('#CL:', 0, tkn)\n        break\n\n      case t.CA:\n        out = m('#CA,', 0, tkn)\n        break\n\n      case t.NR:\n        out = m('#NR', 1, tkn)\n        break\n\n      case t.ST:\n        out = m('#ST', 1, tkn)\n        break\n\n      case t.TX:\n        out = m('#TX', 1, tkn)\n        break\n\n      case t.VL:\n        out = m('#VL', 1, tkn)\n        break\n\n      case t.CM:\n        out = m('#CM', 0, tkn)\n        break\n\n      case t.BD:\n        tkn.val =\n          (undefined === tkn.val\n            ? undefined === tkn.src\n              ? ''\n              : tkn.src\n            : tkn.val) +\n          '~' +\n          tkn.why\n        out = m('#BD', 1, tkn)\n        break\n\n      case t.ZZ:\n        out = m('#ZZ', 0, tkn)\n        break\n    }\n\n    return out.filter((x) => null != x).join(';')\n  }\n})\n"
  },
  {
    "path": "test/long.js",
    "content": "// Validate very long documents\n// RUN:\n// ad hoc: node --expose-gc test/long.js 20\n// csv stats: node --expose-gc test/long.js 20 1.2\n\nconst Fs = require('fs')\n\nconst { Jsonic } = require('..')\n\nconst strs = [\n  {\n    len: true,\n    gen: (r) =>\n      '{' +\n      Array.from({ length: r }).map(\n        (x) => '\"' + Math.random() + '\":' + Math.random(),\n      ) +\n      '}',\n  },\n  { len: true, gen: (r) => '[' + '[1],'.repeat(r) + ']' },\n  {\n    len: false,\n    gen: (r) => ('{' + Math.random() + ':').repeat(r) + 'a' + '}'.repeat(r),\n  },\n  { len: false, gen: (r) => '[1,'.repeat(r) + 'a' + ']'.repeat(r) },\n]\n\nif (require.main === module) {\n  run(true, parseInt(process.argv[2]), process.argv[3])\n}\n\nfunction run(print, size, step) {\n  let r = size || 54321\n\n  if (step) {\n    step = parseInt(step) || 2\n    for (let sI = 0; sI < strs.length; sI++) {\n      let d = [\n        [\n          'S',\n          'R',\n          'D',\n          'Brss',\n          'BhpT',\n          'BhpU',\n          'Bext',\n          'Arss',\n          'AhpT',\n          'AhpU',\n          'Aext',\n        ],\n      ]\n      for (let rI = 10; rI < size; rI += step) {\n        let r = ~~rI\n        let res = long(false, strs[sI].gen(r), r, strs[sI].len)\n        d.push([sI, r, res.dur, ...res.ms, res.me])\n      }\n      Fs.writeFileSync(\n        './long-' + sI + '.csv',\n        d.map((x) => x.join('\\t')).join('\\n'),\n      )\n    }\n  } else {\n    strs.map((s) => long(print, s.gen(r), r, s.len))\n  }\n}\n\nfunction long(print, str, count, lencheck) {\n  global.gc(true)\n  print && console.log('>> ' + str.substring(0, 76))\n  let ms = Object.values(process.memoryUsage())\n  print && console.log(ms.join('\\t'))\n  let start = Date.now()\n  let d0 = Jsonic(str)\n  let dur = Date.now() - start\n  let me = Object.values(process.memoryUsage())\n  print && console.log(me.join('\\t'))\n  print && console.log(dur, Object.keys(d0).length)\n  if (lencheck && Object.keys(d0).length !== count) {\n    throw new Error(\n      str.substring(0, 76) + ' ' + count + '!==' + Object.keys(d0).length,\n    )\n  }\n  return { dur, ms, me }\n}\n"
  },
  {
    "path": "test/module.mjs",
    "content": "import JsonicDirect from '../jsonic.js'\nimport { Jsonic } from '../jsonic.js'\n\nconsole.log('JsonicDirect', JsonicDirect('a:1'))\nconsole.log('Jsonic', Jsonic('a:1'))\n\n\n\n"
  },
  {
    "path": "test/multifile-remove/again.jsonic",
    "content": "foo:$1+1\nred_name: $.red.name\nitem0: $.red\nitem0: {extra:0}\nitem1: $.red\nitem1: {extra:1}"
  },
  {
    "path": "test/multifile-remove/blue01.js",
    "content": "module.exports = {\n  color: 'BLUE'\n}\n"
  },
  {
    "path": "test/multifile-remove/func.js",
    "content": "module.exports = ()=>'FUNC'\n"
  },
  {
    "path": "test/multifile-remove/green01.json",
    "content": "{\n  \"name\":\"GREEN\"\n}\n"
  },
  {
    "path": "test/multifile-remove/main01.jsonic",
    "content": "dynamic: $1+1\nred:     {name:RED}\nredder:  { red: $.red }\ngreen:   @green01.json\nblue:    @blue01.js\ncyan:    @cyan.csv\ntree:    @\"trunk/branch.jsonic\"\nagain:   @again.jsonic\nfunc:    @func.js\n"
  },
  {
    "path": "test/multifile-remove/trunk/branch.jsonic",
    "content": "stem0: leaf0\nstem1: @\"twig/leaf1.jsonic\""
  },
  {
    "path": "test/multifile-remove/trunk/twig/leaf1.jsonic",
    "content": "caterpillar: tummy: yummy!\n"
  },
  {
    "path": "test/nlookahead.test.js",
    "content": "/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Tests for N-token lookahead in alt.s (no fixed 2-token cap).\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\n\nfunction make_norules(opts) {\n  let j = Jsonic.make(opts)\n  let rns = j.rule()\n  Object.keys(rns).map((rn) => j.rule(rn, null))\n  return j\n}\n\n\ndescribe('nlookahead', () => {\n\n  it('three-token-lookahead-matches', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb, Tc] }])\n        .ac((r) => (r.node = r.o[0].src + r.o[1].src + r.o[2].src)),\n    )\n\n    assert.deepEqual(j('abc'), 'abc')\n    assert.throws(() => j('abd'), /unexpected.*d/)\n    assert.throws(() => j('axc'), /unexpected.*x/)\n  })\n\n\n  it('five-token-lookahead-no-cap', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: {\n        token: { Ta: 'a', Tb: 'b', Tc: 'c', Td: 'd', Te: 'e' },\n      },\n    })\n    let { Ta, Tb, Tc, Td, Te } = j.token\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb, Tc, Td, Te] }])\n        .ac((r) => {\n          r.node = r.o.slice(0, r.oN).map((t) => t.src).join('')\n        }),\n    )\n\n    assert.deepEqual(j('abcde'), 'abcde')\n    // Mismatch at position 4 throws \"unexpected\" (error site is the\n    // first token of the failed alt, per parser convention).\n    assert.throws(() => j('abcdf'), /unexpected/)\n  })\n\n\n  it('first-match-wins-across-lookahead-lengths', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: {\n        token: { Ta: 'a', Tb: 'b', Tc: 'c', Td: 'd' },\n      },\n    })\n    let { Ta, Tb, Tc, Td } = j.token\n\n    // 3-token alt first, falls back to 2-token, then 1-token. Each\n    // alt consumes all the tokens it matched, so close state sees\n    // whatever remains. Close on #ZZ enforces no trailing content.\n    j.rule('top', (rs) =>\n      rs\n        .open([\n          { s: [Ta, Tb, Tc], a: (r) => (r.node = 'abc') },\n          { s: [Ta, Tb, Td], a: (r) => (r.node = 'abd') },\n          { s: [Ta, Tb], a: (r) => (r.node = 'ab') },\n          { s: [Ta], a: (r) => (r.node = 'a') },\n        ])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    assert.deepEqual(j('abc'), 'abc')\n    assert.deepEqual(j('abd'), 'abd')\n    assert.deepEqual(j('ab'), 'ab')\n    assert.deepEqual(j('a'), 'a')\n    // 'abe': 3-token alts mismatch at position 2, 2-token alt matches\n    // 'ab', leaving 'e' — the close `#ZZ` fails on 'e'.\n    assert.throws(() => j('abe'), /unexpected/)\n  })\n\n\n  it('legacy-o0-o1-accessors-still-work', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb, Tc] }])\n        .ac((r) => {\n          // All three legacy surfaces must still work.\n          r.node = {\n            o0: r.o0.src,\n            o1: r.o1.src,\n            o2: r.o[2].src,\n            os: r.os,\n            oN: r.oN,\n            same: r.o[0] === r.o0 && r.o[1] === r.o1,\n          }\n        }),\n    )\n\n    assert.deepEqual(j('abc'), {\n      o0: 'a', o1: 'b', o2: 'c',\n      os: 3, oN: 3, same: true,\n    })\n  })\n\n\n  it('ctx-t-array-indexed-access', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    let seenLegacy = null\n    let seenArray = null\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb, Tc], c: (r, ctx) => {\n          // During condition check, t[0..2] are populated after match.\n          seenLegacy = { t0: ctx.t0.src, t1: ctx.t1.src }\n          seenArray = { t0: ctx.t[0].src, t1: ctx.t[1].src, t2: ctx.t[2].src }\n          return true\n        } }])\n        .ac((r) => (r.node = 'ok')),\n    )\n\n    j('abc')\n    assert.deepEqual(seenLegacy, { t0: 'a', t1: 'b' })\n    assert.deepEqual(seenArray, { t0: 'a', t1: 'b', t2: 'c' })\n  })\n\n\n  it('backtrack-n3-leaves-tokens-in-buffer', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    // top matches [a,b,c] and backtracks all 3 (b: 3) before pushing\n    // 'inner'. inner's parse_alts must still see a,b,c in ctx.t, and\n    // consume them in order across successive rule iterations.\n    let innerSaw = []\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb, Tc], b: 3, p: 'inner' }])\n        .close([{ s: '#ZZ' }])\n        .ac((r) => (r.node = innerSaw.join(','))),\n    )\n\n    j.rule('inner', (rs) =>\n      rs\n        .open([\n          { s: [Ta], a: (r) => innerSaw.push(r.o[0].src), r: 'innerB' },\n        ])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j.rule('innerB', (rs) =>\n      rs\n        .open([\n          { s: [Tb], a: (r) => innerSaw.push(r.o[0].src), r: 'innerC' },\n        ])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j.rule('innerC', (rs) =>\n      rs\n        .open([{ s: [Tc], a: (r) => innerSaw.push(r.o[0].src) }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j('abc')\n    assert.deepEqual(innerSaw, ['a', 'b', 'c'])\n  })\n\n\n  it('error-site-is-first-lookahead-slot', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    j.rule('top', (rs) =>\n      rs.open([{ s: [Ta, Tb, Tc] }]).ac((r) => (r.node = 'ok')),\n    )\n\n    // Mismatch at position 2: 'a','b','x' - error must reference the\n    // original first token 'a' per parser convention (error site is\n    // ctx.t[0], not the first mismatching token).\n    try {\n      j('abx')\n      assert.fail('expected throw')\n    } catch (err) {\n      assert.match(err.message, /unexpected/)\n    }\n  })\n\n\n  it('null-middle-slot-is-wildcard-not-terminator', () => {\n    // Regression: previously a null/empty slot caused the match loop\n    // to break, silently dropping checks at later required positions.\n    // A null S[i] must act as \"accept any token at position i\" while\n    // still requiring S[i+1..] to match.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tc: 'c' } },\n    })\n    let { Ta, Tc } = j.token\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, null, Tc] }])\n        .ac((r) => {\n          r.node = r.o.slice(0, r.oN).map((t) => t.src).join('')\n        }),\n    )\n\n    // Middle position is unconstrained, outer positions must match.\n    assert.deepEqual(j('axc'), 'axc')\n    assert.deepEqual(j('a!c'), 'a!c')\n    // Third token still required to be 'c'.\n    assert.throws(() => j('axd'), /unexpected/)\n    // First token still required to be 'a'.\n    assert.throws(() => j('bxc'), /unexpected/)\n  })\n\n\n  it('four-token-with-alternatives-at-positions', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: {\n        token: { Ta: 'a', Tb: 'b', Tc: 'c', Td: 'd', Tx: 'x', Ty: 'y' },\n      },\n    })\n    let { Ta, Tb, Tc, Td, Tx, Ty } = j.token\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, [Tb, Tx], [Tc, Ty], Td] }])\n        .ac((r) => {\n          r.node = r.o.slice(0, r.oN).map((t) => t.src).join('')\n        }),\n    )\n\n    assert.deepEqual(j('abcd'), 'abcd')\n    assert.deepEqual(j('axcd'), 'axcd')\n    assert.deepEqual(j('abyd'), 'abyd')\n    assert.deepEqual(j('axyd'), 'axyd')\n    assert.throws(() => j('abbb'), /unexpected/)\n  })\n\n})\n"
  },
  {
    "path": "test/p0.js",
    "content": "module.exports = function p0(jsonic, popts) {\n  jsonic.options({\n    value: {\n      def: {\n        [popts.s || 'X']: { val: popts.x },\n      },\n    },\n  })\n}\n"
  },
  {
    "path": "test/p1.js",
    "content": "module.exports = {\n  default: function p1(jsonic, popts) {\n    jsonic.options({\n      value: {\n        def: {\n          [popts.s || 'Y']: { val: popts.y },\n        },\n      },\n    })\n  },\n}\n"
  },
  {
    "path": "test/p2.js",
    "content": "module.exports = {\n  p2: (jsonic, popts) => {\n    jsonic.options({\n      value: {\n        def: {\n          [popts.s || 'Z']: { val: popts.z },\n        },\n      },\n    })\n  },\n}\n"
  },
  {
    "path": "test/pa-qa.js",
    "content": "module.exports = function PaQa(jsonic, popts) {\n  jsonic.options({\n    value: {\n      def: {\n        [popts.s || 'Q']: { val: popts.q },\n      },\n    },\n  })\n}\n"
  },
  {
    "path": "test/perf.js",
    "content": "const Util = require('util')\n\nconst { Jsonic, Lexer } = require('..')\nconst config = Jsonic.internal().config\nconst opts = Jsonic.make().options\n\nconst ZZ = Jsonic.make().token.ZZ\n\nlet inputs = [\n  {\n    src: `qs:\"a\\\\\"a'a\",as:'a\"a\\\\'a',hs: 'a \\\\tb' `,\n    out: { qs: `a\"a'a`, as: `a\"a'a`, hs: 'a \\tb' },\n  },\n\n  {\n    src: 'int:100,dec:2.34,exp:-1e6',\n    out: { int: 100, dec: 2.34, exp: -1000000 },\n  },\n\n  {\n    src: 't:true,f:false,n:null',\n    out: { t: true, f: false, n: null },\n  },\n\n  {\n    src: 'a:1,b:{c:2,d:{e:3,f:{g:4,h:{i:5}}}}',\n    out: { a: 1, b: { c: 2, d: { e: 3, f: { g: 4, h: { i: 5 } } } } },\n  },\n\n  {\n    src: '[0,[1],[2,3],[[[[[4]]]]]]',\n    out: [0, [1], [2, 3], [[[[[4]]]]]],\n  },\n]\n\nrun_parse()\n\nfunction run_parse() {\n  console.log('\\n')\n  console.log('parse/sec')\n  console.log('==================')\n\n  inputs.forEach((input) => {\n    let [count, json_count, out] = count_parse(input.src)\n    console.log(\n      input.src.replace(/\\t/, '\\\\t').padEnd(48, ' '),\n      ' >> ',\n      ('' + count).padStart(8, ' '),\n      '  JSON: ',\n      ('' + json_count).padStart(8, ' '),\n      ('' + Number(json_count / count).toFixed(1)).padEnd(6),\n      JSON.stringify(out) === JSON.stringify(input.out)\n        ? true\n        : 'FAIL: ' + JSON.stringify(out) + ' != ' + JSON.stringify(input.out),\n    )\n  })\n}\n\nfunction count_parse(input) {\n  let start = Date.now()\n\n  let json = JSON.stringify(Jsonic(input))\n\n  // warm up\n  while (Date.now() - start < 2000) {\n    Jsonic(input)\n    JSON.parse(json)\n  }\n\n  let count = 0\n  start = Date.now()\n\n  while (Date.now() - start < 1000) {\n    Jsonic(input)\n    count++\n  }\n\n  let json_count = 0\n  start = Date.now()\n\n  while (Date.now() - start < 1000) {\n    JSON.parse(json)\n    json_count++\n  }\n\n  return [count, json_count, Jsonic(input)]\n}\n"
  },
  {
    "path": "test/plugin-default.js",
    "content": "module.exports = {\n  default: () => {},\n}\n"
  },
  {
    "path": "test/plugin-name.js",
    "content": "module.exports = {\n  'plugin-name': () => {},\n}\n"
  },
  {
    "path": "test/plugin.test.js",
    "content": "/* Copyright (c) 2013-2023 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, Lexer, makeParser, JsonicError, make } = require('..')\n\ndescribe('plugin', function () {\n  it('parent-safe', () => {\n    let c0 = Jsonic.make({\n      a: 1,\n      fixed: { token: { '#B': 'b' } },\n    })\n\n    c0.foo = () => 'FOO'\n    c0.bar = 11\n\n    // Jsonic unaffected\n    assert.deepEqual(Jsonic('b'), 'b')\n    assert.equal(Jsonic.foo, undefined)\n    assert.equal(Jsonic.bar, undefined)\n\n    assert.deepEqual(c0.options.a, 1)\n    assert.deepEqual(c0.token['#B'], 18)\n    assert.deepEqual(c0.fixed['b'], 18)\n    assert.deepEqual(c0.token[18], '#B')\n    assert.deepEqual(c0.fixed[18], 'b')\n    assert.deepEqual(c0.token('#B'), 18)\n    assert.deepEqual(c0.fixed('b'), 18)\n    assert.deepEqual(c0.token(18), '#B')\n    assert.deepEqual(c0.fixed(18), 'b')\n    assert.deepEqual(c0.foo(), 'FOO')\n    assert.deepEqual(c0.bar, 11)\n\n    assert.throws(() => c0('b'), /unexpected/)\n\n    // console.log('c0 int A', c0.internal().mark, c0.internal().config.fixed)\n\n    let c1 = c0.make({\n      c: 2,\n      fixed: { token: { '#D': 'd' } },\n    })\n\n    assert.deepEqual(c1.options.a, 1)\n    assert.deepEqual(c1.token['#B'], 18)\n    assert.deepEqual(c1.fixed['b'], 18)\n    assert.deepEqual(c1.token[18], '#B')\n    assert.deepEqual(c1.fixed[18], 'b')\n    assert.deepEqual(c1.token('#B'), 18)\n    assert.deepEqual(c1.fixed('b'), 18)\n    assert.deepEqual(c1.token(18), '#B')\n    assert.deepEqual(c1.fixed(18), 'b')\n    assert.deepEqual(c1.foo(), 'FOO')\n    assert.deepEqual(c1.bar, 11)\n\n    assert.deepEqual(c1.options.c, 2)\n    assert.deepEqual(c1.token['#D'], 19)\n    assert.deepEqual(c1.fixed['d'], 19)\n    assert.deepEqual(c1.token[19], '#D')\n    assert.deepEqual(c1.fixed[19], 'd')\n    assert.deepEqual(c1.token('#D'), 19)\n    assert.deepEqual(c1.fixed('d'), 19)\n    assert.deepEqual(c1.token(19), '#D')\n    assert.deepEqual(c1.fixed(19), 'd')\n    assert.deepEqual(c1.foo(), 'FOO')\n    assert.deepEqual(c1.bar, 11)\n\n    assert.throws(() => c1('b'), /unexpected/)\n    assert.throws(() => c1('d'), /unexpected/)\n\n    // console.log('c1 int A', c1.internal().mark, c1.internal().config.fixed)\n    // console.log('c0 int B', c0.internal().mark, c0.internal().config.fixed)\n\n    // c0 unaffected by c1\n\n    assert.deepEqual(c0.options.a, 1)\n    assert.deepEqual(c0.token['#B'], 18)\n    assert.deepEqual(c0.fixed['b'], 18)\n    assert.deepEqual(c0.token[18], '#B')\n    assert.deepEqual(c0.fixed[18], 'b')\n    assert.deepEqual(c0.token('#B'), 18)\n    assert.deepEqual(c0.fixed('b'), 18)\n    assert.deepEqual(c0.token(18), '#B')\n    assert.deepEqual(c0.fixed(18), 'b')\n    assert.deepEqual(c0.foo(), 'FOO')\n    assert.deepEqual(c0.bar, 11)\n\n    assert.throws(() => c0('b'), /unexpected/)\n\n    assert.equal(c0.options.c, undefined)\n    assert.equal(c0.token['#D'], undefined)\n    assert.equal(c0.fixed['d'], undefined)\n    assert.equal(c0.token[19], undefined)\n    assert.equal(c0.fixed[19], undefined)\n\n    assert.equal(c0.fixed('d'), undefined)\n    assert.equal(c0.token(19), undefined)\n    assert.equal(c0.fixed(19), undefined)\n    // NOTE: c0.token('#D') will create a new token\n  })\n\n  it('clone-parser', () => {\n    let config0 = {\n      config: true,\n      mark: 0,\n      tI: 1,\n      t: {},\n      rule: { include: [], exclude: [] },\n    }\n    let opts0 = { opts: true, mark: 0 }\n    let p0 = makeParser(opts0, config0)\n\n    let config1 = {\n      config: true,\n      mark: 1,\n      tI: 1,\n      t: {},\n      rule: { include: [], exclude: [] },\n    }\n    let opts1 = { opts: true, mark: 1 }\n    let p1 = p0.clone(opts1, config1)\n\n    assert.deepEqual(p0 === p1, false)\n    assert.deepEqual(p0.rsm === p1.rsm, false)\n  })\n\n  it('naked-make', () => {\n    assert.throws(() => Jsonic.use(make_token_plugin('A', 'aaa')))\n\n    // use make to avoid polluting Jsonic\n    const j = make()\n    j.use(make_token_plugin('A', 'aaa'))\n    assert.deepEqual(j('x:A,y:B,z:C', { xlog: -1 }), { x: 'aaa', y: 'B', z: 'C' })\n\n    const a1 = j.make({ a: 1 })\n    assert.deepEqual(a1.options.a, 1)\n    assert.equal(j.options.a, undefined)\n    assert.deepEqual(j.internal().parser === a1.internal().parser, false)\n    assert.deepEqual(j.token.OB === a1.token.OB, true)\n    assert.deepEqual(a1('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n    assert.deepEqual(j('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n\n    const a2 = j.make({ a: 2 })\n    assert.deepEqual(a2.options.a, 2)\n    assert.deepEqual(a1.options.a, 1)\n    assert.equal(j.options.a, undefined)\n    assert.deepEqual(j.internal().parser === a2.internal().parser, false)\n    assert.deepEqual(a2.internal().parser === a1.internal().parser, false)\n    assert.deepEqual(j.token.OB === a2.token.OB, true)\n    assert.deepEqual(a2.token.OB === a1.token.OB, true)\n    assert.deepEqual(a2('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n    assert.deepEqual(a1('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n    assert.deepEqual(j('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n\n    a2.use(make_token_plugin('B', 'bbb'))\n    assert.deepEqual(a2('x:A,y:B,z:C'), { x: 'aaa', y: 'bbb', z: 'C' })\n    assert.deepEqual(a1('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n    assert.deepEqual(j('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n\n    const a22 = a2.make({ a: 22 })\n    assert.deepEqual(a22.options.a, 22)\n    assert.deepEqual(a2.options.a, 2)\n    assert.deepEqual(a1.options.a, 1)\n    assert.equal(j.options.a, undefined)\n    assert.deepEqual(j.internal().parser === a22.internal().parser, false)\n    assert.deepEqual(j.internal().parser === a2.internal().parser, false)\n    assert.deepEqual(a22.internal().parser === a1.internal().parser, false)\n    assert.deepEqual(a2.internal().parser === a1.internal().parser, false)\n    assert.deepEqual(a22.internal().parser === a2.internal().parser, false)\n    assert.deepEqual(j.token.OB === a22.token.OB, true)\n    assert.deepEqual(a22.token.OB === a1.token.OB, true)\n    assert.deepEqual(a2.token.OB === a1.token.OB, true)\n    assert.deepEqual(a22('x:A,y:B,z:C'), { x: 'aaa', y: 'bbb', z: 'C' })\n    assert.deepEqual(a2('x:A,y:B,z:C'), { x: 'aaa', y: 'bbb', z: 'C' })\n    assert.deepEqual(a1('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n    assert.deepEqual(j('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n\n    a22.use(make_token_plugin('C', 'ccc'))\n    assert.deepEqual(a22('x:A,y:B,z:C'), { x: 'aaa', y: 'bbb', z: 'ccc' })\n    assert.deepEqual(a2('x:A,y:B,z:C'), { x: 'aaa', y: 'bbb', z: 'C' })\n    assert.deepEqual(a1('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n    assert.deepEqual(j('x:A,y:B,z:C'), { x: 'aaa', y: 'B', z: 'C' })\n  })\n\n  it('plugin-opts', () => {\n    // use make to avoid polluting Jsonic\n    let x = null\n    const j = make()\n    j.use(\n      function foo(jsonic) {\n        x = jsonic.options.plugin.foo.x\n      },\n      { x: 1 },\n    )\n    assert.deepEqual(x, 1)\n  })\n\n  it('wrap-jsonic', () => {\n    const j = make()\n    let jp = j.use(function foo(jsonic) {\n      return new Proxy(jsonic, {})\n    })\n    assert.deepEqual(jp('a:1'), { a: 1 })\n  })\n\n  it('config-modifiers', () => {\n    const j = make()\n    j.use(function foo(jsonic) {\n      jsonic.options({\n        config: {\n          modify: {\n            foo: (config) => (config.fixed.token['#QQ'] = 99),\n          },\n        },\n      })\n    })\n    assert.deepEqual(j.internal().config.fixed.token['#QQ'], 99)\n  })\n\n  it('decorate', () => {\n    const j = make()\n\n    let jp0 = j.use(function foo(jsonic) {\n      jsonic.foo = () => 'FOO'\n    })\n    assert.deepEqual(jp0.foo(), 'FOO')\n\n    let jp1 = jp0.use(function bar(jsonic) {\n      jsonic.bar = () => 'BAR'\n    })\n    assert.deepEqual(jp1.bar(), 'BAR')\n    assert.deepEqual(jp1.foo(), 'FOO')\n    assert.deepEqual(jp0.foo(), 'FOO')\n  })\n\n  it('context-api', () => {\n    let j0 = Jsonic.make().use(function (jsonic) {\n      jsonic.rule('val', (rs) => {\n        rs.ac((r, ctx) => {\n          assert.deepEqual(ctx.uI > 0, true)\n\n          const inst = ctx.inst()\n          assert.deepEqual(inst, j0)\n          assert.deepEqual(inst, jsonic)\n          assert.deepEqual(inst.id, j0.id)\n          assert.deepEqual(inst.id, jsonic.id)\n          assert.deepEqual(inst !== Jsonic, true)\n          assert.deepEqual(inst.id !== Jsonic.id, true)\n        })\n      })\n    })\n\n    assert.deepEqual(j0('a:1'), { a: 1 })\n  })\n\n  it('custom-parser-error', () => {\n    let j = Jsonic.make().use(function foo(jsonic) {\n      jsonic.options({\n        parser: {\n          start: function (src, jsonic, meta) {\n            if ('e:0' === src) {\n              throw new Error('bad-parser:e:0')\n            } else if ('e:1' === src) {\n              let e1 = new SyntaxError('Unexpected token e:1 at position 0')\n              e1.lineNumber = 1\n              e1.columnNumber = 1\n              throw e1\n            } else if ('e:2' === src) {\n              let e2 = new SyntaxError('bad-parser:e:2')\n              e2.code = 'e2'\n              e2.token = {}\n              e2.details = {}\n              e2.ctx = {\n                src: () => '',\n                cfg: {\n                  t: {},\n                  error: { e2: 'e:2' },\n                  errmsg: { name: 'jsonic', suffix: true },\n                  hint: { e2: 'e:2' },\n                  color: {active:false} },\n                plgn: () => [],\n                opts: {},\n              }\n              throw e2\n            }\n          },\n        },\n      })\n    })\n\n    // j('e:2')\n\n    assert.throws(() => j('e:0'), /e:0/s)\n    assert.throws(() => j('e:1', { log: () => null }), /e:1/s)\n    assert.throws(() => j('e:2'), /e:2/s)\n  })\n\n\n  it('plugin-errmsg', () => {\n    const j = make().use(\n      function Foo(jsonic) {\n        jsonic.options({\n          errmsg: {\n            name: 'bar',\n            suffix: false,\n          },\n          hint: {\n            unexpected: 'FOO'\n          }\n        })\n      }\n    )\n\n    try {\n      j('x::1')\n      assert.deepEqual(true, false)\n    }\n    catch(e) {\n      assert.ok(e.message.includes('bar/unexpected'))\n      assert.ok(e.message.includes('FOO'))\n      assert.ok(!e.message.includes('--internal'))\n    }\n\n    try {\n      j('x:\"s')\n      assert.deepEqual(true, false)\n    }\n    catch(e) {\n      assert.ok(e.message.includes('no end quote'))\n      assert.ok(!e.message.includes('--internal'))\n    }\n  })\n\n\n})\n\nfunction make_token_plugin(char, val) {\n  let tn = '#T<' + char + '>'\n  let plugin = function (jsonic) {\n    jsonic.options({\n      fixed: {\n        token: {\n          [tn]: char,\n        },\n      },\n    })\n\n    let TT = jsonic.token(tn)\n\n    jsonic.rule('val', (rs) => {\n      rs.open({ s: [TT], g: 'cv' + val.toLowerCase() }).bc(false, (rule) => {\n        if (rule.o0 && TT === rule.o0.tin) {\n          rule.o0.val = val\n        }\n      })\n      // return rs\n    })\n  }\n\n  Object.defineProperty(plugin, 'name', { value: 'plugin_' + char })\n  return plugin\n}\n"
  },
  {
    "path": "test/plugins-parity.sh",
    "content": "#!/usr/bin/env bash\n# plugins-parity.sh\n#\n# Builds this jsonic checkout (TS + Go), then for every sibling plugin\n# project in ~/Projects/jsonicjs with a Makefile (excluding jsonic itself),\n# temporarily links the plugin against this local jsonic and runs\n# `make test` so both the TS and Go sides exercise the current fix.\n#\n# TS side: node_modules/jsonic is replaced with a symlink to $JSONIC_DIR.\n# Go side: a `replace github.com/jsonicjs/jsonic/go => $JSONIC_DIR/go`\n# directive is added via `go mod edit` to each plugin's go.mod.\n#\n# Both overrides are reverted after each plugin runs, whether the test\n# passed, failed, or was interrupted. Plugins without an installed\n# node_modules get `npm install` run on demand.\n#\n# Usage:\n#   bash test/plugins-parity.sh              # run all plugins\n#   bash test/plugins-parity.sh expr path    # run named plugins only\n#   bash test/plugins-parity.sh --list       # list discovered plugins\n#\n# Exit status: 0 if every plugin's `make test` succeeded, 1 otherwise.\n\nset -u\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nJSONIC_DIR=\"$(cd \"$SCRIPT_DIR/..\" && pwd)\"\nJSONICJS_DIR=\"$(cd \"$JSONIC_DIR/..\" && pwd)\"\n\nJSONIC_MOD=\"github.com/jsonicjs/jsonic/go\"\n\nlog()  { printf '\\033[1;34m[parity]\\033[0m %s\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m[parity]\\033[0m %s\\n' \"$*\"; }\nfail() { printf '\\033[1;31m[parity]\\033[0m %s\\n' \"$*\"; }\n\n# Discover every sibling project with a Makefile (excluding jsonic itself).\ndiscover_plugins() {\n  local d name\n  for d in \"$JSONICJS_DIR\"/*/; do\n    name=\"$(basename \"$d\")\"\n    [ \"$name\" = \"jsonic\" ] && continue\n    [ -f \"$d/Makefile\" ] || continue\n    printf '%s\\n' \"$name\"\n  done\n}\n\nensure_jsonic_built() {\n  log \"building jsonic (TS)\"\n  ( cd \"$JSONIC_DIR\" && npm run build ) >/dev/null || {\n    fail \"jsonic TS build failed\"\n    return 1\n  }\n  log \"building jsonic (Go)\"\n  ( cd \"$JSONIC_DIR/go\" && go build ./... ) || {\n    fail \"jsonic Go build failed\"\n    return 1\n  }\n}\n\n# Replace node_modules/jsonic with a symlink to our local checkout.\n# Saves a non-link original under .jsonic.parity-bak so it can be restored.\nlink_ts() {\n  local dir=$1\n  local nm=\"$dir/node_modules\"\n  if [ ! -d \"$nm\" ]; then\n    log \"  npm install (no node_modules)\"\n    ( cd \"$dir\" && npm install --no-audit --no-fund --silent ) || {\n      warn \"  npm install failed — skipping ts link\"\n      return 1\n    }\n  fi\n  if [ -e \"$nm/jsonic\" ] && [ ! -L \"$nm/jsonic\" ]; then\n    mv \"$nm/jsonic\" \"$nm/.jsonic.parity-bak\"\n  else\n    rm -rf \"$nm/jsonic\"\n  fi\n  ln -s \"$JSONIC_DIR\" \"$nm/jsonic\"\n}\n\nunlink_ts() {\n  local dir=$1\n  local nm=\"$dir/node_modules\"\n  [ -d \"$nm\" ] || return 0\n  if [ -L \"$nm/jsonic\" ]; then\n    rm -f \"$nm/jsonic\"\n  fi\n  if [ -e \"$nm/.jsonic.parity-bak\" ]; then\n    mv \"$nm/.jsonic.parity-bak\" \"$nm/jsonic\"\n  fi\n}\n\n# Add a `replace` for the local jsonic Go module in the plugin's go.mod.\nlink_go() {\n  local dir=$1\n  [ -f \"$dir/go/go.mod\" ] || return 0\n  ( cd \"$dir/go\" && go mod edit \"-replace=$JSONIC_MOD=$JSONIC_DIR/go\" )\n}\n\nunlink_go() {\n  local dir=$1\n  [ -f \"$dir/go/go.mod\" ] || return 0\n  ( cd \"$dir/go\" && go mod edit \"-dropreplace=$JSONIC_MOD\" ) 2>/dev/null || true\n}\n\n# Called on EXIT/INT/TERM. Restores every plugin we've touched.\nTOUCHED=()\ncleanup() {\n  local p\n  for p in \"${TOUCHED[@]+\"${TOUCHED[@]}\"}\"; do\n    unlink_ts \"$JSONICJS_DIR/$p\"\n    unlink_go \"$JSONICJS_DIR/$p\"\n  done\n}\ntrap cleanup EXIT INT TERM\n\n# Run `make test` for one plugin under the linked local jsonic.\n# Echoes \" pass\" or \" fail <rc>\" on the accumulator FD (3) so the caller\n# can collect results without parsing stdout.\nrun_plugin() {\n  local plugin=$1\n  local dir=\"$JSONICJS_DIR/$plugin\"\n  log \"=== $plugin ===\"\n  TOUCHED+=(\"$plugin\")\n\n  link_ts \"$dir\" || { printf '%s fail link-ts\\n' \"$plugin\" >&3; return 1; }\n  link_go \"$dir\"\n\n  local rc=0\n  ( cd \"$dir\" && make test ) || rc=$?\n\n  unlink_ts \"$dir\"\n  unlink_go \"$dir\"\n\n  # Drop from TOUCHED once cleanly unlinked so cleanup doesn't double-unlink.\n  local i new=()\n  for i in \"${TOUCHED[@]}\"; do\n    [ \"$i\" = \"$plugin\" ] || new+=(\"$i\")\n  done\n  TOUCHED=(\"${new[@]+\"${new[@]}\"}\")\n\n  if [ \"$rc\" -eq 0 ]; then\n    printf '%s pass\\n' \"$plugin\" >&3\n  else\n    printf '%s fail %d\\n' \"$plugin\" \"$rc\" >&3\n  fi\n  return \"$rc\"\n}\n\nmain() {\n  if [ \"${1-}\" = \"--list\" ]; then\n    discover_plugins\n    exit 0\n  fi\n\n  local plugins=()\n  if [ \"$#\" -gt 0 ]; then\n    plugins=(\"$@\")\n  else\n    while IFS= read -r p; do plugins+=(\"$p\"); done < <(discover_plugins)\n  fi\n\n  if [ \"${#plugins[@]}\" -eq 0 ]; then\n    fail \"no plugins discovered in $JSONICJS_DIR\"\n    exit 1\n  fi\n\n  ensure_jsonic_built || exit 1\n\n  local results_file\n  results_file=\"$(mktemp)\"\n  exec 3>\"$results_file\"\n\n  local overall=0\n  local p\n  for p in \"${plugins[@]}\"; do\n    run_plugin \"$p\" || overall=1\n  done\n\n  exec 3>&-\n\n  echo\n  log \"summary\"\n  printf '  %-14s  %s\\n' PLUGIN RESULT\n  printf '  %-14s  %s\\n' -------------- ------\n  while read -r line; do\n    set -- $line\n    printf '  %-14s  %s\\n' \"$1\" \"${2}${3:+ ($3)}\"\n  done <\"$results_file\"\n  rm -f \"$results_file\"\n\n  exit \"$overall\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "test/prep.ts",
    "content": "// temporary file to help prepare test rewrite in ts \n\n\nexport function prep() { return null }\n"
  },
  {
    "path": "test/probe.test.js",
    "content": "/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Tests for the probe + phase-retry pattern the BNF converter uses to\n// resolve `[X D] Y` optional-prefix ambiguities — the canonical shape\n// where X and Y share a character vocabulary and D is a terminal\n// disambiguator. The rewriter emits a dispatcher that:\n//   1. marks the token position and runs a failure-proof probe rule\n//      that greedily consumes the joint X ∪ Y vocab;\n//   2. peeks ctx.t[0] on probe return;\n//   3. rewinds to the mark and retries into either the `X D Y` branch\n//      (if D was seen) or the `Y` branch (if not).\n//\n// The pattern uses only standard jsonic primitives: r:/p:/c:/k: and\n// ctx.mark/rewind/t — no new parser machinery is needed.\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\n\ndescribe('probe-dispatch', () => {\n\n  describe('synthetic [X D] Y pattern', () => {\n\n    // Canonical ambiguous grammar:\n    //   top = [ X \"@\" ] Y\n    //   X   = *( LETTER )      (same vocab as Y)\n    //   Y   = *( LETTER )\n    // After probe+phase-retry, inputs like \"abc\" (no @) take the Y\n    // branch and inputs like \"ab@cd\" take the X \"@\" Y branch.\n    const GRAMMAR = `\ntop = [ X \"@\" ] Y\nX   = *( ALPHA )\nY   = *( ALPHA )\n`\n\n    const makeParser = () => {\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      j.bnf(GRAMMAR)\n      return j\n    }\n\n    it('accepts the X-absent (Y-only) shape', () => {\n      const j = makeParser()\n      assert.doesNotThrow(() => j('abc'))\n    })\n\n\n    it('accepts the X-present (X @ Y) shape', () => {\n      const j = makeParser()\n      assert.doesNotThrow(() => j('ab@cd'))\n    })\n\n\n    it('accepts the empty X-present edge case', () => {\n      // X is nullable (zero letters), so \"@cd\" is still a valid\n      // X \"@\" Y parse — the probe consumes zero tokens before seeing\n      // @, peeks @, commits to the \"with\" branch.\n      const j = makeParser()\n      assert.doesNotThrow(() => j('@cd'))\n    })\n\n\n    it('accepts empty input (both X and Y nullable)', () => {\n      const j = makeParser()\n      assert.doesNotThrow(() => j(' '))\n    })\n\n  })\n\n\n  describe('emitter shape', () => {\n\n    it('synthesises probe/with/no/dispatch helper rules', () => {\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      const spec = j.bnf(`\ntop = [ X \"@\" ] Y\nX   = *( ALPHA )\nY   = *( ALPHA )\n`)\n      // One probe-dispatch group per detected ambiguity, named after\n      // the enclosing rule + alt-offset. The `$with` / `$no` /\n      // `$probe` siblings are the committed branches plus the\n      // failure-proof probe helper.\n      const names = Object.keys(spec.rule)\n      assert.ok(names.some((n) => /top\\$pd\\d+$/.test(n)),\n        'expected a dispatcher rule named like top$pdN')\n      assert.ok(names.some((n) => /top\\$pd\\d+\\$probe$/.test(n)),\n        'expected a probe helper rule')\n      assert.ok(names.some((n) => /top\\$pd\\d+\\$with$/.test(n)),\n        'expected a with-branch rule')\n      assert.ok(names.some((n) => /top\\$pd\\d+\\$no$/.test(n)),\n        'expected a no-branch rule')\n    })\n\n\n    it('no-ambiguity grammars are left alone (no probe rules)', () => {\n      // The optional's body ends with a terminal that can't be in\n      // the tail's vocabulary, so FIRST-set dispatch suffices.\n      const j = Jsonic.make()\n      const spec = j.bnf(`\ntop = [ X \"!\" ] Y\nX   = *( ALPHA )\nY   = *DIGIT\n`)\n      const names = Object.keys(spec.rule)\n      assert.equal(names.filter((n) => /\\$pd\\d+/.test(n)).length, 0,\n        'expected no probe helpers — FIRST(X) ∩ FIRST(Y) is empty')\n    })\n\n  })\n\n\n  describe('disambiguator semantics', () => {\n\n    it('probe vocabulary excludes the disambiguator', () => {\n      // The `@` in [ X \"@\" ] is the disambiguator. If the probe\n      // helper included @ in its vocab, it would eat the @ and the\n      // peek would never see it — breaking the with-branch path.\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      const spec = j.bnf(`\ntop = [ X \"@\" ] Y\nX   = *( ALPHA / \"@\" )\nY   = *( ALPHA )\n`)\n      const probeName = Object.keys(spec.rule).find((n) => /\\$probe$/.test(n))\n      assert.ok(probeName, 'expected a probe helper rule')\n      const tokMap = Object.fromEntries(\n        Object.entries(spec.options.fixed.token || {}))\n      const probeSrcs = spec.rule[probeName].open\n        .filter((a) => a.s)\n        .map((a) => tokMap[a.s[0]])\n      assert.ok(!probeSrcs.includes('@'),\n        `probe vocab must not contain the disambiguator '@', got: ` +\n        JSON.stringify(probeSrcs))\n    })\n\n  })\n\n\n  describe('unhandled LL(k) ambiguities (documented limits)', () => {\n\n    // The probe + phase-retry pattern covers `[X D] Y` shapes where\n    // D is a terminal and FIRST(X) ∩ FIRST(Y) ≠ ∅. It does NOT\n    // cover every LL(k) ambiguity:\n    //\n    //   1. Non-terminal disambiguators — `[ X B ] Y` where B itself\n    //      derives multiple tokens. The current rewriter only peeks\n    //      a single token for the disambiguator; there's no probe\n    //      shape that decides from a multi-token witness.\n    //   2. Two top-level alts that share an arbitrarily-deep prefix\n    //      with no local tie-breaker — e.g., `S = A Z / A Y` where\n    //      A is nullable-or-long. Needs catch-and-rewind at the\n    //      alt-dispatch level (true backtracking), which the\n    //      current emitter doesn't provide.\n    //\n    // These tests are marked `.skip` — they document the capability\n    // boundary rather than assert specific failure modes. If/when\n    // the emitter gains generalised alt-level backtracking they can\n    // be promoted to passing tests.\n\n    it.skip('rule = [ X B ] C, disambiguator is a nonterminal', () => {\n      // Would require a probe shape that peeks a bounded window of\n      // tokens matching B's FIRST (and possibly further) — beyond\n      // the single-token peek the current decide-action uses.\n      const GRAMMAR = `\nrule = [ X B ] C\nX    = *( ALPHA )\nB    = \"-\" \"-\"\nC    = *( ALPHA )\n`\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      j.bnf(GRAMMAR)\n      assert.doesNotThrow(() => j('abc--def'))\n    })\n\n\n    it.skip('shared deep prefix with no terminal disambiguator', () => {\n      // `S = A Z / A Y` — without a disambiguating terminal between\n      // A and the tail, the probe pattern has nothing to peek for.\n      const GRAMMAR = `\nS = A Z / A Y\nA = *( ALPHA )\nZ = \"1\"\nY = \"2\"\n`\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      j.bnf(GRAMMAR)\n      assert.doesNotThrow(() => j('abc2'))\n    })\n\n  })\n\n\n  describe('RFC 3986 authority-style ambiguity', () => {\n\n    // The archetypal real-world case: RFC 3986's authority rule,\n    // where `userinfo` and `reg-name` share a vocabulary and the `@`\n    // separator is the disambiguator.\n    const AUTHORITY = `\nauthority  = [ userinfo \"@\" ] host [ \":\" port ]\nuserinfo   = *( unreserved / \":\" )\nhost       = reg-name\nport       = *DIGIT\nreg-name   = *( unreserved )\nunreserved = ALPHA / \"-\" / \".\"\n`\n\n    const makeParser = () => {\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      j.bnf(AUTHORITY)\n      return j\n    }\n\n    const ACCEPT = [\n      'example.com',\n      'example.com:8080',\n      'user@example.com',\n      'user:pass@example.com',\n      'user:pass@example.com:8080',\n      // Empty userinfo edge case.\n      '@example.com',\n    ]\n\n    for (const input of ACCEPT) {\n      it(`accepts ${JSON.stringify(input)}`, () => {\n        const j = makeParser()\n        assert.doesNotThrow(() => j(input))\n      })\n    }\n\n  })\n\n})\n"
  },
  {
    "path": "test/quick.js",
    "content": "// let { Jsonic, util } = require('..')\n\nlet Jsonic = require('..')\nlet { util } = Jsonic\n\nlet { Debug } = require('../dist/debug')\n\nlet j = Jsonic.make({\n  // rule: { finish: false },\n  // list: { property: false }\n  // match: {\n  //   value: {\n  //     commadigits: {\n  //       match: /^\\d+(,\\d+)+/,\n  //       val: (res)=>20*(+(res[0].replace(/,/g,''))),\n  //     }\n  //   }\n  // },\n  // text: { lex: false }\n}).use(Debug, { trace: true })\n// console.log(j.debug.describe())\n\nconsole.log(\n  j(`\nsrv: {\n  auth:\n    desc: \"Authentication service\"\n  user:\n    desc: \"User profile service\"\n  dash:\n    desc: \"Dashboard service\"\n}\n`),\n)\n\n// console.log(\n//   j(`\n// a:1\n// b:2\n// `)\n// )\n\n// console.log(j('{a:1,b:2}'))\n\n// console.log(j('{,,,}'))\n\n// console.log(j('[1 2,3 a, b, 4, 5]', { log: -1 }))\n\n// console.log(j('# foo', { log: -1 }))\n\n// console.log(j('\"a\": Z1, \"b\":Z2, \"c\": q', { log: -1 }))\n\n// console.log(j('1, 2', { log: -1 }))\n\n// console.log(j('a:1',{log:-1}))\n\n// console.log(j('[1',{log:-1}))\n\n// console.log(j('[9,8,a:1]',{log:-1}))\n// console.log(j('[,1]',{log:-1}))\n\n// console.log(j('}\"',{log:-1}))\n// console.log(j('a]',{log:-1}))\n// console.log(j('[a:b:c]',{log:-1}))\n// console.log(j('[a:1]',{log:-1}))\n// console.log(j('[{}]',{log:-1}))\n// console.log(j('y:{a:b:2},z:0',{log:-1}))\n// console.log(j('{y:{a:b:2},z:0}',{log:-1}))\n// console.log(j('{y:{x:{a:b:2}},z:0}',{log:-1}))\n\n// console.log(j('{,]',{log:-1}))\n// console.log(j('[0,a:1,1,b:2,c:3]',{log:-1}))\n// console.log(j('{',{log:-1}))\n// console.log(j('a:1,b:2,',{log:-1}))\n// console.log(j('a:b:1 c:d:2',{xlog:-1}))\n// console.log(j('a:b:c:1,d:2',{log:-1}))\n// console.log(j('a,b,',{log:-1}))\n// console.log(j('[a,b,]',{log:-1}))\n// console.log(j('#x',{log:-1}))\n// console.log(j('a:b:1,c:2',{log:-1}))\n// console.log(j('{a:1,b:2}',{log:-1}))\n\n// console.log(j('{,]',{log:-1}))\n\n// let json = Jsonic.make('json')\n\n// console.log(util.deep(undefined, 1))\n// console.log(util.deep(1, /a/))\n\n// console.log(Jsonic('{x:[a:1,2]}',{log:-1}))\n\n// console.log(Jsonic('[a:b:c, a:d:e]'))\n\n// console.log(json('{\"a\":1}',{log:-1}))\n// console.log(json('{0:1}',{xlog:-1}))\n\n//console.log(json('[\"a\"00,\"b\"]',{log:-1}))\n\n// console.log(json('[true 00,\"b\"]', { log: -1 }))\n\n// console.log(Jsonic('[{a:1 b:2}]', { log: -1 }))\n\n// console.log(Jsonic.make().token('#CA'))\n"
  },
  {
    "path": "test/readme.test.js",
    "content": "/* Copyright (c) 2021 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\n// Compare via JSON roundtrip to handle null-prototype objects.\nfunction deq(actual, expected, msg) {\n  assert.deepStrictEqual(\n    JSON.parse(JSON.stringify(actual)),\n    JSON.parse(JSON.stringify(expected)),\n    msg,\n  )\n}\nconst eq = assert.strictEqual\n\ndescribe('readme', function () {\n  describe('quick-example', () => {\n    it('parses implicit object', () => {\n      deq(Jsonic('a:1, b:2'), { a: 1, b: 2 })\n    })\n\n    it('parses implicit array', () => {\n      deq(Jsonic('x, y, z'), ['x', 'y', 'z'])\n    })\n\n    it('parses nested object', () => {\n      deq(Jsonic('{a: {b: 1, c: 2}}'), { a: { b: 1, c: 2 } })\n    })\n  })\n\n  describe('syntax-examples', () => {\n    it('unquoted keys and values', () => {\n      deq(Jsonic('a:1,b:B'), { a: 1, b: 'B' })\n    })\n\n    it('newline separated', () => {\n      deq(Jsonic('a:1\\nb:B'), { a: 1, b: 'B' })\n    })\n\n    it('with comments', () => {\n      deq(\n        Jsonic('a:1\\n// a:2\\n# a:3\\n/* b wants\\n * to B\\n */\\nb:B'),\n        { a: 1, b: 'B' },\n      )\n    })\n\n    it('mixed quote styles and number formats', () => {\n      deq(Jsonic('{ \"a\": 100e-2, \\'\\\\u0062\\':`\\\\x42`, }'), {\n        a: 1,\n        b: 'B',\n      })\n    })\n  })\n\n  describe('relaxation-examples', () => {\n    it('unquoted keys and values', () => {\n      deq(Jsonic('a:1'), { a: 1 })\n    })\n\n    it('implicit top-level object', () => {\n      deq(Jsonic('a:1,b:2'), { a: 1, b: 2 })\n    })\n\n    it('implicit top-level array', () => {\n      deq(Jsonic('a,b'), ['a', 'b'])\n    })\n\n    it('trailing commas', () => {\n      deq(Jsonic('{a:1,b:2,}'), { a: 1, b: 2 })\n    })\n\n    it('single-quoted strings', () => {\n      eq(Jsonic(\"'hello'\"), 'hello')\n    })\n\n    it('backtick strings', () => {\n      eq(Jsonic('`hello`'), 'hello')\n    })\n\n    it('object merging', () => {\n      deq(Jsonic('a:{b:1},a:{c:2}'), { a: { b: 1, c: 2 } })\n    })\n\n    it('path diving', () => {\n      deq(Jsonic('a:b:1,a:c:2'), { a: { b: 1, c: 2 } })\n    })\n\n    it('all number formats equivalent', () => {\n      eq(Jsonic('1e1'), 10)\n      eq(Jsonic('0xa'), 10)\n      eq(Jsonic('0o12'), 10)\n      eq(Jsonic('0b1010'), 10)\n    })\n\n    it('number separators', () => {\n      eq(Jsonic('1_000'), 1000)\n    })\n\n  })\n\n  describe('options-example', () => {\n    it('make with options', () => {\n      const lenient = Jsonic.make({\n        comment: { lex: false },\n        number: { hex: false },\n        value: {\n          def: { yes: { val: true }, no: { val: false } },\n        },\n      })\n      eq(lenient('yes'), true)\n    })\n  })\n\n  describe('plugin-example', () => {\n    it('custom plugin with fixed token', () => {\n      function myPlugin(jsonic, options) {\n        jsonic.options({ fixed: { token: { '#TL': '~' } } })\n        const T_TILDE = jsonic.token('#TL')\n\n        jsonic.rule('val', (rs) => {\n          rs.open([\n            {\n              s: [T_TILDE],\n              a: (rule) => {\n                rule.node = options.tildeValue ?? null\n              },\n            },\n          ])\n        })\n      }\n\n      const j = Jsonic.make()\n      j.use(myPlugin, { tildeValue: 42 })\n      eq(j('~'), 42)\n    })\n  })\n\n  describe('api-table-examples', () => {\n    it('Jsonic(src) parses a string', () => {\n      deq(Jsonic('a:1'), { a: 1 })\n    })\n\n    it('Jsonic.make() creates a configured instance', () => {\n      const j = Jsonic.make()\n      deq(j('a:1'), { a: 1 })\n    })\n\n    it('instance.use() registers a plugin', () => {\n      const j = Jsonic.make()\n      let called = false\n      j.use(function testPlugin() {\n        called = true\n      })\n      eq(called, true)\n    })\n\n    it('instance.rule() modifies grammar', () => {\n      const j = Jsonic.make()\n      const rules = j.rule()\n      deq(Object.keys(rules), ['val', 'map', 'list', 'pair', 'elem'])\n    })\n\n    it('instance.token() gets or creates a token type', () => {\n      const j = Jsonic.make()\n      eq(typeof j.token.ST, 'number')\n    })\n\n    it('instance.options returns current options', () => {\n      const j = Jsonic.make()\n      eq(j.options.comment.lex, true)\n    })\n  })\n})\n"
  },
  {
    "path": "test/require.js",
    "content": "const JsonicDirect = require('..')\nconst { Jsonic } = require('..')\n\nconsole.log('JsonicDirect', JsonicDirect('a:1'))\nconsole.log('Jsonic', Jsonic('a:1'))\n"
  },
  {
    "path": "test/rewind.test.js",
    "content": "/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// Tests for the token rewind primitives exposed on ctx.\n// `ctx.mark()` captures the current parse position; `ctx.rewind(m)`\n// replays every token consumed since that mark by pushing them back\n// onto the active lexer's pending-token queue. This is the\n// foundation for seed-and-grow left recursion and other backtracking\n// patterns.\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic } = require('..')\n\n\nfunction make_norules(opts) {\n  let j = Jsonic.make(opts)\n  let rns = j.rule()\n  Object.keys(rns).map((rn) => j.rule(rn, null))\n  return j\n}\n\n\ndescribe('rewind', () => {\n\n  it('ctx.v records each consumed token in order', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    let recorded = null\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb, Tc] }])\n        .close([{\n          s: '#ZZ',\n          // Alt actions fire after the v-history push for this alt,\n          // so by the time this runs all four tokens — a, b, c, and\n          // the end sentinel — are on the stack.\n          a: (r, ctx) => { recorded = ctx.v.map((t) => t.src) },\n        }]),\n    )\n\n    j('abc')\n    assert.deepEqual(recorded, ['a', 'b', 'c', ''])\n  })\n\n\n  it('v1 and v2 still read the top of the history stack', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    let seen = null\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          s: [Ta, Tb, Tc],\n          // Alt actions run after the v-history push, so v1 is the\n          // most-recently consumed token of this match (c) and v2 is\n          // the one before it (b).\n          a: (r, ctx) => {\n            seen = { v1: ctx.v1.src, v2: ctx.v2.src }\n          },\n        }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j('abc')\n    assert.deepEqual(seen, { v1: 'c', v2: 'b' })\n  })\n\n\n  it('rewind replays consumed tokens so they re-lex forward', () => {\n    // A rule that consumes three fixed tokens, rewinds to the start,\n    // then consumes them again. Without rewind, the second consume\n    // attempt would hit end-of-source and fail.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    let { Ta, Tb, Tc } = j.token\n\n    let trace = []\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          s: [Ta, Tb, Tc],\n          a: (r, ctx) => {\n            trace.push('first:' + ctx.v.slice(-3).map((t) => t.src).join(''))\n            const mark = 0  // start of the parse history\n            ctx.rewind(mark)\n            trace.push('after-rewind-v-len:' + ctx.v.length)\n          },\n          p: 'again',\n        }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j.rule('again', (rs) =>\n      rs\n        .open([{\n          s: [Ta, Tb, Tc],\n          a: (r, ctx) => {\n            trace.push('second:' + ctx.v.slice(-3).map((t) => t.src).join(''))\n          },\n        }]),\n    )\n\n    j('abc')\n    assert.deepEqual(trace, [\n      'first:abc',\n      'after-rewind-v-len:0',\n      'second:abc',\n    ])\n  })\n\n\n  it('partial rewind replays only the tokens after the mark', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c', Td: 'd' } },\n    })\n    let { Ta, Tb, Tc, Td } = j.token\n\n    let second = null\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          s: [Ta, Tb, Tc, Td],\n          a: (r, ctx) => {\n            // Mark right after a, then rewind to undo bcd.\n            const markAfterA = 1\n            ctx.rewind(markAfterA)\n          },\n          p: 'tail',\n        }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j.rule('tail', (rs) =>\n      rs\n        .open([{\n          s: [Tb, Tc, Td],\n          a: (r, ctx) => {\n            second = ctx.v.map((t) => t.src).join('')\n          },\n        }]),\n    )\n\n    j('abcd')\n    // Full history at the end of tail's open: all four tokens again\n    // (a was not rewound; bcd were rewound and re-consumed).\n    assert.equal(second, 'abcd')\n  })\n\n\n  it('rewind with no tokens consumed since the mark is a no-op', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n    })\n    let { Ta } = j.token\n\n    let ok = false\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          s: [Ta],\n          a: (r, ctx) => {\n            const mark = ctx.mark()\n            // Same mark, no consumption → rewind is a no-op.\n            ctx.rewind(mark)\n            ok = ctx.v.length === mark\n          },\n        }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j('a')\n    assert.equal(ok, true)\n  })\n\n\n  it('rewind enables speculative lookahead', () => {\n    // Use rewind to implement a peek-and-commit pattern: the rule\n    // consumes a token to inspect its value, then rewinds and\n    // dispatches to one of two continuations based on what it saw.\n    // Without rewind this would need either an N-token `s:` pattern\n    // or custom `c:` predicates reading `ctx.t[i]`; rewind gives us\n    // an imperative alternative that's the backbone of any\n    // backtracking or seed-and-grow scheme.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b', Tc: 'c' } },\n    })\n    const Ta = j.token('Ta')\n    const Tb = j.token('Tb')\n    const Tc = j.token('Tc')\n\n    let branch = null\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          // Peek one token, rewind, then dispatch based on what we saw.\n          s: [Ta],\n          a: (r, ctx) => {\n            const saw = r.o[0].src\n            ctx.rewind(0)       // replay the a we just consumed\n            r.u.branch = saw === 'a' ? 'A' : 'Z'\n          },\n        }])\n        .close([{\n          c: (r) => r.u.branch === 'A',\n          s: [Ta, Tb, Tc],\n          a: () => { branch = 'A' },\n        }, {\n          s: '#ZZ',\n        }]),\n    )\n\n    j('a b c')\n    assert.equal(branch, 'A')\n  })\n\n\n  it('rewind from inside a close-state action also replays', () => {\n    // Exercise the path where the rewind happens from close-state\n    // alt logic. `rule.k` survives an `r:` replacement (`u` does\n    // not), so we store the \"already rewound once\" flag on `k` to\n    // guarantee termination.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a', Tb: 'b' } },\n    })\n    let { Ta, Tb } = j.token\n\n    let attempts = 0\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{ s: [Ta, Tb], a: () => { attempts++ } }])\n        .close([{\n          c: (r) => !r.k.rewound,\n          a: (r, ctx) => {\n            r.k.rewound = true\n            ctx.rewind(0)   // replay both tokens so open can re-match\n          },\n          r: 'top',\n        }, {\n          s: '#ZZ',\n        }]),\n    )\n\n    j('ab')\n    // Open fired once on the initial pass and once again after rewind.\n    assert.equal(attempts, 2)\n  })\n\n\n  it('options.rewind.history caps ctx.v size via batch eviction', () => {\n    // With a capacity of 4, ctx.v never exceeds 2*cap = 8 entries\n    // (eviction happens once the array crosses 2*cap; amortised\n    // O(1) per push).\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n      rewind: { history: 4 },\n    })\n    let { Ta } = j.token\n\n    let maxSeen = 0\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          // Consume a's one at a time via r:self, tracking the peak\n          // size of ctx.v.\n          s: [Ta],\n          a: (r, ctx) => { if (ctx.v.length > maxSeen) maxSeen = ctx.v.length },\n        }])\n        .close([\n          { s: [Ta], b: 1, r: 'top' },\n          { s: '#ZZ' },\n        ]),\n    )\n\n    // 20 a's — well above 2*cap.\n    j('a a a a a a a a a a a a a a a a a a a a')\n    assert.ok(maxSeen <= 2 * 4,\n      `ctx.v grew to ${maxSeen}, expected <= ${2 * 4}`)\n    // The cap never shrinks below `history` itself, so the parser\n    // can still see recent tokens.\n    assert.ok(maxSeen >= 4,\n      `ctx.v only reached ${maxSeen}, expected >= 4`)\n  })\n\n\n  it('marks stay valid across ring-buffer eviction', () => {\n    // Even after the ring evicts older tokens, a mark captured\n    // *within* the retained window rewinds correctly. vAbs is\n    // absolute so the mark's meaning doesn't depend on ctx.v's\n    // current indexing.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n      rewind: { history: 4 },\n    })\n    let { Ta } = j.token\n\n    let rewound = null\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          // Consume five a's, then rewind to right after the third.\n          s: [Ta, Ta, Ta, Ta, Ta],\n          a: (r, ctx) => {\n            const after3 = ctx.vAbs - 2 // absolute mark after the 3rd a\n            ctx.rewind(after3)\n            rewound = ctx.vAbs\n          },\n        }])\n        .close([\n          { s: [Ta, Ta], a: () => {} }, // re-consume the replayed 4th and 5th\n          { s: '#ZZ' },\n        ]),\n    )\n\n    j('a a a a a')\n    assert.equal(rewound, 3)\n  })\n\n\n  it('rewinding past the retained window throws', () => {\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n      rewind: { history: 2 },\n    })\n    let { Ta } = j.token\n\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          // Try to rewind to absolute index 0 after consuming six a's;\n          // with history=2 and batch eviction at 2*cap=4, the oldest\n          // mark still in reach is 4, so target=0 is out of range.\n          s: [Ta, Ta, Ta, Ta, Ta, Ta],\n          a: (r, ctx) => { ctx.rewind(0) },\n        }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    assert.throws(\n      () => j('a a a a a a'),\n      /ctx\\.rewind target 0 is outside the retained history/,\n    )\n  })\n\n\n  it('default history is 64 (retains every token for small parses)', () => {\n    // The default cap is 64, which means parses shorter than the cap\n    // retain every consumed token — identical to an unbounded\n    // history for small/medium inputs. Batch eviction only kicks\n    // in once ctx.v crosses 2 * 64 = 128 entries.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n    })\n    let { Ta } = j.token\n\n    let finalV = 0\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          s: [Ta, Ta, Ta, Ta, Ta, Ta, Ta, Ta, Ta, Ta],\n          a: (r, ctx) => { finalV = ctx.v.length },\n        }])\n        .close([{ s: '#ZZ' }]),\n    )\n\n    j('a a a a a a a a a a')\n    assert.equal(finalV, 10)\n  })\n\n\n  it('Infinity history retains every token regardless of input size', () => {\n    // Opt in to unbounded retention by passing Infinity.\n    let j = make_norules({\n      rule: { start: 'top' },\n      fixed: { token: { Ta: 'a' } },\n      rewind: { history: Infinity },\n    })\n    let { Ta } = j.token\n\n    let maxV = 0\n    j.rule('top', (rs) =>\n      rs\n        .open([{\n          s: [Ta],\n          a: (r, ctx) => { if (ctx.v.length > maxV) maxV = ctx.v.length },\n        }])\n        .close([\n          { s: [Ta], b: 1, r: 'top' },\n          { s: '#ZZ' },\n        ]),\n    )\n\n    // 200 a's — would be batch-evicted under the default cap of 64\n    // (max 128), but Infinity keeps every one.\n    j(Array(200).fill('a').join(' '))\n    assert.ok(maxV >= 200, `expected >= 200 retained, got ${maxV}`)\n  })\n\n})\n"
  },
  {
    "path": "test/rfc3986.test.js",
    "content": "/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */\n'use strict'\n\n// End-to-end test for RFC 3986 Appendix A — the collected ABNF\n// grammar for URI.  https://www.rfc-editor.org/rfc/rfc3986.txt\n//\n// The fixture `test/grammar/rfc3986-uri.abnf` contains the full\n// grammar, one rule per RFC 3986 name, using the same syntax the\n// RFC prints. This exercises every ABNF feature the converter\n// implements:\n//   - bare-identifier rule names with hyphens  (path-abempty …)\n//   - `=`, `=/`, `/`, `[ … ]`, `( … )`\n//   - prefix repetition  *A, 1*A, m*nA, *nA, m*A, nA\n//   - numeric values     %x41-5A, %x30-39, …\n//   - case-insensitive quoted literals (default)\n//   - automatic inclusion of the core rules (ALPHA / DIGIT / HEXDIG)\n//   - `;` comments\n//\n// The RFC 3986 grammar is not LL(k) — the `authority` production\n// is ambiguous on its `[ userinfo \"@\" ]` prefix, because `userinfo`\n// can match the same character set as `reg-name`. The converter\n// handles this via the probe + phase-retry dispatcher it emits for\n// every detected `[X D] Y` pattern (see src/bnf.ts and the\n// dedicated coverage in test/probe.test.js). Both authority shapes\n// — with and without userinfo — now parse cleanly; this test drives\n// the end-to-end integration.\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\nconst Fs = require('node:fs')\nconst Path = require('node:path')\n\nconst { Jsonic } = require('..')\n\nconst GRAMMAR = Fs.readFileSync(\n  Path.join(__dirname, 'grammar', 'rfc3986-uri.abnf'),\n).toString()\n\n\ndescribe('rfc3986', () => {\n\n  describe('grammar compilation', () => {\n\n    it('compiles the full RFC 3986 grammar without error', () => {\n      // Just building the jsonic rule set is already a non-trivial\n      // test — 30 productions, bounded repetition, numeric ranges,\n      // nullable alternatives, transitive core-rule inclusion, and\n      // the ABNF-wide case-insensitive string default all have to\n      // co-exist cleanly.\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      assert.doesNotThrow(() => j.bnf(GRAMMAR))\n    })\n\n\n    it('every RFC 3986 production survives into the emitted spec', () => {\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      const spec = j.bnf(GRAMMAR)\n      // Every name from the .abnf file should appear as a rule in\n      // the spec (plus `__start__` and the generated helpers).\n      const ruleNames = Object.keys(spec.rule)\n      const expected = [\n        'URI', 'hier-part', 'scheme', 'authority', 'userinfo',\n        'host', 'port', 'IP-literal', 'IPvFuture', 'IPv6address',\n        'h16', 'ls32', 'IPv4address', 'dec-octet', 'reg-name',\n        'path-abempty', 'path-absolute', 'path-rootless',\n        'path-empty', 'segment', 'segment-nz', 'pchar', 'query',\n        'fragment', 'pct-encoded', 'unreserved', 'sub-delims',\n        // Core rules the grammar uses implicitly:\n        'ALPHA', 'DIGIT', 'HEXDIG',\n      ]\n      for (const name of expected) {\n        assert.ok(\n          ruleNames.includes(name),\n          `missing rule '${name}' in spec (only saw ${ruleNames.length} rules)`,\n        )\n      }\n    })\n\n\n    it('detects and rewrites the authority ambiguity', () => {\n      // The `[ userinfo \"@\" ] host [ \":\" port ]` shape in `authority`\n      // is ambiguous under FIRST-set dispatch. The rewriter\n      // synthesises a probe + phase-retry dispatcher for it; the\n      // presence of `authority$pdN$probe` / `$with` / `$no` rules in\n      // the spec confirms the rewrite fired.\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      const spec = j.bnf(GRAMMAR)\n      const names = Object.keys(spec.rule)\n      assert.ok(names.some((n) => /^authority\\$pd\\d+\\$probe$/.test(n)),\n        'expected a probe helper for authority')\n      assert.ok(names.some((n) => /^authority\\$pd\\d+\\$with$/.test(n)),\n        'expected a with-branch rule for authority')\n      assert.ok(names.some((n) => /^authority\\$pd\\d+\\$no$/.test(n)),\n        'expected a no-branch rule for authority')\n    })\n\n  })\n\n\n  describe('URI acceptance', () => {\n\n    const parser = (() => {\n      const j = Jsonic.make({ rewind: { history: 4096 } })\n      j.bnf(GRAMMAR)\n      return j\n    })()\n\n    const ACCEPT = [\n      // path-rootless: scheme ':' segment …\n      'urn:isbn:0451450523',\n      'mailto:alice@example.com',\n      'tag:yaml.org,2002:int',\n      // IP-literal authority (the leading '[' disambiguates)\n      'http://[::1]/',\n      // Authority without userinfo (the canonical LL(k)-ambiguous\n      // case the probe dispatcher now resolves):\n      'http://example.com',\n      'http://example.com:8080',\n      // Authority with userinfo (same probe path, different phase):\n      'ftp://user@host',\n      'http://user@example.com:8080',\n      'http://user:pass@example.com:8080/some/path',\n      // Full URI exercising every optional tail:\n      'https://www.example.org/path/to/resource?name=value&other=thing#section',\n    ]\n\n    for (const uri of ACCEPT) {\n      it(`accepts ${JSON.stringify(uri)}`, { timeout: 5000 }, () => {\n        assert.doesNotThrow(() => parser(uri))\n      })\n    }\n\n    // Obviously invalid — must reject.\n    const REJECT = [\n      'not a uri',     // space disallowed at this position\n      ':foo',          // scheme can't start with ':'\n    ]\n\n    for (const uri of REJECT) {\n      it(`rejects ${JSON.stringify(uri)}`, { timeout: 5000 }, () => {\n        assert.throws(() => parser(uri), /unexpected/)\n      })\n    }\n\n  })\n\n\n  describe('remaining LL(k) limitations', () => {\n\n    // The probe + phase-retry pattern resolves the specific\n    // `[X D] Y` shape where X and Y share a character vocabulary\n    // and D is a terminal disambiguator. It does NOT handle every\n    // imaginable LL(k) ambiguity — in particular, ambiguities where\n    // the disambiguator is itself a nonterminal, or where two\n    // alternatives share an arbitrarily-deep prefix with no local\n    // tie-breaker, still require true backtracking at the\n    // alt-dispatch level (a catch-and-rewind mechanism the emitter\n    // doesn't provide).\n    //\n    // No such unhandled shape appears in RFC 3986 as written; the\n    // remaining edge cases are documented here rather than asserted\n    // so the converter's capability boundary stays visible. If a\n    // future grammar exposes a genuinely un-probeable ambiguity,\n    // add the concrete failing input as a `.skip` or `.todo` test\n    // with a one-line explanation.\n\n    it.skip('placeholder for grammars with non-terminal disambiguators', () => {\n      // Example shape:\n      //   rule = [ A B ] C     where A, B, C are all nonterminals,\n      //                        FIRST(A) ∩ FIRST(C) ≠ ∅, and B is\n      //                        not a terminal (so there's no single\n      //                        token to peek for).\n      // The current rewriter requires `D` to be a term / regex\n      // element; detection skips this shape with no rewrite.\n    })\n\n  })\n\n})\n"
  },
  {
    "path": "test/safe.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst Util = require('util')\nconst I = Util.inspect\n\nconst { Jsonic, JsonicError, RuleSpec } = require('..')\n\nconst j = Jsonic\n\nconst JS = (x) => JSON.stringify(x)\n\ndescribe('safe', function () {\n  it('key', () => {\n    // Objects are protected because they are Object.create(null)\n    let p0o = Jsonic('{__proto__:{toString:FAIL}}')\n    assert.deepEqual(p0o.__proto__, { toString: 'FAIL' })\n    assert.deepEqual({}.toString(), '[object Object]')\n\n    // Arrays are protected\n    let p0a = Jsonic('[1,2,__proto__:{toString:FAIL}]')\n    assert.deepEqual(('' + p0a.toString).startsWith('function toString()'), true)\n    assert.deepEqual(p0a, [1, 2])\n    assert.deepEqual(p0a.__proto__.toString !== 'FAIL', true)\n    assert.deepEqual([1, 2].toString(), '1,2')\n\n    // Objects are still protected\n    let unsafe = Jsonic.make({ safe: { key: false } })\n    let p1o = unsafe('{__proto__:{toString:FAIL}}')\n    assert.deepEqual(p1o.__proto__, { toString: 'FAIL' })\n    assert.deepEqual({}.toString(), '[object Object]')\n\n    // Arrays not are protected\n    let p1a = unsafe('[1,2,__proto__:{toString:FAIL}]')\n    assert.deepEqual(('' + p1a.toString).startsWith('FAIL'), true)\n  })\n\n  it('prop', () => {\n    const { prop } = Jsonic.util\n    const v = {}\n\n    assert.throws(() => prop({}, '__proto__.x', 11), /Cannot/)\n    assert.deepEqual(v.x, undefined)\n  })\n})\n"
  },
  {
    "path": "test/smoke.js",
    "content": "let { Jsonic } = require('..')\n\nlet j = (s) => {\n  try {\n    return JSON.stringify(Jsonic(s))\n  } catch (e) {\n    return e.message.split(/\\n/)[0]\n  }\n}\n\nlet cases = [\n  ['1', '1'],\n  ['true', 'true'],\n  ['x', '\"x\"'],\n  ['\"y\"', '\"y\"'],\n\n  ['{a:1}', '{\"a\":1}'],\n  ['{a:1,b:2}', '{\"a\":1,\"b\":2}'],\n  ['{a:1,b:2,c:3}', '{\"a\":1,\"b\":2,\"c\":3}'],\n  ['{a:{b:2}}', '{\"a\":{\"b\":2}}'],\n  ['{a:{b:2},c:3}', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['{a:{b:2,c:3}}', '{\"a\":{\"b\":2,\"c\":3}}'],\n  ['{a:{b:{c:3}}', '{\"a\":{\"b\":{\"c\":3}}}'],\n\n  ['a:1', '{\"a\":1}'],\n  ['a:1,b:2', '{\"a\":1,\"b\":2}'],\n  ['a:1,b:2,c:3', '{\"a\":1,\"b\":2,\"c\":3}'],\n  ['a:{b:2}', '{\"a\":{\"b\":2}}'],\n  ['a:{b:2},c:3', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['a:{b:2,c:3}', '{\"a\":{\"b\":2,\"c\":3}}'],\n  ['a:{b:{c:3}', '{\"a\":{\"b\":{\"c\":3}}}'],\n\n  ['{a:1,x:0}', '{\"a\":1,\"x\":0}'],\n  ['{a:1,b:2,x:0}', '{\"a\":1,\"b\":2,\"x\":0}'],\n  ['{a:{b:2,x:0},x:0}', '{\"a\":{\"b\":2,\"x\":0},\"x\":0}'],\n  ['{a:{b:2,x:0},c:3,x:0}', '{\"a\":{\"b\":2,\"x\":0},\"c\":3,\"x\":0}'],\n  ['{a:{b:2,c:3,x:0},x:0}', '{\"a\":{\"b\":2,\"c\":3,\"x\":0},\"x\":0}'],\n  ['{a:{b:{c:3,x:0},x:0}', '{\"a\":{\"b\":{\"c\":3,\"x\":0},\"x\":0}}'],\n\n  ['a:1,x:0', '{\"a\":1,\"x\":0}'],\n  ['a:1,b:2,x:0', '{\"a\":1,\"b\":2,\"x\":0}'],\n  ['a:{b:2,x:0},x:0', '{\"a\":{\"b\":2,\"x\":0},\"x\":0}'],\n  ['a:{b:2,x:0},c:3,x:0', '{\"a\":{\"b\":2,\"x\":0},\"c\":3,\"x\":0}'],\n  ['a:{b:2,c:3,x:0},x:0', '{\"a\":{\"b\":2,\"c\":3,\"x\":0},\"x\":0}'],\n  ['a:{b:{c:3,x:0},x:0', '{\"a\":{\"b\":{\"c\":3,\"x\":0},\"x\":0}}'],\n\n  ['{a:b:2}', '{\"a\":{\"b\":2}}'],\n  ['{a:b:c:3}', '{\"a\":{\"b\":{\"c\":3}}}'],\n  ['{a:b:2,c:3}', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['{a:1,b:c:3}', '{\"a\":1,\"b\":{\"c\":3}}'],\n  ['{a:b:c:3,d:4}', '{\"a\":{\"b\":{\"c\":3}},\"d\":4}'],\n  ['{a:1,b:c:d:4}', '{\"a\":1,\"b\":{\"c\":{\"d\":4}}}'],\n  ['{a:b:2,c:d:4}', '{\"a\":{\"b\":2},\"c\":{\"d\":4}}'],\n  ['{a:b:c:3,d:e:f:6}', '{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}'],\n\n  ['a:b:2', '{\"a\":{\"b\":2}}'],\n  ['a:b:c:3', '{\"a\":{\"b\":{\"c\":3}}}'],\n  ['a:b:2,c:3', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['a:1,b:c:3', '{\"a\":1,\"b\":{\"c\":3}}'],\n  ['a:b:c:3,d:4', '{\"a\":{\"b\":{\"c\":3}},\"d\":4}'],\n  ['a:1,b:c:d:4', '{\"a\":1,\"b\":{\"c\":{\"d\":4}}}'],\n  ['a:b:2,c:d:4', '{\"a\":{\"b\":2},\"c\":{\"d\":4}}'],\n  ['a:b:c:3,d:e:f:6', '{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}'],\n\n  ['{x:{a:b:2}}', '{\"x\":{\"a\":{\"b\":2}}}'],\n  ['{x:{a:b:c:3}}', '{\"x\":{\"a\":{\"b\":{\"c\":3}}}}'],\n  ['{x:{a:b:2,c:3}}', '{\"x\":{\"a\":{\"b\":2},\"c\":3}}'],\n  ['{x:{a:1,b:c:3}}', '{\"x\":{\"a\":1,\"b\":{\"c\":3}}}'],\n  ['{x:{a:b:c:3,d:4}}', '{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}'],\n  ['{x:{a:1,b:c:d:4}}', '{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}'],\n  ['{x:{a:b:2,c:d:4}}', '{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}'],\n  ['{x:{a:b:c:3,d:e:f:6}}', '{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}'],\n\n  ['x:{a:b:2}', '{\"x\":{\"a\":{\"b\":2}}}'],\n  ['x:{a:b:c:3}', '{\"x\":{\"a\":{\"b\":{\"c\":3}}}}'],\n  ['x:{a:b:2,c:3}', '{\"x\":{\"a\":{\"b\":2},\"c\":3}}'],\n  ['x:{a:1,b:c:3}', '{\"x\":{\"a\":1,\"b\":{\"c\":3}}}'],\n  ['x:{a:b:c:3,d:4}', '{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}'],\n  ['x:{a:1,b:c:d:4}', '{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}'],\n  ['x:{a:b:2,c:d:4}', '{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}'],\n  ['x:{a:b:c:3,d:e:f:6}', '{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}'],\n\n  ['{y:{x:{a:b:2}}}', '{\"y\":{\"x\":{\"a\":{\"b\":2}}}}'],\n  ['{y:{x:{a:b:c:3}}}', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}}}'],\n  ['{y:{x:{a:b:2,c:3}}}', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}}}'],\n  ['{y:{x:{a:1,b:c:3}}}', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}}}'],\n  ['{y:{x:{a:b:c:3,d:4}}}', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}}'],\n  ['{y:{x:{a:1,b:c:d:4}}}', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}}'],\n  ['{y:{x:{a:b:2,c:d:4}}}', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}}'],\n  [\n    '{y:{x:{a:b:c:3,d:e:f:6}}}',\n    '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}}',\n  ],\n\n  ['y:{x:{a:b:2}}', '{\"y\":{\"x\":{\"a\":{\"b\":2}}}}'],\n  ['y:{x:{a:b:c:3}}', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}}}'],\n  ['y:{x:{a:b:2,c:3}}', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}}}'],\n  ['y:{x:{a:1,b:c:3}}', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}}}'],\n  ['y:{x:{a:b:c:3,d:4}}', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}}'],\n  ['y:{x:{a:1,b:c:d:4}}', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}}'],\n  ['y:{x:{a:b:2,c:d:4}}', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}}'],\n  [\n    'y:{x:{a:b:c:3,d:e:f:6}}',\n    '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}}',\n  ],\n\n  ['{y:{x:{a:b:2}},z:0}', '{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":0}'],\n  ['{y:{x:{a:b:c:3}},z:0}', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}},\"z\":0}'],\n  ['{y:{x:{a:b:2,c:3}},z:0}', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}},\"z\":0}'],\n  ['{y:{x:{a:1,b:c:3}},z:0}', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}},\"z\":0}'],\n  ['{y:{x:{a:b:c:3,d:4}},z:0}', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}},\"z\":0}'],\n  ['{y:{x:{a:1,b:c:d:4}},z:0}', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}},\"z\":0}'],\n  ['{y:{x:{a:b:2,c:d:4}},z:0}', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}},\"z\":0}'],\n  [\n    '{y:{x:{a:b:c:3,d:e:f:6}},z:0}',\n    '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}},\"z\":0}',\n  ],\n\n  ['y:{x:{a:b:2}},z:0', '{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":0}'],\n  ['y:{x:{a:b:c:3}},z:0', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}},\"z\":0}'],\n  ['y:{x:{a:b:2,c:3}},z:0', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}},\"z\":0}'],\n  ['y:{x:{a:1,b:c:3}},z:0', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}},\"z\":0}'],\n  ['y:{x:{a:b:c:3,d:4}},z:0', '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}},\"z\":0}'],\n  ['y:{x:{a:1,b:c:d:4}},z:0', '{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}},\"z\":0}'],\n  ['y:{x:{a:b:2,c:d:4}},z:0', '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}},\"z\":0}'],\n  [\n    'y:{x:{a:b:c:3,d:e:f:6}},z:0',\n    '{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}},\"z\":0}',\n  ],\n\n  ['{y:{x:{a:b:2}},z:k:0}', '{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":{\"k\":0}}'],\n  [\n    '{y:{x:{a:b:2,c:d:e:5,f:g:7}},z:k:{m:n:0,r:11},s:22}',\n    '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":{\"e\":5}},\"f\":{\"g\":7}}},\"z\":{\"k\":{\"m\":{\"n\":0},\"r\":11}},\"s\":22}',\n  ],\n\n  ['y:{x:{a:b:2}},z:k:0', '{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":{\"k\":0}}'],\n  [\n    'y:{x:{a:b:2,c:d:e:5,f:g:7}},z:k:{m:n:0,r:11},s:22',\n    '{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":{\"e\":5}},\"f\":{\"g\":7}}},\"z\":{\"k\":{\"m\":{\"n\":0},\"r\":11}},\"s\":22}',\n  ],\n\n  ['{a:1 b:2}', '{\"a\":1,\"b\":2}'],\n  ['a:1 b:2', '{\"a\":1,\"b\":2}'],\n\n  ['{a:1 b:2 c:3}', '{\"a\":1,\"b\":2,\"c\":3}'],\n  ['a:1 b:2 c:3', '{\"a\":1,\"b\":2,\"c\":3}'],\n\n  ['{a:b:2 c:3}', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['{a:b:2 `c`:3}', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['{a:b:2 99:3}', '{\"99\":3,\"a\":{\"b\":2}}'],\n  ['{a:b:2 true:3}', '{\"a\":{\"b\":2},\"true\":3}'],\n\n  ['a:b:2 c:3', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['a:b:2 `c`:3', '{\"a\":{\"b\":2},\"c\":3}'],\n  ['a:b:2 99:3', '{\"99\":3,\"a\":{\"b\":2}}'],\n  ['a:b:2 true:3', '{\"a\":{\"b\":2},\"true\":3}'],\n\n  ['{a:{b:c:3} d:4}', '{\"a\":{\"b\":{\"c\":3}},\"d\":4}'],\n  ['a:{b:c:3} d:4', '{\"a\":{\"b\":{\"c\":3}},\"d\":4}'],\n\n  ['[a]', '[\"a\"]'],\n  ['[a,b]', '[\"a\",\"b\"]'],\n\n  ['[a]', '[\"a\"]'],\n  ['[a,[b]]', '[\"a\",[\"b\"]]'],\n\n  ['[a b]', '[\"a\",\"b\"]'],\n  ['[a [b]]', '[\"a\",[\"b\"]]'],\n  ['[a {b:2}]', '[\"a\",{\"b\":2}]'],\n\n  ['[a,b,]', '[\"a\",\"b\"]'],\n  ['{a:1,b:2,}', '{\"a\":1,\"b\":2}'],\n\n  ['a,b', '[\"a\",\"b\"]'],\n\n  ['{}', '{}'],\n  ['[]', '[]'],\n\n  ['[,]', '[null]'],\n  ['[,1]', '[null,1]'],\n  ['[,,1]', '[null,null,1]'],\n  ['[2,]', '[2]'],\n  ['[2,,1]', '[2,null,1]'],\n]\n\nlet count = { pass: 0, fail: 0 }\n\ncases.forEach((c) => {\n  let out = j(c[0])\n  let ok = out === c[1]\n  count[ok ? 'pass' : 'fail']++\n  console.log(ok ? '\\x1b[0;32mPASS' : '\\x1b[1;31mFAIL', c[0], '->', out)\n  if (!ok) {\n    console.log(' '.repeat(7 + c[0].length), '\\x1b[1;34m', c[1])\n  }\n})\n\nconsole.log('\\x1b[0m', count)\n"
  },
  {
    "path": "test/spec/alignment-empty.tsv",
    "content": "input\texpected\n\tnull\n#\tnull\n//\tnull\n/**/\tnull\n"
  },
  {
    "path": "test/spec/alignment-errors.tsv",
    "content": "input\texpected\n}\tERROR:unexpected\n]\tERROR:unexpected\n:\tERROR:unexpected\n\"unterminated\tERROR:unterminated_string\n'unterminated\tERROR:unterminated_string\n`unterminated\tERROR:unterminated_string\n/*\tERROR:unterminated_comment\na:1,2\tERROR:unexpected\n"
  },
  {
    "path": "test/spec/alignment-map-merge.tsv",
    "content": "input\texpected\n{a:1,a:2}\t{\"a\":2}\n{a:{b:1},a:{c:2}}\t{\"a\":{\"b\":1,\"c\":2}}\n{a:{b:1,c:2},a:{c:3,e:4}}\t{\"a\":{\"b\":1,\"c\":3,\"e\":4}}\n{a:{b:1,x:1},a:{b:2,y:2},a:{b:3,z:3}}\t{\"a\":{\"b\":3,\"x\":1,\"y\":2,\"z\":3}}\n{a:[1],a:[2]}\t{\"a\":[2]}\n{a:1,b:2,a:3}\t{\"a\":3,\"b\":2}\n{a:{b:{c:1}},a:{b:{d:2}}}\t{\"a\":{\"b\":{\"c\":1,\"d\":2}}}\n"
  },
  {
    "path": "test/spec/alignment-number-text.tsv",
    "content": "input\texpected\n1a\t\"1a\"\n1abc\t\"1abc\"\n1+\t\"1+\"\n1-\t\"1-\"\n1-+\t\"1-+\"\n-\t\"-\"\n+\t\"+\"\na1\t\"a1\"\na1b\t\"a1b\"\n1.2.3\t\"1.2.3\"\n1e\t\"1e\"\n1e+\t\"1e+\"\n1e-\t\"1e-\"\n"
  },
  {
    "path": "test/spec/alignment-safe-key.tsv",
    "content": "input\texpected\n{a:1,__proto__:2}\t{\"a\":1,\"__proto__\":2}\n{a:1,constructor:2}\t{\"a\":1,\"constructor\":2}\n{__proto__:1}\t{\"__proto__\":1}\n{constructor:1}\t{\"constructor\":1}\n{a:1,b:2}\t{\"a\":1,\"b\":2}\n{__proto__:1,a:2}\t{\"__proto__\":1,\"a\":2}\n{constructor:1,a:2}\t{\"constructor\":1,\"a\":2}\n"
  },
  {
    "path": "test/spec/alignment-structure.tsv",
    "content": "input\texpected\n{\t{}\n[\t[]\n{a:1\t{\"a\":1}\n[1\t[1]\n{a:{b:1}\t{\"a\":{\"b\":1}}\n[[1\t[[1]]\n,\t[null]\na:\t{\"a\":null}\na:,b:\t{\"a\":null,\"b\":null}\n{a:}\t{\"a\":null}\n"
  },
  {
    "path": "test/spec/alignment-values.tsv",
    "content": "input\texpected\nNaN\t\"NaN\"\nInfinity\t\"Infinity\"\na:NaN\t{\"a\":\"NaN\"}\na:Infinity\t{\"a\":\"Infinity\"}\n[NaN]\t[\"NaN\"]\n[Infinity]\t[\"Infinity\"]\n{a:NaN,b:Infinity}\t{\"a\":\"NaN\",\"b\":\"Infinity\"}\nNaN,Infinity\t[\"NaN\",\"Infinity\"]\n"
  },
  {
    "path": "test/spec/comma-implicit-comma.tsv",
    "content": "input\texpected\n[0,1]\t[0,1]\n[0,null]\t[0,null]\n{a:0,b:null}\t{\"a\":0,\"b\":null}\n{a:1,b:2}\t{\"a\":1,\"b\":2}\n[1,2]\t[1,2]\n{a:1,\\nb:2}\t{\"a\":1,\"b\":2}\n[1,\\n2]\t[1,2]\na:1,b:2\t{\"a\":1,\"b\":2}\n1,2\t[1,2]\n1,2,3\t[1,2,3]\na:1,\\nb:2\t{\"a\":1,\"b\":2}\n1,\\n2\t[1,2]\n{a:1\\nb:2}\t{\"a\":1,\"b\":2}\n[1\\n2]\t[1,2]\na:1\\nb:2\t{\"a\":1,\"b\":2}\n1\\n2\t[1,2]\na\\nb\t[\"a\",\"b\"]\n1\\n2\\n3\t[1,2,3]\na\\nb\\nc\t[\"a\",\"b\",\"c\"]\ntrue\\nfalse\\nnull\t[true,false,null]\n"
  },
  {
    "path": "test/spec/comma-optional-comma.tsv",
    "content": "input\texpected\n[,]\t[null]\n[,,]\t[null,null]\n[,,,]\t[null,null,null]\n[,,,,]\t[null,null,null,null]\n[,,,,,]\t[null,null,null,null,null]\n[1,]\t[1]\n[1,,]\t[1,null]\n[1,,,]\t[1,null,null]\n[1,,,,]\t[1,null,null,null]\n[1,,,,,]\t[1,null,null,null,null]\n[,1]\t[null,1]\n[,1,]\t[null,1]\n[,1,,]\t[null,1,null]\n[,1,,,]\t[null,1,null,null]\n[,1,,,,]\t[null,1,null,null,null]\n[,1,,,,,]\t[null,1,null,null,null,null]\n{,}\t{}\n{,,}\t{}\n{,,,}\t{}\n{,,,,}\t{}\n{,,,,,}\t{}\n{a:1,}\t{\"a\":1}\n{a:1,,}\t{\"a\":1}\n{a:1,,,}\t{\"a\":1}\n{a:1,,,,}\t{\"a\":1}\n{a:1,,,,,}\t{\"a\":1}\n{,a:1}\t{\"a\":1}\n{,a:1,}\t{\"a\":1}\n{,a:1,,}\t{\"a\":1}\n{,a:1,,,}\t{\"a\":1}\n{,a:1,,,,}\t{\"a\":1}\n[1\\n2]\t[1,2]\n{a:1},\t[{\"a\":1}]\na:1,\t{\"a\":1}\na:b:1,\t{\"a\":{\"b\":1}}\na:1 b:2\t{\"a\":1,\"b\":2}\na:b:1 a:c:2\t{\"a\":{\"b\":1,\"c\":2}}\n{a:1\\nb:2}\t{\"a\":1,\"b\":2}\n{,a:1}\t{\"a\":1}\n{a:1,}\t{\"a\":1}\n{,a:1,}\t{\"a\":1}\n{a:1,b:2,}\t{\"a\":1,\"b\":2}\n[{a:1},]\t[{\"a\":1}]\n[{a:1},{b:2}]\t[{\"a\":1},{\"b\":2}]\n[[a],]\t[[\"a\"]]\n[[a],[b],]\t[[\"a\"],[\"b\"]]\n[[a],[b],[c],]\t[[\"a\"],[\"b\"],[\"c\"]]\n[[a]]\t[[\"a\"]]\n[[a][b]]\t[[\"a\"],[\"b\"]]\n[[a][b][c]]\t[[\"a\"],[\"b\"],[\"c\"]]\n[[0],]\t[[0]]\n[[0],[1],]\t[[0],[1]]\n[[0],[1],[2],]\t[[0],[1],[2]]\n[[0]]\t[[0]]\n[[0][1]]\t[[0],[1]]\n[[0][1][2]]\t[[0],[1],[2]]\n"
  },
  {
    "path": "test/spec/exclude-comma-errors.tsv",
    "content": "input\texpected\n{\"a\":1,}\tERROR:unexpected\n"
  },
  {
    "path": "test/spec/exclude-comma.tsv",
    "content": "input\texpected\n[1,2,3]\t[1,2,3]\n[1,2,]\t[1,2]\n{\"a\":1,\"b\":2}\t{\"a\":1,\"b\":2}\na:1,b:2\t{\"a\":1,\"b\":2}\n[1,2,3,]\t[1,2,3]\n"
  },
  {
    "path": "test/spec/exclude-strict-json-errors.tsv",
    "content": "input\texpected\n\"unterminated\tERROR:unterminated_string\n{\"a\":1,}\tERROR:unexpected\n[1,2,]\tERROR:unexpected\na:1\tERROR:unexpected\n"
  },
  {
    "path": "test/spec/exclude-strict-json.tsv",
    "content": "input\texpected\n{\"a\":1}\t{\"a\":1}\n{\"a\":\"b\"}\t{\"a\":\"b\"}\n{\"a\":true}\t{\"a\":true}\n{\"a\":false}\t{\"a\":false}\n{\"a\":null}\t{\"a\":null}\n{\"a\":1,\"b\":2}\t{\"a\":1,\"b\":2}\n{\"a\":\"hello world\"}\t{\"a\":\"hello world\"}\n{\"a\":{\"b\":1}}\t{\"a\":{\"b\":1}}\n{\"a\":{\"b\":{\"c\":\"d\"}}}\t{\"a\":{\"b\":{\"c\":\"d\"}}}\n{\"a\":[1,2,3]}\t{\"a\":[1,2,3]}\n[1,2,3]\t[1,2,3]\n[\"a\",\"b\",\"c\"]\t[\"a\",\"b\",\"c\"]\n[{\"a\":1},{\"b\":2}]\t[{\"a\":1},{\"b\":2}]\n[[1,2],[3,4]]\t[[1,2],[3,4]]\n{}\t{}\n[]\t[]\n42\t42\n\"hello\"\t\"hello\"\ntrue\ttrue\nfalse\tfalse\nnull\tnull\n3.14\t3.14\n-1\t-1\n0\t0\n{\"a\":\"{}[]:,\"}\t{\"a\":\"{}[]:,\"}\n{\"a\":\"hello:world\"}\t{\"a\":\"hello:world\"}\n{\"a\":\"hello,world\"}\t{\"a\":\"hello,world\"}\n{\"a\":\"\"}\t{\"a\":\"\"}\n[1,\"two\",true,null]\t[1,\"two\",true,null]\n"
  },
  {
    "path": "test/spec/feature-comment-suffix-block.tsv",
    "content": "input\texpected\n[1,/* end */2]\t[1,2]\n[1,/* suf !!2]\t[1,2]\n{a:/* x */ 1, b:2}\t{\"a\":1,\"b\":2}\n{a:/* x !! 1, b:2}\t{\"a\":1,\"b\":2}\n{a:/* body\\nlines !!1}\t{\"a\":1}\n{a:/* ok */1}\t{\"a\":1}\n"
  },
  {
    "path": "test/spec/feature-comment-suffix-line.tsv",
    "content": "input\texpected\n[1,# note @@ 2]\t[1,2]\n{a:# note @@ b}\t{\"a\":\"b\"}\n{a:1,# x@@b:2}\t{\"a\":1,\"b\":2}\n{a:1,# just a comment\\nb:2}\t{\"a\":1,\"b\":2}\n{a:1}\t{\"a\":1}\n[1,# one@@2,# two@@3]\t[1,2,3]\n"
  },
  {
    "path": "test/spec/feature-debug-cases.tsv",
    "content": "input\texpected\n1\t1\ntrue\ttrue\nx\t\"x\"\n\"y\"\t\"y\"\n{a:1}\t{\"a\":1}\n{a:1,b:2}\t{\"a\":1,\"b\":2}\n{a:1,b:2,c:3}\t{\"a\":1,\"b\":2,\"c\":3}\n{a:{b:2}}\t{\"a\":{\"b\":2}}\n{a:{b:2},c:3}\t{\"a\":{\"b\":2},\"c\":3}\n{a:{b:2,c:3}}\t{\"a\":{\"b\":2,\"c\":3}}\n{a:{b:{c:3}}\t{\"a\":{\"b\":{\"c\":3}}}\na:1\t{\"a\":1}\na:1,b:2\t{\"a\":1,\"b\":2}\na:1,b:2,c:3\t{\"a\":1,\"b\":2,\"c\":3}\na:{b:2}\t{\"a\":{\"b\":2}}\na:{b:2},c:3\t{\"a\":{\"b\":2},\"c\":3}\na:{b:2,c:3}\t{\"a\":{\"b\":2,\"c\":3}}\na:{b:{c:3}\t{\"a\":{\"b\":{\"c\":3}}}\n{a:1,x:0}\t{\"a\":1,\"x\":0}\n{a:1,b:2,x:0}\t{\"a\":1,\"b\":2,\"x\":0}\n{a:{b:2,x:0},x:0}\t{\"a\":{\"b\":2,\"x\":0},\"x\":0}\n{a:{b:2,x:0},c:3,x:0}\t{\"a\":{\"b\":2,\"x\":0},\"c\":3,\"x\":0}\n{a:{b:2,c:3,x:0},x:0}\t{\"a\":{\"b\":2,\"c\":3,\"x\":0},\"x\":0}\n{a:{b:{c:3,x:0},x:0}\t{\"a\":{\"b\":{\"c\":3,\"x\":0},\"x\":0}}\na:1,x:0\t{\"a\":1,\"x\":0}\na:1,b:2,x:0\t{\"a\":1,\"b\":2,\"x\":0}\na:{b:2,x:0},x:0\t{\"a\":{\"b\":2,\"x\":0},\"x\":0}\na:{b:2,x:0},c:3,x:0\t{\"a\":{\"b\":2,\"x\":0},\"c\":3,\"x\":0}\na:{b:2,c:3,x:0},x:0\t{\"a\":{\"b\":2,\"c\":3,\"x\":0},\"x\":0}\na:{b:{c:3,x:0},x:0\t{\"a\":{\"b\":{\"c\":3,\"x\":0},\"x\":0}}\n{a:b:2}\t{\"a\":{\"b\":2}}\n{a:b:c:3}\t{\"a\":{\"b\":{\"c\":3}}}\n{a:b:2,c:3}\t{\"a\":{\"b\":2},\"c\":3}\n{a:1,b:c:3}\t{\"a\":1,\"b\":{\"c\":3}}\n{a:b:c:3,d:4}\t{\"a\":{\"b\":{\"c\":3}},\"d\":4}\n{a:1,b:c:d:4}\t{\"a\":1,\"b\":{\"c\":{\"d\":4}}}\n{a:b:2,c:d:4}\t{\"a\":{\"b\":2},\"c\":{\"d\":4}}\n{a:b:c:3,d:e:f:6}\t{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}\na:b:2\t{\"a\":{\"b\":2}}\na:b:c:3\t{\"a\":{\"b\":{\"c\":3}}}\na:b:2,c:3\t{\"a\":{\"b\":2},\"c\":3}\na:1,b:c:3\t{\"a\":1,\"b\":{\"c\":3}}\na:b:c:3,d:4\t{\"a\":{\"b\":{\"c\":3}},\"d\":4}\na:1,b:c:d:4\t{\"a\":1,\"b\":{\"c\":{\"d\":4}}}\na:b:2,c:d:4\t{\"a\":{\"b\":2},\"c\":{\"d\":4}}\na:b:c:3,d:e:f:6\t{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}\n{x:{a:b:2}}\t{\"x\":{\"a\":{\"b\":2}}}\n{x:{a:b:c:3}}\t{\"x\":{\"a\":{\"b\":{\"c\":3}}}}\n{x:{a:b:2,c:3}}\t{\"x\":{\"a\":{\"b\":2},\"c\":3}}\n{x:{a:1,b:c:3}}\t{\"x\":{\"a\":1,\"b\":{\"c\":3}}}\n{x:{a:b:c:3,d:4}}\t{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}\n{x:{a:1,b:c:d:4}}\t{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}\n{x:{a:b:2,c:d:4}}\t{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}\n{x:{a:b:c:3,d:e:f:6}}\t{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}\nx:{a:b:2}\t{\"x\":{\"a\":{\"b\":2}}}\nx:{a:b:c:3}\t{\"x\":{\"a\":{\"b\":{\"c\":3}}}}\nx:{a:b:2,c:3}\t{\"x\":{\"a\":{\"b\":2},\"c\":3}}\nx:{a:1,b:c:3}\t{\"x\":{\"a\":1,\"b\":{\"c\":3}}}\nx:{a:b:c:3,d:4}\t{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}\nx:{a:1,b:c:d:4}\t{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}\nx:{a:b:2,c:d:4}\t{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}\nx:{a:b:c:3,d:e:f:6}\t{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}\n{y:{x:{a:b:2}}}\t{\"y\":{\"x\":{\"a\":{\"b\":2}}}}\n{y:{x:{a:b:c:3}}}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}}}\n{y:{x:{a:b:2,c:3}}}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}}}\n{y:{x:{a:1,b:c:3}}}\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}}}\n{y:{x:{a:b:c:3,d:4}}}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}}\n{y:{x:{a:1,b:c:d:4}}}\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}}\n{y:{x:{a:b:2,c:d:4}}}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}}\n{y:{x:{a:b:c:3,d:e:f:6}}}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}}\ny:{x:{a:b:2}}\t{\"y\":{\"x\":{\"a\":{\"b\":2}}}}\ny:{x:{a:b:c:3}}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}}}\ny:{x:{a:b:2,c:3}}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}}}\ny:{x:{a:1,b:c:3}}\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}}}\ny:{x:{a:b:c:3,d:4}}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}}}\ny:{x:{a:1,b:c:d:4}}\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}}}\ny:{x:{a:b:2,c:d:4}}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}}}\ny:{x:{a:b:c:3,d:e:f:6}}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}}}\n{y:{a:b:2},z:0}\t{\"y\":{\"a\":{\"b\":2}},\"z\":0}\n{y:{x:{a:b:2}},z:0}\t{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":0}\n{y:{x:{a:b:c:3}},z:0}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}},\"z\":0}\n{y:{x:{a:b:2,c:3}},z:0}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}},\"z\":0}\n{y:{x:{a:1,b:c:3}},z:0}\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}},\"z\":0}\n{y:{x:{a:b:c:3,d:4}},z:0}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}},\"z\":0}\n{y:{x:{a:1,b:c:d:4}},z:0}\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}},\"z\":0}\n{y:{x:{a:b:2,c:d:4}},z:0}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}},\"z\":0}\n{y:{x:{a:b:c:3,d:e:f:6}},z:0}\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}},\"z\":0}\ny:{x:{a:b:2}},z:0\t{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":0}\ny:{x:{a:b:c:3}},z:0\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}}}},\"z\":0}\ny:{x:{a:b:2,c:3}},z:0\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":3}},\"z\":0}\ny:{x:{a:1,b:c:3}},z:0\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":3}}},\"z\":0}\ny:{x:{a:b:c:3,d:4}},z:0\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":4}},\"z\":0}\ny:{x:{a:1,b:c:d:4}},z:0\t{\"y\":{\"x\":{\"a\":1,\"b\":{\"c\":{\"d\":4}}}},\"z\":0}\ny:{x:{a:b:2,c:d:4}},z:0\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":4}}},\"z\":0}\ny:{x:{a:b:c:3,d:e:f:6}},z:0\t{\"y\":{\"x\":{\"a\":{\"b\":{\"c\":3}},\"d\":{\"e\":{\"f\":6}}}},\"z\":0}\n{y:{x:{a:b:2}},z:k:0}\t{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":{\"k\":0}}\n{y:{x:{a:b:2,c:d:e:5,f:g:7}},z:k:{m:n:0,r:11},s:22}\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":{\"e\":5}},\"f\":{\"g\":7}}},\"z\":{\"k\":{\"m\":{\"n\":0},\"r\":11}},\"s\":22}\ny:{x:{a:b:2}},z:k:0\t{\"y\":{\"x\":{\"a\":{\"b\":2}}},\"z\":{\"k\":0}}\ny:{x:{a:b:2,c:d:e:5,f:g:7}},z:k:{m:n:0,r:11},s:22\t{\"y\":{\"x\":{\"a\":{\"b\":2},\"c\":{\"d\":{\"e\":5}},\"f\":{\"g\":7}}},\"z\":{\"k\":{\"m\":{\"n\":0},\"r\":11}},\"s\":22}\n{a:1 b:2}\t{\"a\":1,\"b\":2}\na:1 b:2\t{\"a\":1,\"b\":2}\n{a:1 b:2 c:3}\t{\"a\":1,\"b\":2,\"c\":3}\na:1 b:2 c:3\t{\"a\":1,\"b\":2,\"c\":3}\n{a:b:2 c:3}\t{\"a\":{\"b\":2},\"c\":3}\n{a:b:2 `c`:3}\t{\"a\":{\"b\":2},\"c\":3}\n{a:b:2 99:3}\t{\"99\":3,\"a\":{\"b\":2}}\n{a:b:2 true:3}\t{\"a\":{\"b\":2},\"true\":3}\na:b:2 c:3\t{\"a\":{\"b\":2},\"c\":3}\na:b:2 `c`:3\t{\"a\":{\"b\":2},\"c\":3}\na:b:2 99:3\t{\"99\":3,\"a\":{\"b\":2}}\na:b:2 true:3\t{\"a\":{\"b\":2},\"true\":3}\n{a:{b:c:3} d:4}\t{\"a\":{\"b\":{\"c\":3}},\"d\":4}\na:{b:c:3} d:4\t{\"a\":{\"b\":{\"c\":3}},\"d\":4}\n[a]\t[\"a\"]\n[a,b]\t[\"a\",\"b\"]\n[a]\t[\"a\"]\n[a,[b]]\t[\"a\",[\"b\"]]\n[a b]\t[\"a\",\"b\"]\n[a [b]]\t[\"a\",[\"b\"]]\n[a {b:2}]\t[\"a\",{\"b\":2}]\n[a,b,]\t[\"a\",\"b\"]\n{a:1,b:2,}\t{\"a\":1,\"b\":2}\na,b\t[\"a\",\"b\"]\n{}\t{}\n[]\t[]\n[,]\t[null]\n[,1]\t[null,1]\n[,,1]\t[null,null,1]\n[2,]\t[2]\n[2,,1]\t[2,null,1]\n"
  },
  {
    "path": "test/spec/feature-implicit-map.tsv",
    "content": "input\texpected\na:1\t{\"a\":1}\na:1,b:2\t{\"a\":1,\"b\":2}\n{a:b:1}\t{\"a\":{\"b\":1}}\n{a:b:1,a:c:2}\t{\"a\":{\"b\":1,\"c\":2}}\n{a:b:1,a:c:2,a:d:3}\t{\"a\":{\"b\":1,\"c\":2,\"d\":3}}\na:b:1\t{\"a\":{\"b\":1}}\na:b:1,a:c:2\t{\"a\":{\"b\":1,\"c\":2}}\na:b:1,a:c:2,a:d:3\t{\"a\":{\"b\":1,\"c\":2,\"d\":3}}\na:b:c:1\t{\"a\":{\"b\":{\"c\":1}}}\na:b:1,d:2\t{\"a\":{\"b\":1},\"d\":2}\na:b:c:1,d:2\t{\"a\":{\"b\":{\"c\":1}},\"d\":2}\n{a:b:1}\t{\"a\":{\"b\":1}}\na:{b:c:1}\t{\"a\":{\"b\":{\"c\":1}}}\n{a:,b:\t{\"a\":null,\"b\":null}\na:,b:\t{\"a\":null,\"b\":null}\n"
  },
  {
    "path": "test/spec/feature-implicit-object.tsv",
    "content": "input\texpected\na:1\t{\"a\":1}\na:1,b:2\t{\"a\":1,\"b\":2}\na:1,b:2,c:3\t{\"a\":1,\"b\":2,\"c\":3}\na:1,b:2,c:3,d:4\t{\"a\":1,\"b\":2,\"c\":3,\"d\":4}\na:1,b:2,c:3,d:4,e:5\t{\"a\":1,\"b\":2,\"c\":3,\"d\":4,\"e\":5}\na:A\t{\"a\":\"A\"}\na:A,b:true\t{\"a\":\"A\",\"b\":true}\na:A,b:true,c:null\t{\"a\":\"A\",\"b\":true,\"c\":null}\na:A,b:true,c:null,d:a\t{\"a\":\"A\",\"b\":true,\"c\":null,\"d\":\"a\"}\na:A,b:true,c:null,d:a,e:0xAA\t{\"a\":\"A\",\"b\":true,\"c\":null,\"d\":\"a\",\"e\":170}\na:{}\t{\"a\":{}}\nx:1,a:{}\t{\"x\":1,\"a\":{}}\nx:1,a:{},y:2\t{\"x\":1,\"a\":{},\"y\":2}\na:{},y:2\t{\"a\":{},\"y\":2}\na:{},\t{\"a\":{}}\nx:1,a:{},\t{\"x\":1,\"a\":{}}\nx:1,a:{},y:2,\t{\"x\":1,\"a\":{},\"y\":2}\na:{},y:2,\t{\"a\":{},\"y\":2}\na:{},b:{}\t{\"a\":{},\"b\":{}}\nx:1,a:{},b:{}\t{\"x\":1,\"a\":{},\"b\":{}}\nx:1,a:{},b:{},y:2\t{\"x\":1,\"a\":{},\"b\":{},\"y\":2}\na:{},b:{},y:2\t{\"a\":{},\"b\":{},\"y\":2}\na:{},b:{},c:{}\t{\"a\":{},\"b\":{},\"c\":{}}\nx:1,a:{},b:{},c:{}\t{\"x\":1,\"a\":{},\"b\":{},\"c\":{}}\nx:1,a:{},b:{},c:{},y:2\t{\"x\":1,\"a\":{},\"b\":{},\"c\":{},\"y\":2}\na:{},b:{},c:{},y:2\t{\"a\":{},\"b\":{},\"c\":{},\"y\":2}\na:{},b:{},\t{\"a\":{},\"b\":{}}\nx:1,a:{},b:{},\t{\"x\":1,\"a\":{},\"b\":{}}\nx:1,a:{},b:{},y:2,\t{\"x\":1,\"a\":{},\"b\":{},\"y\":2}\na:{},b:{},y:2,\t{\"a\":{},\"b\":{},\"y\":2}\na:{},b:{},c:{},\t{\"a\":{},\"b\":{},\"c\":{}}\nx:1,a:{},b:{},c:{},\t{\"x\":1,\"a\":{},\"b\":{},\"c\":{}}\nx:1,a:{},b:{},c:{},y:2,\t{\"x\":1,\"a\":{},\"b\":{},\"c\":{},\"y\":2}\na:{},b:{},c:{},y:2,\t{\"a\":{},\"b\":{},\"c\":{},\"y\":2}\na:[]\t{\"a\":[]}\nx:1,a:[]\t{\"x\":1,\"a\":[]}\nx:1,a:[],y:2\t{\"x\":1,\"a\":[],\"y\":2}\na:[],y:2\t{\"a\":[],\"y\":2}\na:[],\t{\"a\":[]}\nx:1,a:[],\t{\"x\":1,\"a\":[]}\nx:1,a:[],y:2,\t{\"x\":1,\"a\":[],\"y\":2}\na:[],y:2,\t{\"a\":[],\"y\":2}\na:[],b:[]\t{\"a\":[],\"b\":[]}\nx:1,a:[],b:[]\t{\"x\":1,\"a\":[],\"b\":[]}\nx:1,a:[],b:[],y:2\t{\"x\":1,\"a\":[],\"b\":[],\"y\":2}\na:[],b:[],y:2\t{\"a\":[],\"b\":[],\"y\":2}\na:[],b:[],c:[]\t{\"a\":[],\"b\":[],\"c\":[]}\nx:1,a:[],b:[],c:[]\t{\"x\":1,\"a\":[],\"b\":[],\"c\":[]}\nx:1,a:[],b:[],c:[],y:2\t{\"x\":1,\"a\":[],\"b\":[],\"c\":[],\"y\":2}\na:[],b:[],c:[],y:2\t{\"a\":[],\"b\":[],\"c\":[],\"y\":2}\na:[],b:[],\t{\"a\":[],\"b\":[]}\nx:1,a:[],b:[],\t{\"x\":1,\"a\":[],\"b\":[]}\nx:1,a:[],b:[],y:2,\t{\"x\":1,\"a\":[],\"b\":[],\"y\":2}\na:[],b:[],y:2,\t{\"a\":[],\"b\":[],\"y\":2}\na:[],b:[],c:[],\t{\"a\":[],\"b\":[],\"c\":[]}\nx:1,a:[],b:[],c:[],\t{\"x\":1,\"a\":[],\"b\":[],\"c\":[]}\nx:1,a:[],b:[],c:[],y:2,\t{\"x\":1,\"a\":[],\"b\":[],\"c\":[],\"y\":2}\na:[],b:[],c:[],y:2,\t{\"a\":[],\"b\":[],\"c\":[],\"y\":2}\na:{b:1}\t{\"a\":{\"b\":1}}\na:{b:{c:1}}\t{\"a\":{\"b\":{\"c\":1}}}\na:{b:1},d:{e:2}\t{\"a\":{\"b\":1},\"d\":{\"e\":2}}\n"
  },
  {
    "path": "test/spec/feature-list-child-deep.tsv",
    "content": "input\texpected_array\texpected_child\n[[:1]]\t[[]]\n[[:1],[:2]]\t[[],[]]\n[[[:1]]]\t[[[]]]\n[[:1],:2]\t[[]]\t2\n[:[:1]]\t[]\t[]\n[1,[:2,:3],4,:5]\t[1,[],4]\t5\n[1,:[:2,3]]\t[1]\t[3]\n[[[:1],:2],:3]\t[[[]]]\t3\n[:[:[:1]]]\t[]\t[]\n[[1,2],[:3]]\t[[1,2],[]]\n[[:{a:1}],[:{b:2}],:3]\t[[],[]]\t3\n[1,[2,[3,:4]]]\t[1,[2,[3]]]\n[[:1],[2,3],[:4]]\t[[],[2,3],[]]\n[[:1],:2,[:3],:4]\t[[],[]]\t4\n[:1,[:2]]\t[[]]\t1\n[[:1],2,:3]\t[[],2]\t3\n[,[:{a:1}],:2]\t[null,[]]\t2\n[:[:1,:2]]\t[]\t[]\n[:{a:1},:[:2]]\t[]\t[]\n"
  },
  {
    "path": "test/spec/feature-list-child-pair-deep.tsv",
    "content": "input\texpected_array\texpected_child\n[[a:1,:2]]\t[[{\"a\":1}]]\n[a:1,[b:2,:3],:4]\t[{\"a\":1},[{\"b\":2}]]\t4\n[[[:5],:6],:7]\t[[[]]]\t7\n[a:1,[b:2,c:3,:4]]\t[{\"a\":1},[{\"b\":2},{\"c\":3}]]\n[[a:1,:2],[b:3,:4]]\t[[{\"a\":1}],[{\"b\":3}]]\n[:1,[a:2]]\t[[{\"a\":2}]]\t1\n[a:1,[:2]]\t[{\"a\":1},[]]\n"
  },
  {
    "path": "test/spec/feature-list-child-pair.tsv",
    "content": "input\texpected_array\texpected_child\n[:1]\t[]\t1\n[a:1]\t[{\"a\":1}]\n[a:1,:2]\t[{\"a\":1}]\t2\n[:1,a:2]\t[{\"a\":2}]\t1\n[a:1,:2,b:3]\t[{\"a\":1},{\"b\":3}]\t2\n[:1,a:2,b:3]\t[{\"a\":2},{\"b\":3}]\t1\n[a:1,b:2,:3]\t[{\"a\":1},{\"b\":2}]\t3\n[a:1,:2,:3]\t[{\"a\":1}]\t3\n[:1,:2,a:3]\t[{\"a\":3}]\t2\n[]\t[]\n[1,2,3]\t[1,2,3]\n[a:1,2,:3]\t[{\"a\":1},2]\t3\n[:1,2,a:3]\t[2,{\"a\":3}]\t1\n[{a:1},:2]\t[{\"a\":1}]\t2\n[:1,{a:2}]\t[{\"a\":2}]\t1\n[:{a:1}]\t[]\t{\"a\":1}\n[a:{b:1},:2]\t[{\"a\":{\"b\":1}}]\t2\n"
  },
  {
    "path": "test/spec/feature-list-child.tsv",
    "content": "input\texpected_array\texpected_child\n[:1]\t[]\t1\n[:a]\t[]\t\"a\"\n[:\"hello\"]\t[]\t\"hello\"\n[:true]\t[]\ttrue\n[:false]\t[]\tfalse\n[:null]\t[]\tnull\n[:]\t[]\tnull\n[:{a:1}]\t[]\t{\"a\":1}\n[:{a:1,b:2}]\t[]\t{\"a\":1,\"b\":2}\n[:{}]\t[]\t{}\n[:{a:{b:1}}]\t[]\t{\"a\":{\"b\":1}}\n[:[1,2]]\t[]\t[1,2]\n[:[]]\t[]\t[]\n[:[[1],[2]]]\t[]\t[[1],[2]]\n[1,:2]\t[1]\t2\n[:1,2]\t[2]\t1\n[1,:2,3]\t[1,3]\t2\n[1,2,:3]\t[1,2]\t3\n[:1,2,3]\t[2,3]\t1\n[:1,:2]\t[]\t2\n[:1,:2,:3]\t[]\t3\n[:{a:1},:{b:2}]\t[]\t{\"a\":1,\"b\":2}\n[:{a:1},:{b:2},:{c:3}]\t[]\t{\"a\":1,\"b\":2,\"c\":3}\n[:{a:{x:1}},:{a:{y:2}}]\t[]\t{\"a\":{\"x\":1,\"y\":2}}\n[:{a:1},:{a:2}]\t[]\t{\"a\":2}\n[a:1,:2]\t[]\t2\n[:a:b]\t[]\t{\"a\":\"b\"}\n[:a:b:1]\t[]\t{\"a\":{\"b\":1}}\n[:1,]\t[]\t1\n[1,:2,]\t[1]\t2\n[:1,:2,]\t[]\t2\n[,:1]\t[null]\t1\n[,,:1]\t[null,null]\t1\n[]\t[]\n[1,2,3]\t[1,2,3]\n[{a:1},:2]\t[{\"a\":1}]\t2\n[:1,{a:2}]\t[{\"a\":2}]\t1\n[[1,2],:3]\t[[1,2]]\t3\n[:1,[2,3]]\t[[2,3]]\t1\n[{a:1},:2,{b:3}]\t[{\"a\":1},{\"b\":3}]\t2\n[:,1]\t[1]\tnull\n[1,:]\t[1]\tnull\n[:1,:2,3,:4]\t[3]\t4\n[1,:2,3,:4,5]\t[1,3,5]\t4\n"
  },
  {
    "path": "test/spec/feature-list-pair.tsv",
    "content": "input\texpected\n[a:1]\t[{\"a\":1}]\n[a:1,b:2]\t[{\"a\":1},{\"b\":2}]\n[a:1,b:2,c:3]\t[{\"a\":1},{\"b\":2},{\"c\":3}]\n[a:1,2,b:3]\t[{\"a\":1},2,{\"b\":3}]\n[1,a:2,3]\t[1,{\"a\":2},3]\n[1,2,a:3]\t[1,2,{\"a\":3}]\n[a:1,2,3]\t[{\"a\":1},2,3]\n[a:{b:1}]\t[{\"a\":{\"b\":1}}]\n[a:{b:1,c:2}]\t[{\"a\":{\"b\":1,\"c\":2}}]\n[a:{},b:1]\t[{\"a\":{}},{\"b\":1}]\n[a:{b:{c:1}}]\t[{\"a\":{\"b\":{\"c\":1}}}]\n[a:{b:1,c:{d:2}}]\t[{\"a\":{\"b\":1,\"c\":{\"d\":2}}}]\n[a:{b:1},c:{d:2}]\t[{\"a\":{\"b\":1}},{\"c\":{\"d\":2}}]\n[{a:1},b:2]\t[{\"a\":1},{\"b\":2}]\n[a:1,{b:2}]\t[{\"a\":1},{\"b\":2}]\n[{a:1},{b:2}]\t[{\"a\":1},{\"b\":2}]\n[{a:1,b:2},c:3,{d:4}]\t[{\"a\":1,\"b\":2},{\"c\":3},{\"d\":4}]\n[a:[1,2]]\t[{\"a\":[1,2]}]\n[a:[1,2],b:[3,4]]\t[{\"a\":[1,2]},{\"b\":[3,4]}]\n[a:[],b:1]\t[{\"a\":[]},{\"b\":1}]\n[[1],a:2]\t[[1],{\"a\":2}]\n[a:1,[2,3]]\t[{\"a\":1},[2,3]]\n[a:[{b:1},{c:2}]]\t[{\"a\":[{\"b\":1},{\"c\":2}]}]\n[a:[b:1]]\t[{\"a\":[{\"b\":1}]}]\n[a:[b:1,c:2]]\t[{\"a\":[{\"b\":1},{\"c\":2}]}]\n[a:{b:[c:1]}]\t[{\"a\":{\"b\":[{\"c\":1}]}}]\n[a:b:1]\t[{\"a\":{\"b\":1}}]\n[a:b:c]\t[{\"a\":{\"b\":\"c\"}}]\n[a:b:c:1]\t[{\"a\":{\"b\":{\"c\":1}}}]\n[a:b:1,c:d:2]\t[{\"a\":{\"b\":1}},{\"c\":{\"d\":2}}]\n[a:]\t[{\"a\":null}]\n[a:,b:]\t[{\"a\":null},{\"b\":null}]\n[a:null]\t[{\"a\":null}]\n[a:\"hello\"]\t[{\"a\":\"hello\"}]\n[\"a\":1]\t[{\"a\":1}]\n[1:a]\t[{\"1\":\"a\"}]\n[a:true]\t[{\"a\":true}]\n[a:false]\t[{\"a\":false}]\n[a:1,a:2]\t[{\"a\":1},{\"a\":2}]\n[]\t[]\n[1,2,3]\t[1,2,3]\n[a:1,]\t[{\"a\":1}]\n[a:1,b:2,]\t[{\"a\":1},{\"b\":2}]\n[a:1 b:2]\t[{\"a\":1},{\"b\":2}]\n[a:1 2 b:3]\t[{\"a\":1},2,{\"b\":3}]\n[a:b:1 c:d:2]\t[{\"a\":{\"b\":1}},{\"c\":{\"d\":2}}]\n[a:{x:1} b:{y:2}]\t[{\"a\":{\"x\":1}},{\"b\":{\"y\":2}}]\n[,a:1]\t[null,{\"a\":1}]\n[a:1,,b:2]\t[{\"a\":1},null,{\"b\":2}]\n[a:[[1]]]\t[{\"a\":[[1]]}]\n[a:'hello world']\t[{\"a\":\"hello world\"}]\n"
  },
  {
    "path": "test/spec/feature-map-child-deep.tsv",
    "content": "input\texpected\n{a:[:1,2],:3}\t{\"a\":[2],\"child$\":3}\n[{:1,a:2}]\t[{\"child$\":1,\"a\":2}]\n{a:[{:1}]}\t{\"a\":[{\"child$\":1}]}\n{a:{b:[{:1},2]}}\t{\"a\":{\"b\":[{\"child$\":1},2]}}\n{:[1,2]}\t{\"child$\":[1,2]}\n[{:1},{:2}]\t[{\"child$\":1},{\"child$\":2}]\n{a:{:1},b:{:2}}\t{\"a\":{\"child$\":1},\"b\":{\"child$\":2}}\n{a:[{:1}],:2}\t{\"a\":[{\"child$\":1}],\"child$\":2}\n{:{a:1},b:2}\t{\"child$\":{\"a\":1},\"b\":2}\n{:1,a:[1,2],b:{:3,c:[3]}}\t{\"child$\":1,\"a\":[1,2],\"b\":{\"child$\":3,\"c\":[3]}}\n"
  },
  {
    "path": "test/spec/feature-map-child.tsv",
    "content": "input\texpected\n{:1}\t{\"child$\":1}\n{:1,a:2}\t{\"child$\":1,\"a\":2}\n{a:2,:1}\t{\"a\":2,\"child$\":1}\n{:1,:2}\t{\"child$\":2}\n{:1,a:2,b:3}\t{\"child$\":1,\"a\":2,\"b\":3}\n{a:1,:2,b:3}\t{\"a\":1,\"child$\":2,\"b\":3}\n{a:1,b:2,:3}\t{\"a\":1,\"b\":2,\"child$\":3}\n{:null}\t{\"child$\":null}\n{:true}\t{\"child$\":true}\n{:false}\t{\"child$\":false}\n{:\"hello\"}\t{\"child$\":\"hello\"}\n{:[1,2,3]}\t{\"child$\":[1,2,3]}\n{:{x:1}}\t{\"child$\":{\"x\":1}}\n{:{a:1},:{b:2}}\t{\"child$\":{\"a\":1,\"b\":2}}\n{:{a:{x:1}},:{a:{y:2}}}\t{\"child$\":{\"a\":{\"x\":1,\"y\":2}}}\n{:1,:2,:3}\t{\"child$\":3}\n{:0}\t{\"child$\":0}\n{:-1}\t{\"child$\":-1}\n{:3.14}\t{\"child$\":3.14}\n{:1,}\t{\"child$\":1}\n{,:1}\t{\"child$\":1}\n{ : 1 , a : 2 }\t{\"child$\":1,\"a\":2}\n{}\t{}\n{a:1}\t{\"a\":1}\na:1,:2\t{\"a\":1,\"child$\":2}\na:1,:2,b:3\t{\"a\":1,\"child$\":2,\"b\":3}\n{a:{:1}}\t{\"a\":{\"child$\":1}}\n{a:{:1,b:2}}\t{\"a\":{\"child$\":1,\"b\":2}}\n{:1,a:{:2}}\t{\"child$\":1,\"a\":{\"child$\":2}}\n{:1,a:{:2,b:{:3}}}\t{\"child$\":1,\"a\":{\"child$\":2,\"b\":{\"child$\":3}}}\n{a:{b:{:1}}}\t{\"a\":{\"b\":{\"child$\":1}}}\n"
  },
  {
    "path": "test/spec/feature-nested-space-pairs.tsv",
    "content": "input\texpected\n{a:1 b:2}\t{\"a\":1,\"b\":2}\n{a:1 b:2 c:3}\t{\"a\":1,\"b\":2,\"c\":3}\n[{a:1 b:2}]\t[{\"a\":1,\"b\":2}]\n{x:[{a:1 b:2}]}\t{\"x\":[{\"a\":1,\"b\":2}]}\nx:[{a:1 b:2}]\t{\"x\":[{\"a\":1,\"b\":2}]}\n{x:{a:1 b:2}}\t{\"x\":{\"a\":1,\"b\":2}}\n{x:[{a:1 b:2}]\t{\"x\":[{\"a\":1,\"b\":2}]}\n{x:[{a:1 b:2},{c:3 d:4}]}\t{\"x\":[{\"a\":1,\"b\":2},{\"c\":3,\"d\":4}]}\n{x:{y:{a:1 b:2}}}\t{\"x\":{\"y\":{\"a\":1,\"b\":2}}}\n{x:[{a:1 b:2 c:3}]}\t{\"x\":[{\"a\":1,\"b\":2,\"c\":3}]}\n{x:[{a:1 b:2},3]}\t{\"x\":[{\"a\":1,\"b\":2},3]}\n{x:[1,{a:1 b:2}]}\t{\"x\":[1,{\"a\":1,\"b\":2}]}\n{x:[1,{a:1 b:2},3]}\t{\"x\":[1,{\"a\":1,\"b\":2},3]}\na:{b:1 c:2}\t{\"a\":{\"b\":1,\"c\":2}}\na:[{b:1 c:2}]\t{\"a\":[{\"b\":1,\"c\":2}]}\na:[{b:1 c:2},{d:3 e:4}]\t{\"a\":[{\"b\":1,\"c\":2},{\"d\":3,\"e\":4}]}\n{a:[{b:1 c:2}],d:[{e:3 f:4}]}\t{\"a\":[{\"b\":1,\"c\":2}],\"d\":[{\"e\":3,\"f\":4}]}\n{a:{b:1 c:2},d:{e:3 f:4}}\t{\"a\":{\"b\":1,\"c\":2},\"d\":{\"e\":3,\"f\":4}}\n"
  },
  {
    "path": "test/spec/fv-arrays.tsv",
    "content": "input\texpected\n[]\t[]\n[1]\t[1]\n[1,2]\t[1,2]\n[ 1 , 2 ]\t[1,2]\n{a:[],b:[1],c:[1,2]}\t{\"a\":[],\"b\":[1],\"c\":[1,2]}\n{a: [ ] , b:[b], c:[ c , dd ]}\t{\"a\":[],\"b\":[\"b\"],\"c\":[\"c\",\"dd\"]}\n['a']\t[\"a\"]\n[\"a\"]\t[\"a\"]\n['a',\"b\"]\t[\"a\",\"b\"]\n[ 'a' , \"b\" ]\t[\"a\",\"b\"]\n"
  },
  {
    "path": "test/spec/fv-comma.tsv",
    "content": "input\texpected\na:1, b:2, \t{\"a\":1,\"b\":2}\na:1,\t{\"a\":1}\n,\t[null]\n,,\t[null,null]\n[a,]\t[\"a\"]\n[a,1,]\t[\"a\",1]\n[,a,1,]\t[null,\"a\",1]\n[,]\t[null]\n[,,]\t[null,null]\n"
  },
  {
    "path": "test/spec/fv-deep.tsv",
    "content": "input\texpected\n{a:[[{b:1}],{c:[{d:1}]}]}\t{\"a\":[[{\"b\":1}],{\"c\":[{\"d\":1}]}]}\n[{a:[[{b:1}],{c:[{d:1}]}]}]\t[{\"a\":[[{\"b\":1}],{\"c\":[{\"d\":1}]}]}]\n"
  },
  {
    "path": "test/spec/fv-drop-outs.tsv",
    "content": "input\texpected\na:0a\t{\"a\":\"0a\"}\na:-0a\t{\"a\":\"-0a\"}\na:0.a\t{\"a\":\"0.a\"}\na:0.0a\t{\"a\":\"0.0a\"}\na:-0.0a\t{\"a\":\"-0.0a\"}\n"
  },
  {
    "path": "test/spec/fv-numbers.tsv",
    "content": "input\texpected\nx:0,a:102,b:1.2,c:-3,d:-4.5,e:-10\t{\"x\":0,\"a\":102,\"b\":1.2,\"c\":-3,\"d\":-4.5,\"e\":-10}\nx:0,a:102,b:1.2,c:1e2,d:1.2e3,e:1e+2,f:1e-2,g:1.2e+3,h:1.2e-3,i:-1.2e+3,j:-1.2e-3\t{\"x\":0,\"a\":102,\"b\":1.2,\"c\":100,\"d\":1200,\"e\":100,\"f\":0.01,\"g\":1200,\"h\":0.0012,\"i\":-1200,\"j\":-0.0012}\nx:01,a:1a,b:10b,c:1e2e\t{\"x\":1,\"a\":\"1a\",\"b\":\"10b\",\"c\":\"1e2e\"}\n"
  },
  {
    "path": "test/spec/fv-subobj.tsv",
    "content": "input\texpected\na:{b:1},c:2\t{\"a\":{\"b\":1},\"c\":2}\na:{b:1}\t{\"a\":{\"b\":1}}\na:{b:{c:1}}\t{\"a\":{\"b\":{\"c\":1}}}\n"
  },
  {
    "path": "test/spec/fv-types.tsv",
    "content": "input\texpected\nt:{null:null,int:100,dec:9.9,t:true,f:false,qs:\"a\\\"a'a\",as:'a\"a\\'a'}\t{\"t\":{\"null\":null,\"int\":100,\"dec\":9.9,\"t\":true,\"f\":false,\"qs\":\"a\\\"a'a\",\"as\":\"a\\\"a'a\"}}\nnull:null,int:100,dec:9.9,t:true,f:false,qs:\"a\\\"a'a\",as:'a\"a\\'a'\t{\"null\":null,\"int\":100,\"dec\":9.9,\"t\":true,\"f\":false,\"qs\":\"a\\\"a'a\",\"as\":\"a\\\"a'a\"}\n"
  },
  {
    "path": "test/spec/fv-works.tsv",
    "content": "input\texpected\nfoo:1, bar:zed\t{\"foo\":1,\"bar\":\"zed\"}\nfoo-foo:1, bar:zed\t{\"foo-foo\":1,\"bar\":\"zed\"}\n\"foo-foo\":1, bar:zed\t{\"foo-foo\":1,\"bar\":\"zed\"}\n\"foo-1\":1, bar:zed\t{\"foo-1\":1,\"bar\":\"zed\"}\n\"foo-0\":1, bar:zed\t{\"foo-0\":1,\"bar\":\"zed\"}\n\"-foo-\":1, bar:zed\t{\"-foo-\":1,\"bar\":\"zed\"}\n\"-foo\":1, bar:zed\t{\"-foo\":1,\"bar\":\"zed\"}\n\"foo-bar-\":1, bar:zed\t{\"foo-bar-\":1,\"bar\":\"zed\"}\n\"foo-\":1, bar:zed\t{\"foo-\":1,\"bar\":\"zed\"}\n\"foo---foo\":1, bar:zed\t{\"foo---foo\":1,\"bar\":\"zed\"}\nfoo--foo:1, bar:zed\t{\"foo--foo\":1,\"bar\":\"zed\"}\n\"foo--1\":1, bar:zed\t{\"foo--1\":1,\"bar\":\"zed\"}\n\"foo---0\":1, bar:zed\t{\"foo---0\":1,\"bar\":\"zed\"}\n\"--foo--\":1, bar:zed\t{\"--foo--\":1,\"bar\":\"zed\"}\n\"--foo\":1, bar:zed\t{\"--foo\":1,\"bar\":\"zed\"}\n\"foo--bar-baz\":1, \"-bar\":zed\t{\"foo--bar-baz\":1,\"-bar\":\"zed\"}\n\"foo--\":1, bar:zed\t{\"foo--\":1,\"bar\":\"zed\"}\n{foo:\"bar\", arr:[0,0]}\t{\"foo\":\"bar\",\"arr\":[0,0]}\n'a':1,':':2, c : 3\t{\"a\":1,\":\":2,\"c\":3}\n"
  },
  {
    "path": "test/spec/happy.tsv",
    "content": "input\texpected\n{a:1}\t{\"a\":1}\n{a:1,b:2}\t{\"a\":1,\"b\":2}\na:1\t{\"a\":1}\na:1,b:2\t{\"a\":1,\"b\":2}\n{a:q}\t{\"a\":\"q\"}\n{\"a\":1}\t{\"a\":1}\na,\t[\"a\"]\na,1\t[\"a\",1]\n[a]\t[\"a\"]\n[a,1]\t[\"a\",1]\n[\"a\",1]\t[\"a\",1]\n"
  },
  {
    "path": "test/spec/include-json-errors.tsv",
    "content": "input\texpected\n\"unterminated\tERROR:unterminated_string\n{\"a\":1,}\tERROR:unexpected\n[1,2,]\tERROR:unexpected\na:1\tERROR:unexpected\n"
  },
  {
    "path": "test/spec/include-json.tsv",
    "content": "input\texpected\n{\"a\":1}\t{\"a\":1}\n{\"a\":\"b\"}\t{\"a\":\"b\"}\n{\"a\":true}\t{\"a\":true}\n{\"a\":false}\t{\"a\":false}\n{\"a\":null}\t{\"a\":null}\n{\"a\":1,\"b\":2}\t{\"a\":1,\"b\":2}\n{\"a\":\"hello world\"}\t{\"a\":\"hello world\"}\n{\"a\":{\"b\":1}}\t{\"a\":{\"b\":1}}\n{\"a\":{\"b\":{\"c\":\"d\"}}}\t{\"a\":{\"b\":{\"c\":\"d\"}}}\n{\"a\":[1,2,3]}\t{\"a\":[1,2,3]}\n[1,2,3]\t[1,2,3]\n[\"a\",\"b\",\"c\"]\t[\"a\",\"b\",\"c\"]\n[{\"a\":1},{\"b\":2}]\t[{\"a\":1},{\"b\":2}]\n[[1,2],[3,4]]\t[[1,2],[3,4]]\n{}\t{}\n[]\t[]\n42\t42\n\"hello\"\t\"hello\"\ntrue\ttrue\nfalse\tfalse\nnull\tnull\n3.14\t3.14\n-1\t-1\n0\t0\n{\"a\":\"{}[]:,\"}\t{\"a\":\"{}[]:,\"}\n{\"a\":\"hello:world\"}\t{\"a\":\"hello:world\"}\n{\"a\":\"hello,world\"}\t{\"a\":\"hello,world\"}\n{\"a\":\"\"}\t{\"a\":\"\"}\n[1,\"two\",true,null]\t[1,\"two\",true,null]\n"
  },
  {
    "path": "test/spec/jsonic-basic-array-tree.tsv",
    "content": "input\texpected\n[]\t[]\n[0]\t[0]\n[0,1]\t[0,1]\n[0,1,2]\t[0,1,2]\n[[]]\t[[]]\n[0,[]]\t[0,[]]\n[[],1]\t[[],1]\n[0,[],1]\t[0,[],1]\n[[],0,[],1]\t[[],0,[],1]\n[0,[],1,[]]\t[0,[],1,[]]\n[[],0,[],1,[]]\t[[],0,[],1,[]]\n[[2]]\t[[2]]\n[0,[2]]\t[0,[2]]\n[[2],1]\t[[2],1]\n[0,[2],1]\t[0,[2],1]\n[[2],0,[3],1]\t[[2],0,[3],1]\n[0,[3],1,[2]]\t[0,[3],1,[2]]\n[[2],0,[4],1,[3]]\t[[2],0,[4],1,[3]]\n[[2,9]]\t[[2,9]]\n[0,[2,9]]\t[0,[2,9]]\n[[2,9],1]\t[[2,9],1]\n[0,[2,9],1]\t[0,[2,9],1]\n[[2,9],0,[3,9],1]\t[[2,9],0,[3,9],1]\n[0,[3,9],1,[2,9]]\t[0,[3,9],1,[2,9]]\n[[2,9],0,[4,9],1,[3,9]]\t[[2,9],0,[4,9],1,[3,9]]\n[[[[]]]]\t[[[[]]]]\n[[[[0]]]]\t[[[[0]]]]\n[[[1,[0]]]]\t[[[1,[0]]]]\n[[[1,[0],2]]]\t[[[1,[0],2]]]\n[[3,[1,[0],2]]]\t[[3,[1,[0],2]]]\n[[3,[1,[0],2],4]]\t[[3,[1,[0],2],4]]\n[5,[3,[1,[0],2],4]]\t[5,[3,[1,[0],2],4]]\n[5,[3,[1,[0],2],4],6]\t[5,[3,[1,[0],2],4],6]\n"
  },
  {
    "path": "test/spec/jsonic-basic-json.tsv",
    "content": "input\texpected\n\"a\"\t\"a\"\n{\"a\":1}\t{\"a\":1}\n{\"a\":\"1\"}\t{\"a\":\"1\"}\n{\"a\":1,\"b\":\"2\"}\t{\"a\":1,\"b\":\"2\"}\n{\"a\":{\"b\":1}}\t{\"a\":{\"b\":1}}\n[1]\t[1]\n[1,\"2\"]\t[1,\"2\"]\n[1,[2]]\t[1,[2]]\n{\"a\":[1]}\t{\"a\":[1]}\n{\"a\":[1,{\"b\":2}]}\t{\"a\":[1,{\"b\":2}]}\n { \"a\" : 1 } \t{\"a\":1}\n [ 1 , \"2\" ] \t[1,\"2\"]\n { \"a\" : [ 1 ] }\t{\"a\":[1]}\n { \"a\" : [ 1 , { \"b\" : 2 } ] } \t{\"a\":[1,{\"b\":2}]}\n{\"a\":true,\"b\":false,\"c\":null}\t{\"a\":true,\"b\":false,\"c\":null}\n[true,false,null]\t[true,false,null]\n"
  },
  {
    "path": "test/spec/jsonic-basic-mixed-tree.tsv",
    "content": "input\texpected\n[{}]\t[{}]\n{a:[]}\t{\"a\":[]}\n[{a:[]}]\t[{\"a\":[]}]\n{a:[{}]}\t{\"a\":[{}]}\n[{a:[{}]}]\t[{\"a\":[{}]}]\n{a:[{b:[]}]}\t{\"a\":[{\"b\":[]}]}\n"
  },
  {
    "path": "test/spec/jsonic-basic-object-tree.tsv",
    "content": "input\texpected\n{}\t{}\n{a:{}}\t{\"a\":{}}\n{a:{b:{}}}\t{\"a\":{\"b\":{}}}\n{a:{b:{c:{}}}}\t{\"a\":{\"b\":{\"c\":{}}}}\n{a:1}\t{\"a\":1}\n{a:1,b:2}\t{\"a\":1,\"b\":2}\n{a:1,b:2,c:3}\t{\"a\":1,\"b\":2,\"c\":3}\n{a:{b:2}}\t{\"a\":{\"b\":2}}\n{a:{b:{c:2}}}\t{\"a\":{\"b\":{\"c\":2}}}\n{a:{b:{c:{d:2}}}}\t{\"a\":{\"b\":{\"c\":{\"d\":2}}}}\n{x:10,a:{b:2}}\t{\"x\":10,\"a\":{\"b\":2}}\n{x:10,a:{b:{c:2}}}\t{\"x\":10,\"a\":{\"b\":{\"c\":2}}}\n{x:10,a:{b:{c:{d:2}}}}\t{\"x\":10,\"a\":{\"b\":{\"c\":{\"d\":2}}}}\n{a:{b:2},y:20}\t{\"a\":{\"b\":2},\"y\":20}\n{a:{b:{c:2}},y:20}\t{\"a\":{\"b\":{\"c\":2}},\"y\":20}\n{a:{b:{c:{d:2}}},y:20}\t{\"a\":{\"b\":{\"c\":{\"d\":2}}},\"y\":20}\n{x:10,a:{b:2},y:20}\t{\"x\":10,\"a\":{\"b\":2},\"y\":20}\n{x:10,a:{b:{c:2}},y:20}\t{\"x\":10,\"a\":{\"b\":{\"c\":2}},\"y\":20}\n{x:10,a:{b:{c:{d:2}}},y:20}\t{\"x\":10,\"a\":{\"b\":{\"c\":{\"d\":2}}},\"y\":20}\n{a:{b:2,c:3}}\t{\"a\":{\"b\":2,\"c\":3}}\n{a:{b:2,c:3,d:4}}\t{\"a\":{\"b\":2,\"c\":3,\"d\":4}}\n{a:{b:{e:2},c:3,d:4}}\t{\"a\":{\"b\":{\"e\":2},\"c\":3,\"d\":4}}\n{a:{b:2,c:{e:3},d:4}}\t{\"a\":{\"b\":2,\"c\":{\"e\":3},\"d\":4}}\n{a:{b:2,c:3,d:{e:4}}}\t{\"a\":{\"b\":2,\"c\":3,\"d\":{\"e\":4}}}\n{a:{b:{c:2,d:3}}}\t{\"a\":{\"b\":{\"c\":2,\"d\":3}}}\n{a:{b:{c:2,d:3,e:4}}}\t{\"a\":{\"b\":{\"c\":2,\"d\":3,\"e\":4}}}\n{a:{b:{c:{f:2},d:3,e:4}}}\t{\"a\":{\"b\":{\"c\":{\"f\":2},\"d\":3,\"e\":4}}}\n{a:{b:{c:2,d:{f:3},e:4}}}\t{\"a\":{\"b\":{\"c\":2,\"d\":{\"f\":3},\"e\":4}}}\n{a:{b:{c:2,d:3,e:{f:4}}}}\t{\"a\":{\"b\":{\"c\":2,\"d\":3,\"e\":{\"f\":4}}}}\na:b:1\t{\"a\":{\"b\":1}}\na:b:c:1\t{\"a\":{\"b\":{\"c\":1}}}\na:b:1,c:2\t{\"a\":{\"b\":1},\"c\":2}\n"
  },
  {
    "path": "test/spec/jsonic-funky-keys.tsv",
    "content": "input\texpected\nx:1\t{\"x\":1}\nnull:1\t{\"null\":1}\ntrue:1\t{\"true\":1}\nfalse:1\t{\"false\":1}\n{a:{x:1}}\t{\"a\":{\"x\":1}}\na:{x:1}\t{\"a\":{\"x\":1}}\na:{null:1}\t{\"a\":{\"null\":1}}\na:{true:1}\t{\"a\":{\"true\":1}}\na:{false:1}\t{\"a\":{\"false\":1}}\n"
  },
  {
    "path": "test/spec/jsonic-process-array.tsv",
    "content": "input\texpected\n[a]\t[\"a\"]\n[a,]\t[\"a\"]\n[a,,]\t[\"a\",null]\n[,a]\t[null,\"a\"]\n[,a,]\t[null,\"a\"]\n[,,a]\t[null,null,\"a\"]\n[,,a,]\t[null,null,\"a\"]\n[,,a,,]\t[null,null,\"a\",null]\n [ a ] \t[\"a\"]\n [ a , ] \t[\"a\"]\n [ a , , ] \t[\"a\",null]\n [ , a ] \t[null,\"a\"]\n [ , a , ] \t[null,\"a\"]\n [ , , a ] \t[null,null,\"a\"]\n [ , , a , ] \t[null,null,\"a\"]\n [ , , a , , ] \t[null,null,\"a\",null]\n,\t[null]\n,,\t[null,null]\n1,\t[1]\n0,\t[0]\n,1\t[null,1]\n,0\t[null,0]\n,1,\t[null,1]\n,0,\t[null,0]\n,1,,\t[null,1,null]\n,0,,\t[null,0,null]\n[]\t[]\n[,]\t[null]\n[,,]\t[null,null]\n[0]\t[0]\n[0,1]\t[0,1]\n[0,1,2]\t[0,1,2]\n[0,]\t[0]\n[0,1,]\t[0,1]\n[0,1,2,]\t[0,1,2]\n[q]\t[\"q\"]\n[q,\"w\"]\t[\"q\",\"w\"]\n[q,\"w\",false]\t[\"q\",\"w\",false]\n[q,\"w\",false,0x,0x1]\t[\"q\",\"w\",false,\"0x\",1]\n[q,\"w\",false,0x,0x1,$]\t[\"q\",\"w\",false,\"0x\",1,\"$\"]\n[q,]\t[\"q\"]\n[q,\"w\",]\t[\"q\",\"w\"]\n[q,\"w\",false,]\t[\"q\",\"w\",false]\n[q,\"w\",false,0x,0x1,$,]\t[\"q\",\"w\",false,\"0x\",1,\"$\"]\n0,1\t[0,1]\n0,1,\t[0,1]\na:{b:1}\t{\"a\":{\"b\":1}}\na:[1]\t{\"a\":[1]}\na:[0,1]\t{\"a\":[0,1]}\na:[0,1,2]\t{\"a\":[0,1,2]}\n{a:[0,1,2]}\t{\"a\":[0,1,2]}\na:[1],b:[2,3]\t{\"a\":[1],\"b\":[2,3]}\n[[]]\t[[]]\n[[],]\t[[]]\n[[],[]]\t[[],[]]\n[[[]],[]]\t[[[]],[]]\n[[[],[]],[]]\t[[[],[]],[]]\n[[[],[[]]],[]]\t[[[],[[]]],[]]\n[[[],[[],[]]],[]]\t[[[],[[],[]]],[]]\n"
  },
  {
    "path": "test/spec/jsonic-process-implicit-object.tsv",
    "content": "input\texpected\na:1\t{\"a\":1}\na:1,b:2\t{\"a\":1,\"b\":2}\n"
  },
  {
    "path": "test/spec/jsonic-process-mixed-nodes.tsv",
    "content": "input\texpected\na:[{b:1}]\t{\"a\":[{\"b\":1}]}\n{a:[{b:1}]}\t{\"a\":[{\"b\":1}]}\n[{a:1}]\t[{\"a\":1}]\n[{a:1},{b:2}]\t[{\"a\":1},{\"b\":2}]\n[[{a:1}]]\t[[{\"a\":1}]]\n[[{a:1},{b:2}]]\t[[{\"a\":1},{\"b\":2}]]\n[[[{a:1}]]]\t[[[{\"a\":1}]]]\n[[[{a:1},{b:2}]]]\t[[[{\"a\":1},{\"b\":2}]]]\n[{a:[1]}]\t[{\"a\":[1]}]\n[{a:[{b:1}]}]\t[{\"a\":[{\"b\":1}]}]\n[{a:{b:[1]}}]\t[{\"a\":{\"b\":[1]}}]\n[{a:{b:[{c:1}]}}]\t[{\"a\":{\"b\":[{\"c\":1}]}}]\n[{a:{b:{c:[1]}}}]\t[{\"a\":{\"b\":{\"c\":[1]}}}]\n[{},{a:[1]}]\t[{},{\"a\":[1]}]\n[{},{a:[{b:1}]}]\t[{},{\"a\":[{\"b\":1}]}]\n[{},{a:{b:[1]}}]\t[{},{\"a\":{\"b\":[1]}}]\n[{},{a:{b:[{c:1}]}}]\t[{},{\"a\":{\"b\":[{\"c\":1}]}}]\n[{},{a:{b:{c:[1]}}}]\t[{},{\"a\":{\"b\":{\"c\":[1]}}}]\n[[],{a:[1]}]\t[[],{\"a\":[1]}]\n[[],{a:[{b:1}]}]\t[[],{\"a\":[{\"b\":1}]}]\n[[],{a:{b:[1]}}]\t[[],{\"a\":{\"b\":[1]}}]\n[[],{a:{b:[{c:1}]}}]\t[[],{\"a\":{\"b\":[{\"c\":1}]}}]\n[[],{a:{b:{c:[1]}}}]\t[[],{\"a\":{\"b\":{\"c\":[1]}}}]\n[{a:[1]},{a:[1]}]\t[{\"a\":[1]},{\"a\":[1]}]\n[{a:[{b:1}]},{a:[{b:1}]}]\t[{\"a\":[{\"b\":1}]},{\"a\":[{\"b\":1}]}]\n[{a:{b:[1]}},{a:{b:[1]}}]\t[{\"a\":{\"b\":[1]}},{\"a\":{\"b\":[1]}}]\n[{a:{b:[{c:1}]}},{a:{b:[{c:1}]}}]\t[{\"a\":{\"b\":[{\"c\":1}]}},{\"a\":{\"b\":[{\"c\":1}]}}]\n[{a:{b:{c:[1]}}},{a:{b:{c:[1]}}}]\t[{\"a\":{\"b\":{\"c\":[1]}}},{\"a\":{\"b\":{\"c\":[1]}}}]\n"
  },
  {
    "path": "test/spec/jsonic-process-object-tree.tsv",
    "content": "input\texpected\n{}\t{}\n{a:1}\t{\"a\":1}\n{a:1,b:q}\t{\"a\":1,\"b\":\"q\"}\n{a:1,b:q,c:\"w\"}\t{\"a\":1,\"b\":\"q\",\"c\":\"w\"}\na:1,b:{c:2}\t{\"a\":1,\"b\":{\"c\":2}}\na:1,d:3,b:{c:2}\t{\"a\":1,\"d\":3,\"b\":{\"c\":2}}\na:1,b:{c:2},d:3\t{\"a\":1,\"d\":3,\"b\":{\"c\":2}}\na:1,b:{c:2},e:{f:4}\t{\"a\":1,\"b\":{\"c\":2},\"e\":{\"f\":4}}\na:1,b:{c:2},d:3,e:{f:4}\t{\"a\":1,\"d\":3,\"b\":{\"c\":2},\"e\":{\"f\":4}}\na:1,b:{c:2},d:3,e:{f:4},g:5\t{\"a\":1,\"d\":3,\"b\":{\"c\":2},\"e\":{\"f\":4},\"g\":5}\na:{b:1}\t{\"a\":{\"b\":1}}\n{a:{b:1}}\t{\"a\":{\"b\":1}}\na:{b:1}\t{\"a\":{\"b\":1}}\n{a:{b:{c:1}}}\t{\"a\":{\"b\":{\"c\":1}}}\na:{b:{c:1}}\t{\"a\":{\"b\":{\"c\":1}}}\na:1,b:{c:2},d:{e:{f:3}}\t{\"a\":1,\"b\":{\"c\":2},\"d\":{\"e\":{\"f\":3}}}\na:1,b:{c:2},d:{e:{f:3}},g:4\t{\"a\":1,\"b\":{\"c\":2},\"d\":{\"e\":{\"f\":3}},\"g\":4}\na:1,b:{c:2},d:{e:{f:3}},h:{i:5},g:4\t{\"a\":1,\"b\":{\"c\":2},\"d\":{\"e\":{\"f\":3}},\"g\":4,\"h\":{\"i\":5}}\na:1,b:{c:2}d:3\t{\"a\":1,\"b\":{\"c\":2},\"d\":3}\n"
  },
  {
    "path": "test/spec/jsonic-process-scalars.tsv",
    "content": "input\texpected\nnull\tnull\ntrue\ttrue\nfalse\tfalse\n123\t123\n\"a\"\t\"a\"\n'b'\t\"b\"\nq\t\"q\"\nx\t\"x\"\n"
  },
  {
    "path": "test/spec/jsonic-process-text.tsv",
    "content": "input\texpected\nq\t\"q\"\n[ qq ]\t[\"qq\"]\n[ q ]\t[\"q\"]\n[ c ]\t[\"c\"]\nc:[ c ]\t{\"c\":[\"c\"]}\nc:[ c , cc ]\t{\"c\":[\"c\",\"cc\"]}\n"
  },
  {
    "path": "test/spec/jsonic-process-whitespace.tsv",
    "content": "input\texpected\n[0,1]\t[0,1]\n[0, 1]\t[0,1]\n[0 ,1]\t[0,1]\n[0 ,1 ]\t[0,1]\n[0,1 ]\t[0,1]\n[ 0,1]\t[0,1]\n[ 0,1 ]\t[0,1]\n{a: 1}\t{\"a\":1}\n{a : 1}\t{\"a\":1}\n{a: 1,b: 2}\t{\"a\":1,\"b\":2}\n{a : 1,b : 2}\t{\"a\":1,\"b\":2}\n{a:\\n1}\t{\"a\":1}\n{a\\n:\\n1}\t{\"a\":1}\n{a:\\n1,b:\\n2}\t{\"a\":1,\"b\":2}\n{a\\n:\\n1,b\\n:\\n2}\t{\"a\":1,\"b\":2}\n{a:\\r\\n1}\t{\"a\":1}\n{a\\r\\n:\\r\\n1}\t{\"a\":1}\n{a:\\r\\n1,b:\\r\\n2}\t{\"a\":1,\"b\":2}\n{a\\r\\n:\\r\\n1,b\\r\\n:\\r\\n2}\t{\"a\":1,\"b\":2}\n { a: 1 } \t{\"a\":1}\n { a : 1 } \t{\"a\":1}\n { a: 1 , b: 2 } \t{\"a\":1,\"b\":2}\n { a : 1 , b : 2 } \t{\"a\":1,\"b\":2}\n  {  a:  1  }  \t{\"a\":1}\n  {  a  :  1  }  \t{\"a\":1}\n  {  a:  1  ,  b:  2  }  \t{\"a\":1,\"b\":2}\n  {  a  :  1  ,  b  :  2  }  \t{\"a\":1,\"b\":2}\n\\n  {\\n  a:\\n  1\\n  }\\n  \t{\"a\":1}\n\\n  {\\n  a\\n  :\\n  1\\n  }\\n  \t{\"a\":1}\n\\n  {\\n  a:\\n  1\\n  ,\\n  b:\\n  2\\n  }\\n  \t{\"a\":1,\"b\":2}\n\\n  {\\n  a\\n  :\\n  1\\n  ,\\n  b\\n  :\\n  2\\n  }\\n  \t{\"a\":1,\"b\":2}\n\\n  \\n{\\n  \\na:\\n  \\n1\\n  \\n}\\n  \\n\t{\"a\":1}\n\\n  \\n{\\n  \\na\\n  \\n:\\n  \\n1\\n  \\n}\\n  \\n\t{\"a\":1}\n\\n  \\n{\\n  \\na:\\n  \\n1\\n  \\n,\\n  \\nb:\\n  \\n2\\n  \\n}\\n  \\n\t{\"a\":1,\"b\":2}\n\\n  \\n{\\n  \\na\\n  \\n:\\n  \\n1\\n  \\n,\\n  \\nb\\n  \\n:\\n  \\n2\\n  \\n}\\n  \\n\t{\"a\":1,\"b\":2}\n\\n\\n{\\n\\na:\\n\\n1\\n\\n}\\n\\n\t{\"a\":1}\n\\n\\n{\\n\\na\\n\\n:\\n\\n1\\n\\n}\\n\\n\t{\"a\":1}\n\\n\\n{\\n\\na:\\n\\n1\\n\\n,\\n\\nb:\\n\\n2\\n\\n}\\n\\n\t{\"a\":1,\"b\":2}\n\\n\\n{\\n\\na\\n\\n:\\n\\n1\\n\\n,\\n\\nb\\n\\n:\\n\\n2\\n\\n}\\n\\n\t{\"a\":1,\"b\":2}\n\\r\\n{\\r\\na:\\r\\n1\\r\\n}\\r\\n\t{\"a\":1}\n\\r\\n{\\r\\na\\r\\n:\\r\\n1\\r\\n}\\r\\n\t{\"a\":1}\n\\r\\n{\\r\\na:\\r\\n1\\r\\n,\\r\\nb:\\r\\n2\\r\\n}\\r\\n\t{\"a\":1,\"b\":2}\n\\r\\n{\\r\\na\\r\\n:\\r\\n1\\r\\n,\\r\\nb\\r\\n:\\r\\n2\\r\\n}\\r\\n\t{\"a\":1,\"b\":2}\na: 1\t{\"a\":1}\n a: 1\t{\"a\":1}\n a: 1 \t{\"a\":1}\n a : 1 \t{\"a\":1}\n a: [ { b: 1 } ] \t{\"a\":[{\"b\":1}]}\n\\na: [\\n  {\\n     b: 1\\n  }\\n]\\n\t{\"a\":[{\"b\":1}]}\n"
  },
  {
    "path": "test/spec/lex-errors.tsv",
    "content": "input\texpected\n\"unterminated\tERROR:unterminated_string\n'unterminated\tERROR:unterminated_string\n`unterminated\tERROR:unterminated_string\n/*unterminated\tERROR:unterminated_comment\n{\"a\":\"unterminated}\tERROR:unterminated_string\n[\"unterminated]\tERROR:unterminated_string\n"
  },
  {
    "path": "test/spec/utility-deep.tsv",
    "content": "arg1\targ2\targ3\targ4\texpected\nnull\t\t\t\tnull\nnull\tnull\t\t\tnull\n1\tnull\t\t\tnull\n1\t2\t\t\t2\n1\t\"a\"\t\t\t\"a\"\n{}\t\t\t\t{}\nnull\t{}\t\t\t{}\n{}\tnull\t\t\tnull\n[]\t\t\t\t[]\nnull\t[]\t\t\t[]\n[]\tnull\t\t\tnull\n1\t{}\t\t\t{}\n{}\t1\t\t\t1\n1\t[]\t\t\t[]\n[]\t1\t\t\t1\n{\"a\":1}\t\t\t\t{\"a\":1}\nnull\t{\"a\":1}\t\t\t{\"a\":1}\n{\"a\":1}\tnull\t\t\tnull\n{\"a\":1}\t{}\t\t\t{\"a\":1}\n{\"a\":1}\t{\"b\":2}\t\t\t{\"a\":1,\"b\":2}\n{\"a\":1}\t{\"b\":2,\"a\":3}\t\t\t{\"a\":3,\"b\":2}\n{\"a\":1,\"b\":{\"c\":2}}\t{}\t\t\t{\"a\":1,\"b\":{\"c\":2}}\n{\"a\":1,\"b\":{\"c\":2}}\t{\"a\":3}\t\t\t{\"a\":3,\"b\":{\"c\":2}}\n{\"a\":1,\"b\":{\"c\":2}}\t{\"a\":3,\"b\":{}}\t\t\t{\"a\":3,\"b\":{\"c\":2}}\n{\"a\":1,\"b\":{\"c\":2}}\t{\"a\":3,\"b\":{\"d\":4}}\t\t\t{\"a\":3,\"b\":{\"c\":2,\"d\":4}}\n{\"a\":1,\"b\":{\"c\":2}}\t{\"a\":3,\"b\":{\"c\":5}}\t\t\t{\"a\":3,\"b\":{\"c\":5}}\n{\"a\":1,\"b\":{\"c\":2}}\t{\"a\":3,\"b\":{\"c\":5,\"e\":6}}\t\t\t{\"a\":3,\"b\":{\"c\":5,\"e\":6}}\n[1]\t\t\t\t[1]\nnull\t[1]\t\t\t[1]\n[1]\tnull\t\t\tnull\n[1]\t[]\t\t\t[1]\n[]\t[1]\t\t\t[1]\n[1]\t[2]\t\t\t[2]\n[1,3]\t[2]\t\t\t[2,3]\n{\"a\":1,\"b\":[]}\t\t\t\t{\"a\":1,\"b\":[]}\n{\"a\":1,\"b\":[2]}\t\t\t\t{\"a\":1,\"b\":[2]}\n{\"a\":1,\"b\":[2],\"c\":[{\"d\":3}]}\t\t\t\t{\"a\":1,\"b\":[2],\"c\":[{\"d\":3}]}\n{\"a\":1,\"b\":[2],\"c\":[{\"d\":3}]}\t{\"a\":4,\"b\":[5],\"c\":[{\"d\":6}]}\t\t\t{\"a\":4,\"b\":[5],\"c\":[{\"d\":6}]}\n[]\t{}\t\t\t{}\n{}\t[]\t\t\t[]\n{\"a\":[]}\t{\"a\":{}}\t\t\t{\"a\":{}}\n{\"a\":{}}\t{\"a\":[]}\t\t\t{\"a\":[]}\n[[]]\t[{}]\t\t\t[{}]\n[{}]\t[[]]\t\t\t[[]]\n{\"a\":1}\t{\"b\":2}\t{\"c\":3}\t\t{\"a\":1,\"b\":2,\"c\":3}\n{\"a\":1}\t{\"a\":2,\"b\":4}\t{\"c\":3}\t\t{\"a\":2,\"b\":4,\"c\":3}\n{\"a\":1}\t{\"a\":2,\"b\":4}\t{\"a\":3,\"c\":5}\t\t{\"a\":3,\"b\":4,\"c\":5}\n{\"a\":1}\t{\"b\":2}\tnull\t\tnull\n{\"a\":1}\tnull\t{\"c\":3}\t\t{\"c\":3}\nnull\t{\"b\":2}\t{\"c\":3}\t\t{\"b\":2,\"c\":3}\n{}\t{\"a\":1}\t{\"b\":2}\t{\"c\":3}\t{\"a\":1,\"b\":2,\"c\":3}\n{\"a\":1}\t{\"a\":null}\t\t\t{\"a\":null}\n"
  },
  {
    "path": "test/spec/utility-modlist.tsv",
    "content": "input\topts\texpected\n[]\t\t[]\n[\"a\"]\t\t[\"a\"]\n[\"a\",\"b\"]\t\t[\"a\",\"b\"]\n[\"a\",\"b\",\"c\"]\t\t[\"a\",\"b\",\"c\"]\n[]\t{\"delete\":[]}\t[]\n[]\t{\"delete\":[0]}\t[]\n[]\t{\"delete\":[1]}\t[]\n[]\t{\"delete\":[-1]}\t[]\n[]\t{\"delete\":[10]}\t[]\n[]\t{\"delete\":[-10]}\t[]\n[\"a\"]\t{\"delete\":[]}\t[\"a\"]\n[\"b\"]\t{\"delete\":[0]}\t[]\n[\"c\"]\t{\"delete\":[1]}\t[\"c\"]\n[\"d\"]\t{\"delete\":[-1]}\t[]\n[\"e\"]\t{\"delete\":[10]}\t[\"e\"]\n[\"f\"]\t{\"delete\":[-10]}\t[\"f\"]\n[\"a\",\"z\"]\t{\"delete\":[]}\t[\"a\",\"z\"]\n[\"b\",\"z\"]\t{\"delete\":[0]}\t[\"z\"]\n[\"c\",\"z\"]\t{\"delete\":[1]}\t[\"c\"]\n[\"d\",\"z\"]\t{\"delete\":[-1]}\t[\"d\"]\n[\"e\",\"z\"]\t{\"delete\":[10]}\t[\"e\",\"z\"]\n[\"f\",\"z\"]\t{\"delete\":[-10]}\t[\"f\",\"z\"]\n[\"a\",\"y\",\"z\"]\t{\"delete\":[]}\t[\"a\",\"y\",\"z\"]\n[\"b\",\"y\",\"z\"]\t{\"delete\":[0]}\t[\"y\",\"z\"]\n[\"c\",\"y\",\"z\"]\t{\"delete\":[1]}\t[\"c\",\"z\"]\n[\"d\",\"y\",\"z\"]\t{\"delete\":[-1]}\t[\"d\",\"y\"]\n[\"e\",\"y\",\"z\"]\t{\"delete\":[10]}\t[\"e\",\"y\",\"z\"]\n[\"f\",\"y\",\"z\"]\t{\"delete\":[-10]}\t[\"f\",\"y\",\"z\"]\n[]\t{\"delete\":[]}\t[]\n[]\t{\"delete\":[0]}\t[]\n[]\t{\"delete\":[2]}\t[]\n[]\t{\"delete\":[-2]}\t[]\n[]\t{\"delete\":[20]}\t[]\n[]\t{\"delete\":[-20]}\t[]\n[\"a\"]\t{\"delete\":[]}\t[\"a\"]\n[\"b\"]\t{\"delete\":[0]}\t[]\n[\"c\"]\t{\"delete\":[2]}\t[\"c\"]\n[\"d\"]\t{\"delete\":[-2]}\t[\"d\"]\n[\"e\"]\t{\"delete\":[20]}\t[\"e\"]\n[\"f\"]\t{\"delete\":[-20]}\t[\"f\"]\n[\"a\",\"z\"]\t{\"delete\":[]}\t[\"a\",\"z\"]\n[\"b\",\"z\"]\t{\"delete\":[0]}\t[\"z\"]\n[\"c\",\"z\"]\t{\"delete\":[2]}\t[\"c\",\"z\"]\n[\"d\",\"z\"]\t{\"delete\":[-2]}\t[\"z\"]\n[\"e\",\"z\"]\t{\"delete\":[20]}\t[\"e\",\"z\"]\n[\"f\",\"z\"]\t{\"delete\":[-20]}\t[\"f\",\"z\"]\n[\"a\",\"y\",\"z\"]\t{\"delete\":[]}\t[\"a\",\"y\",\"z\"]\n[\"b\",\"y\",\"z\"]\t{\"delete\":[0]}\t[\"y\",\"z\"]\n[\"c\",\"y\",\"z\"]\t{\"delete\":[2]}\t[\"c\",\"y\"]\n[\"d\",\"y\",\"z\"]\t{\"delete\":[-2]}\t[\"d\",\"z\"]\n[\"e\",\"y\",\"z\"]\t{\"delete\":[20]}\t[\"e\",\"y\",\"z\"]\n[\"f\",\"y\",\"z\"]\t{\"delete\":[-20]}\t[\"f\",\"y\",\"z\"]\n[]\t{\"delete\":[]}\t[]\n[]\t{\"delete\":[0]}\t[]\n[]\t{\"delete\":[3]}\t[]\n[]\t{\"delete\":[-3]}\t[]\n[]\t{\"delete\":[30]}\t[]\n[]\t{\"delete\":[-30]}\t[]\n[\"a\"]\t{\"delete\":[]}\t[\"a\"]\n[\"b\"]\t{\"delete\":[0]}\t[]\n[\"c\"]\t{\"delete\":[3]}\t[\"c\"]\n[\"d\"]\t{\"delete\":[-3]}\t[\"d\"]\n[\"e\"]\t{\"delete\":[30]}\t[\"e\"]\n[\"f\"]\t{\"delete\":[-30]}\t[\"f\"]\n[\"a\",\"z\"]\t{\"delete\":[]}\t[\"a\",\"z\"]\n[\"b\",\"z\"]\t{\"delete\":[0]}\t[\"z\"]\n[\"c\",\"z\"]\t{\"delete\":[3]}\t[\"c\",\"z\"]\n[\"d\",\"z\"]\t{\"delete\":[-3]}\t[\"d\",\"z\"]\n[\"e\",\"z\"]\t{\"delete\":[30]}\t[\"e\",\"z\"]\n[\"f\",\"z\"]\t{\"delete\":[-30]}\t[\"f\",\"z\"]\n[\"a\",\"y\",\"z\"]\t{\"delete\":[]}\t[\"a\",\"y\",\"z\"]\n[\"b\",\"y\",\"z\"]\t{\"delete\":[0]}\t[\"y\",\"z\"]\n[\"c\",\"y\",\"z\"]\t{\"delete\":[3]}\t[\"c\",\"y\",\"z\"]\n[\"d\",\"y\",\"z\"]\t{\"delete\":[-3]}\t[\"y\",\"z\"]\n[\"e\",\"y\",\"z\"]\t{\"delete\":[30]}\t[\"e\",\"y\",\"z\"]\n[\"f\",\"y\",\"z\"]\t{\"delete\":[-30]}\t[\"f\",\"y\",\"z\"]\n[\"a\",\"y\",\"z\"]\t{\"move\":[0,1]}\t[\"y\",\"a\",\"z\"]\n[\"a\",\"y\",\"z\"]\t{\"move\":[0,-1]}\t[\"y\",\"z\",\"a\"]\n"
  },
  {
    "path": "test/spec/utility-str.tsv",
    "content": "input\tmaxlen\texpected\n\"12345\"\t6\t12345\n\"12345\"\t5\t12345\n\"12345\"\t4\t1...\n\"12345\"\t3\t...\n\"12345\"\t2\t..\n\"12345\"\t1\t.\n\"12345\"\t0\n\"12345\"\t-1\n\"123\"\t4\t123\n\"123\"\t3\t123\n\"123\"\t2\t..\n\"123\"\t1\t.\n\"123\"\t0\n\"123\"\t-1\n1\t\t1\ntrue\t\ttrue\n{\"a\":1}\t\t{\"a\":1}\n[1,2]\t\t[1,2]\n{\"a\":1}\t7\t{\"a\":1}\n[1,2]\t5\t[1,2]\n{\"a\":1}\t6\t{\"a...\n[1,2]\t4\t[...\n[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3]\t\t[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,...\n"
  },
  {
    "path": "test/spec/utility-strinject.tsv",
    "content": "template\tvalues\texpected\na{b}c\t{\"b\":\"B\"}\taBc\na{b}c{d}e\t{\"b\":\"B\",\"d\":\"D\"}\taBcDe\n\t{}\n\t[]\n\t\"\"\na{b}c\t{}\ta{b}c\na{b}c\t[]\ta{b}c\na{b}c\t1\ta{b}c\na{b}c\t\"x\"\ta{b}c\na{b}c\tnull\ta{b}c\na{b.d}c\t{\"b\":{\"d\":\"B\"}}\taBc\na{b.d.e}c\t{\"b\":{\"d\":{\"e\":\"B\"}}}\taBc\na{0}c\t[\"B\"]\taBc\na{1}c\t[\"A\",\"B\"]\taBc\na{b.0}c\t{\"b\":[\"B\"]}\taBc\na{0.b}c\t[{\"b\":\"B\"}]\taBc\n{a}\t{\"a\":\"A\"}\tA\n{a}\t{\"a\":11}\t11\n{a}\t{\"a\":2.2}\t2.2\n{a}\t{\"a\":[11,22]}\t[11,22]\n{a}\t{\"a\":{\"b\":1}}\t{b:1}\n{a}\t{\"a\":{\"b\":[{\"c\":{\"d\":1}}]}}\t{b:[{c:{d:1}}]}\n"
  },
  {
    "path": "test/spec.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { loadTSV } = require('./utility')\n\ndescribe('spec', function () {\n  it('loadTSV-not-found', () => {\n    assert.throws(() => loadTSV('does-not-exist'), Error,\n      /spec file not found/,)\n  })\n\n  it('loadTSV-returns-rows', () => {\n    const entries = loadTSV('happy')\n    assert.ok(entries.length > 0)\n    assert.deepEqual(Object.keys({ row: 1 }).reduce((a,k)=>(a[k]=(entries[0])[k],a),{}), { row: 1 })\n    assert.ok(Array.isArray(entries[0].cols))\n    assert.deepEqual(entries[0].cols.length, 2)\n  })\n})\n"
  },
  {
    "path": "test/syntax-error.js",
    "content": "const { Jsonic } = require('..')\nJsonic(`{\n  a: 1\n  ]\n}`)\n"
  },
  {
    "path": "test/test-plugins.sh",
    "content": "# Assumes plugin repos checked out at same level\n\ncd ../path\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../directive\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../multisource\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../hoover\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../expr\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../csv\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../toml\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../ini\nnpm link jsonic\nnpm run build\nnpm test\n\ncd ../jsonic\n\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"module\": \"nodenext\",\n    \"noEmitOnError\": true,\n    \"outDir\":\"../dist-test\",\n    \"rootDir\":\".\",\n    \"resolveJsonModule\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"target\": \"ES2021\",\n    \"types\": [\"node\"]\n  }\n}"
  },
  {
    "path": "test/utility.js",
    "content": "const { readFileSync, existsSync } = require('fs')\nconst { join } = require('path')\n\nfunction unescape(str) {\n  return str.replace(/\\\\r\\\\n|\\\\n|\\\\r/g, (m) => {\n    if (m === '\\\\r\\\\n') return '\\r\\n'\n    if (m === '\\\\n') return '\\n'\n    if (m === '\\\\r') return '\\r'\n    return m\n  })\n}\n\nfunction loadTSV(name) {\n  const specPath = join(__dirname, 'spec', name + '.tsv')\n\n  if (!existsSync(specPath)) {\n    throw new Error('spec file not found: ' + specPath)\n  }\n\n  const lines = readFileSync(specPath, 'utf8').split(/\\r?\\n/).filter(Boolean)\n  return lines.slice(1).map((line, i) => {\n    const cols = line.split('\\t').map(unescape)\n    return { cols, row: i + 1 }\n  })\n}\n\nmodule.exports = { loadTSV }\n"
  },
  {
    "path": "test/utility.test.js",
    "content": "/* Copyright (c) 2013-2025 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst Util = require('util')\n\nconst { filterRules, modlist } = require('../dist/utility')\nconst { strinject } = require('../dist/error')\n\nconst { util, Jsonic, makeToken, makePoint } = require('..')\nconst { loadTSV } = require('./utility')\n\nconst {\n  deep,\n  errinject,\n  srcfmt,\n  badlex,\n  regexp,\n  mesc,\n  makelog,\n  tokenize,\n  errdesc,\n  trimstk,\n  configure,\n  TIME,\n  prop,\n  str,\n\n  // TODO: validated as util API\n  omap,\n} = util\n\nconst I = Util.inspect\n\nconst D = (o) => console.dir(o, { depth: null })\n\ndescribe('utility', () => {\n\n  it('omap', () => {\n    let o0 = { x: 1, y: 2 }\n\n    // Modify.\n    assert.deepEqual(Object.keys({ x: 2, y: 4 }).reduce((a,k)=>(a[k]=(omap(o0, ([k, v]) => [k, v * 2]))[k],a),{}), { x: 2, y: 4 })\n\n    // Delete.\n    assert.deepEqual(Object.keys({\n      y: 2,\n    }).reduce((a,k)=>(a[k]=(omap(o0, ([k, v]) => ['x' === k ? undefined : k, v]))[k],a),{}), {\n      y: 2,\n    })\n\n    // Add.\n    assert.deepEqual(Object.keys({\n      x: 1,\n      y: 2,\n      zx: 2,\n      zy: 4,\n    }).reduce((a,k)=>(a[k]=(omap(o0, ([k, v]) => [k, v, 'z' + k, v * 2]))[k],a),{}), {\n      x: 1,\n      y: 2,\n      zx: 2,\n      zy: 4,\n    })\n\n    // Delete and Add.\n    assert.deepEqual(Object.keys({ zx: 2, zy: 4 }).reduce((a,k)=>(a[k]=(\n      omap(o0, ([k, v]) => [undefined, undefined, 'z' + k, v * 2]))[k],a),{}), { zx: 2, zy: 4 })\n  })\n  \n\n  it('str', () => {\n    const entries = loadTSV('utility-str')\n    for (const { cols, row } of entries) {\n      try {\n        const val = JSON.parse(cols[0])\n        const result = cols[1] !== '' ? str(val, Number(cols[1])) : str(val)\n        assert.deepEqual(result, cols[2] || '')\n      } catch (err) {\n        err.message = `utility-str row ${row}: input=${cols[0]} maxlen=${cols[1]} expected=${cols[2]}\\n${err.message}`\n        throw err\n      }\n    }\n  })\n\n  it('token', () => {\n    let p0 = makePoint(4, 3, 2, 1)\n    assert.deepEqual('' + p0, 'Point[3/4,2,1]')\n    assert.deepEqual(I(p0), 'Point[3/4,2,1]')\n  })\n\n  it('token', () => {\n    let p0 = makePoint(1, 2, 3, 4)\n    let t0 = makeToken('a', 1, 'b', 'bs', p0, { x: 1 }, 'W')\n    assert.deepEqual('' + t0, 'Token[a=1 bs=b 2,3,4 {x:1} W]')\n\n    let t0e = t0.bad('foo')\n    assert.ok(t0 === t0e)\n    assert.deepEqual(t0e.err, 'foo')\n    assert.deepEqual('' + t0e, 'Token[a=1 bs=b 2,3,4 {x:1} foo W]')\n  })\n\n  it('configure', () => {\n    configure({}, {}, {})\n\n    configure(\n      {},\n      {},\n      {\n        fixed: null,\n        tokenSet: null,\n        text: null,\n        value: null,\n        string: null,\n        comment: null,\n        number: null,\n        space: null,\n        line: null,\n        lex: null,\n        rule: null,\n        config: null,\n        debug: null,\n        map: null,\n      },\n    )\n\n    configure({}, {}, { debug: { print: null }, comment: { lex: true } })\n\n    let c = { t: {}, tI: 0 }\n    let o0 = {\n      fixed: {},\n      tokenSet: {},\n      text: {},\n      value: {},\n      string: {},\n      comment: {},\n      number: {},\n      space: {},\n      line: {},\n      lex: {},\n      rule: {},\n      config: {},\n      debug: {},\n      map: {},\n    }\n\n    configure({}, c, o0)\n    assert.deepEqual(Object.keys(c.t).length > 0, true)\n\n    c = { t: {}, tI: 1 }\n    let o1 = deep({ fixed: { token: { '#Ta': 'a' } } }, o0)\n    configure({}, c, o1)\n    // console.log(c)\n    assert.deepEqual(c.t.Ta, 12)\n  })\n\n  it('token-gen', () => {\n    let s = 0\n    let config = {\n      tI: 1,\n      t: {},\n    }\n\n    assert.deepEqual(tokenize(undefined, config), undefined)\n    assert.deepEqual(tokenize(null, config), undefined)\n\n    let s1 = tokenize('AA', config)\n    assert.deepEqual(s1, s + 1)\n    assert.deepEqual(config.t.AA, s + 1)\n    assert.deepEqual(config.t[s + 1], 'AA')\n    assert.deepEqual(tokenize('AA', config), s + 1)\n    assert.deepEqual(tokenize(s + 1, config), 'AA')\n\n    let s1a = tokenize('AA', config)\n    assert.deepEqual(s1a, s + 1)\n    assert.deepEqual(config.t.AA, s + 1)\n    assert.deepEqual(config.t[s + 1], 'AA')\n    assert.deepEqual(tokenize('AA', config), s + 1)\n    assert.deepEqual(tokenize(s + 1, config), 'AA')\n\n    let s2 = tokenize('BB', config)\n    assert.deepEqual(s2, s + 2)\n    assert.deepEqual(config.t.BB, s + 2)\n    assert.deepEqual(config.t[s + 2], 'BB')\n    assert.deepEqual(tokenize('BB', config), s + 2)\n    assert.deepEqual(tokenize(s + 2, config), 'BB')\n  })\n\n  it('deep', () => {\n    const entries = loadTSV('utility-deep')\n    for (const { cols, row } of entries) {\n      try {\n        const args = []\n        for (let i = 0; i < 4; i++) {\n          if (cols[i] !== undefined && cols[i] !== '') {\n            args.push(JSON.parse(cols[i]))\n          } else {\n            break\n          }\n        }\n        const expected = JSON.parse(cols[4])\n        assert.deepEqual(deep(...args), expected)\n      } catch (err) {\n        err.message = `utility-deep row ${row}: args=${cols.slice(0, 4).join(',')} expected=${cols[4]}\\n${err.message}`\n        throw err\n      }\n    }\n  })\n\n  it('errinject', () => {\n    let args = [\n      'c0',\n      { a: 1 },\n      { b: 2 },\n      { c: 3 },\n      { d: 4, meta: { g: 7 }, opts: { e: 5 }, cfg: { f: 6 } },\n    ]\n    assert.deepEqual(\n      errinject('x {code} {a} {b} {c} {d} {e} {f} {g} {Z} x', ...args), 'x c0 1 2 3 4 5 6 7 {Z} x')\n  })\n\n  it('srcfmt', () => {\n    let F = srcfmt({ debug: { maxlen: 4, print: {} } })\n    assert.deepEqual(F('a'), '\"a\"')\n    assert.deepEqual(F('ab'), '\"ab\"')\n    assert.deepEqual(F('abc'), '\"abc...')\n  })\n\n  it('trimstk', () => {\n    trimstk({})\n  })\n\n  it('regexp', () => {\n    assert.deepEqual(regexp('', 'a'), /a/)\n    assert.deepEqual(regexp('', 'a*'), /a*/)\n    assert.deepEqual(regexp('', mesc('ab*')), /ab\\*/)\n  })\n\n  it('prop', () => {\n    let o0 = {}\n\n    assert.deepEqual(prop(o0, 'a', 1), 1)\n    assert.deepEqual(o0, { a: 1 })\n\n    assert.deepEqual(prop(o0, 'b.c', 2), 2)\n    assert.deepEqual(o0, { a: 1, b: { c: 2 } })\n\n    assert.throws(() => prop(o0, 'a.d', 3), /Cannot set path a\\.d on object/)\n  })\n\n  it('modlist', () => {\n    const entries = loadTSV('utility-modlist')\n    for (const { cols, row } of entries) {\n      try {\n        const list = JSON.parse(cols[0])\n        const result = cols[1] !== '' ? modlist(list, JSON.parse(cols[1])) : modlist(list)\n        assert.deepEqual(result, JSON.parse(cols[2]))\n      } catch (err) {\n        err.message = `utility-modlist row ${row}: input=${cols[0]} opts=${cols[1]} expected=${cols[2]}\\n${err.message}`\n        throw err\n      }\n    }\n  })\n\n  it('makelog', () => {\n    let log = []\n    let dir = []\n\n    let cfg = {\n      debug: {\n        print: {\n          config: true,\n        },\n        get_console: () => ({\n          log: (x) => log.push(x),\n          dir: (x) => dir.push(x),\n        }),\n      },\n    }\n\n    let g0 = makelog({})\n    let g1 = makelog({ cfg }, { log: 1 })\n    let g2 = makelog({ cfg }, { log: -1 })\n\n    assert.equal(g0, undefined)\n\n    log = []\n    dir = []\n    g1('A')\n    assert.deepEqual(log, [])\n    assert.deepEqual(dir, [['A']])\n\n    log = []\n    dir = []\n    g2('B')\n    assert.deepEqual(log, ['B'])\n    assert.deepEqual(dir, [])\n\n    log = []\n    dir = []\n    let j = Jsonic.make(cfg)\n    j('a:1', { log: -1 })\n    assert.deepEqual(dir[0].debug.print.config, true)\n  })\n\n  it('errdesc', () => {\n    let ctx0 = {\n      cfg: {\n        t: {\n          1: '#T1',\n        },\n        error: {\n          foo: 'foo-code',\n          unknown: 'unknown-code',\n        },\n        errmsg: {\n          name: 'jsonic',\n          suffix: true,\n        },\n        hint: {\n          foo: 'foo-hint',\n          unknown: 'unknown-hint',\n        },\n        color: {\n          active: false\n        }\n      },\n      src: () => 'src',\n      plgn: () => [{ name: 'p0' }],\n      opts: {\n        tag: 'zed',\n      },\n    }\n\n    let d0 = errdesc('foo', {}, { tin: 1 }, {}, ctx0)\n    // console.log(d0)\n    assert.deepEqual(d0.code, 'foo')\n    assert.deepEqual(d0.message.includes('foo-code'), true)\n    assert.deepEqual(d0.message.includes('foo-hint'), true)\n\n    let d1 = errdesc(\n      'not-a-code',\n      { x: 1 },\n      { tin: 1 },\n      {},\n      { ...ctx0, meta: { mode: 'm0', fileName: 'fn0' } },\n    )\n    //console.log(d1)\n    assert.deepEqual(d1.code, 'not-a-code')\n    assert.deepEqual(d1.message.includes('unknown-code'), true)\n    assert.deepEqual(d1.message.includes('unknown-hint'), true)\n  })\n\n  it('filterRules', () => {\n    let F = (r, c) =>\n      omap(filterRules(deep({}, r), { rule: c }).def, ([k, v]) => [\n        k,\n        v.map((r) => r.x).join(''),\n      ])\n    let DF = (r, c) => D(F(r, c))\n\n    let rs0 = {\n      def: {\n        open: [\n          { x: 1, g: 'a0,a1' },\n          { x: 2, g: 'a0,a2' },\n          { x: 3, g: 'a1,a2' },\n          { x: 4, g: 'a3,a4' },\n        ],\n        close: [],\n      },\n    }\n\n    assert.deepEqual(F(rs0, { include: [], exclude: [] }), {\n      open: '1234',\n      close: '',\n    })\n    assert.deepEqual(F(rs0, { include: ['a0'], exclude: [] }), {\n      open: '12',\n      close: '',\n    })\n    assert.deepEqual(F(rs0, { include: ['a1'], exclude: [] }), {\n      open: '13',\n      close: '',\n    })\n    assert.deepEqual(F(rs0, { include: ['x0'], exclude: [] }), {\n      open: '',\n      close: '',\n    })\n    assert.deepEqual(F(rs0, { include: ['a1', 'a2'], exclude: [] }), {\n      open: '123',\n      close: '',\n    })\n\n    let rs1 = {\n      def: {\n        open: [\n          { x: 1, g: 'a0,a1' },\n          { x: 2, g: 'a0,a2' },\n          { x: 3, g: 'a1,a2' },\n          { x: 4, g: 'a3,a4' },\n        ],\n        close: [],\n      },\n    }\n\n    assert.deepEqual(F(rs1, { include: [], exclude: [] }), {\n      open: '1234',\n      close: '',\n    })\n    assert.deepEqual(F(rs1, { include: [], exclude: ['a0'] }), {\n      open: '34',\n      close: '',\n    })\n    assert.deepEqual(F(rs1, { include: [], exclude: ['a1'] }), {\n      open: '24',\n      close: '',\n    })\n    assert.deepEqual(F(rs1, { include: [], exclude: ['x0'] }), {\n      open: '1234',\n      close: '',\n    })\n    assert.deepEqual(F(rs1, { include: [], exclude: ['a1', 'a2'] }), {\n      open: '4',\n      close: '',\n    })\n  })\n\n  it('strinject', () => {\n    const entries = loadTSV('utility-strinject')\n    for (const { cols, row } of entries) {\n      try {\n        const template = cols[0]\n        const values = cols[1] !== '' ? JSON.parse(cols[1]) : undefined\n        assert.deepEqual(strinject(template, values), cols[2] || '')\n      } catch (err) {\n        err.message = `utility-strinject row ${row}: template=${cols[0]} values=${cols[1]} expected=${cols[2]}\\n${err.message}`\n        throw err\n      }\n    }\n  })\n\n})\n"
  },
  {
    "path": "test/variant.test.js",
    "content": "/* Copyright (c) 2013-2022 Richard Rodger and other contributors, MIT License */\n'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert')\n\nconst { Jsonic, JsonicError } = require('..')\n\nconst j = Jsonic\n\ndescribe('variant', function () {\n  it('just-json-happy', () => {\n    let json = Jsonic.make('json')\n\n    assert.deepEqual(json('{\"a\":1}'), { a: 1 })\n    assert.deepEqual(\n      json('{\"a\":1,\"b\":\"x\",\"c\":true,\"d\":{\"e\":[-1.1e2,{\"f\":null}]}}'), { a: 1, b: 'x', c: true, d: { e: [-1.1e2, { f: null }] } })\n    assert.deepEqual(json(' \"a\" '), 'a')\n    assert.deepEqual(json('\\r\\n\\t1.0\\n'), 1.0)\n\n    // NOTE: as per JSON.parse\n    assert.deepEqual(json('{\"a\":1,\"a\":2}'), { a: 2 })\n\n    // console.log(json('{\"a\":1,}'))\n\n    assert.throws(() => json('{a:1}'), /unexpected.*:1:2/s)\n    assert.throws(() => json('{\"a\":1,}'), /unexpected.*:1:8/s)\n    assert.throws(() => json('[a]'), /unexpected.*:1:2/s)\n    assert.throws(() => json('[\"a\",]'), /unexpected.*:1:6/s)\n    assert.throws(() => json('\"a\" # foo'), /unexpected.*:1:5/s)\n    assert.throws(() => json('0xA'), /unexpected.*:1:1/s)\n    assert.throws(() => json('`a`'), /unexpected.*:1:1/s)\n    assert.throws(() => json(\"'a'\"), /unexpected.*:1:1/s)\n    assert.throws(() => json(''), /unexpected.*:1:1/s)\n    assert.throws(() => json('{\"a\":1'), /unexpected.*:1:7/s)\n    assert.throws(() => json('[,a]'), /unexpected.*:1:2/s)\n    assert.throws(() => json(''), /unexpected/s)\n    assert.throws(() => json('00'), /unexpected/s)\n    assert.throws(() => json('{0:1}'), /unexpected/s)\n    assert.throws(() => json('[\"a\"00,\"b\"]'), /unexpected/s)\n    assert.throws(() => json('[{}00,\"b\"]'), /unexpected/s)\n  })\n\n  // TODO: move to plugin\n  /*\n  it('comment-suffix', () => {\n    let js = Jsonic.make()\n\n    let jc = Jsonic.make({\n      comment: {\n        def: {\n          hash: { suffix: 'makeLineMatcher' },\n        },\n      },\n    })\n\n    let tknlogS = []\n    js.sub({\n      lex: (tkn) => {\n        tknlogS.push(tkn)\n      },\n    })\n\n    let tknlogC = []\n    jc.sub({\n      lex: (tkn) => {\n        tknlogC.push(tkn)\n      },\n    })\n\n    assert.deepEqual(js('a#b \\nc'), ['a', 'c'])\n    assert.deepEqual(jc('a#b \\nc'), ['a', 'c'])\n\n    // console.log(''+tknlogS)\n    // console.log(''+tknlogC)\n\n    assert.deepEqual('' + tknlogS, \n      'Token[#TX=10 a 0,1,1],Token[#CM=7 #b  1,1,2],Token[#LN=6 . 4,1,5],Token[#TX=10 c 5,2,1],Token[#ZZ=2  6,2,2],Token[#ZZ=2  6,2,2],Token[#ZZ=2  6,2,2],Token[#ZZ=2  6,2,2]'\n    )\n\n    assert.deepEqual('' + tknlogC, \n      'Token[#TX=10 a 0,1,1],Token[#CM=7 #b  1,1,2],Token[#TX=10 c 5,2,1],Token[#ZZ=2  6,2,2],Token[#ZZ=2  6,2,2],Token[#ZZ=2  6,2,2],Token[#ZZ=2  6,2,2]'\n    )\n\n    tknlogC.length = 0\n    assert.deepEqual(jc('a#b \\n\\n\\nc'), ['a', 'c'])\n    // console.log(''+tknlogC)\n    assert.deepEqual('' + tknlogC, \n      'Token[#TX=10 a 0,1,1],Token[#CM=7 #b  1,1,2],Token[#TX=10 c 7,4,1],Token[#ZZ=2  8,4,2],Token[#ZZ=2  8,4,2],Token[#ZZ=2  8,4,2],Token[#ZZ=2  8,4,2]'\n    )\n  })\n  */\n\n  it('line-lex-single', () => {\n    let j = Jsonic\n    let js = Jsonic.make({\n      line: { single: true },\n    })\n\n    let tknlog = []\n    j.sub({\n      lex: (tkn) => {\n        tknlog.push(tkn)\n      },\n    })\n\n    let tknlogS = []\n    js.sub({\n      lex: (tkn) => {\n        tknlogS.push(tkn)\n      },\n    })\n\n    assert.deepEqual(j('a\\n\\nb'), ['a', 'b'])\n    // console.log(''+tknlog)\n    assert.deepEqual('' + tknlog, \n      'Token[#TX=10 a 0,1,1],' +\n        'Token[#LN=6 .. 1,1,2],' +\n        'Token[#TX=10 b 3,3,1],Token[#ZZ=2  4,3,2],Token[#ZZ=2  4,3,2],Token[#ZZ=2  4,3,2],Token[#ZZ=2  4,3,2]',\n    )\n\n    assert.deepEqual(js('a\\n\\nb'), ['a', 'b'])\n    // console.log(''+tknlogS)\n    assert.deepEqual('' + tknlogS, \n      'Token[#TX=10 a 0,1,1],' +\n        'Token[#LN=6 . 1,1,2],Token[#LN=6 . 2,2,1],' +\n        'Token[#TX=10 b 3,3,1],Token[#ZZ=2  4,3,2],Token[#ZZ=2  4,3,2],Token[#ZZ=2  4,3,2],Token[#ZZ=2  4,3,2]',\n    )\n  })\n})\n"
  },
  {
    "path": "test/web-all.js",
    "content": "// TODO: check missing\nrequire('./jsonic.test.js')\nrequire('./feature.test.js')\n"
  }
]