diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4388d178..3646b431 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,14 @@ on: pull_request: branches: [ main, dev ] +permissions: + contents: read + +env: + # actions/checkout and the setup-* actions still bundle the deprecated + # Node 20 runtime; opt them into Node 24 now (default from 2026-06-02). + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: test-ts: runs-on: ${{ matrix.os }} @@ -14,9 +22,9 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] node-version: [24, latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies @@ -36,9 +44,9 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] node-version: ['24', 'latest'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies @@ -55,9 +63,9 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Run tests @@ -71,9 +79,9 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] go-version: ['1.20', '1.21', '1.22', '1.23', '1.24'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Build @@ -89,7 +97,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: @@ -110,14 +118,18 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.1' - name: Install dependencies working-directory: ./php + # setup-php intermittently writes a malformed github-oauth token to its + # auth.json; a fresh COMPOSER_HOME sidesteps it (no auth needed here). run: composer install + env: + COMPOSER_HOME: ${{ runner.temp }}/composer-home - name: Run tests working-directory: ./php run: vendor/bin/phpunit @@ -128,7 +140,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Rust uses: dtolnay/rust-toolchain@stable - name: Build @@ -144,9 +156,9 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' - name: Run tests @@ -160,7 +172,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] java-version: ['17'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Java ${{ matrix.java-version }} uses: actions/setup-java@v4 with: @@ -173,6 +185,7 @@ jobs: - name: Run tests (Windows) if: runner.os == 'Windows' working-directory: ./kt + shell: pwsh run: .\gradlew.bat test test-java: @@ -182,7 +195,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] java-version: ['17'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Java ${{ matrix.java-version }} uses: actions/setup-java@v4 with: @@ -198,7 +211,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Lua uses: leafo/gh-actions-lua@v10 with: @@ -222,7 +235,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] zig-version: ['0.13.0'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Zig ${{ matrix.zig-version }} uses: mlugg/setup-zig@v1 with: @@ -252,7 +265,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install nlohmann/json (Linux) if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y nlohmann-json3-dev diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c4c4999a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,167 @@ +name: Lint + +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + +permissions: + contents: read + +env: + # actions/checkout and the setup-* actions still bundle the deprecated + # Node 20 runtime; opt them into Node 24 now (default from 2026-06-02). + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + lint-ts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - working-directory: ./ts + run: npm install + - working-directory: ./ts + run: make lint + + lint-js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - working-directory: ./js + run: npm install + - working-directory: ./js + run: make lint + + lint-py: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - working-directory: ./py + run: pip install -r requirements-dev.txt + - working-directory: ./py + run: make lint + + lint-go: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + - name: Install golangci-lint + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v2.5.0 + - working-directory: ./go + run: | + gobin="$(go env GOPATH)/bin" + PATH="$gobin:$PATH" make lint + + lint-rs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + - working-directory: ./rs + run: make lint + + lint-rb: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + working-directory: ./rb + - working-directory: ./rb + run: make lint + + lint-php: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + - working-directory: ./php + # Use a fresh COMPOSER_HOME: setup-php intermittently writes a + # malformed github-oauth token to its auth.json, which makes + # `composer install` abort. We don't need authenticated installs here. + run: composer install + env: + COMPOSER_HOME: ${{ runner.temp }}/composer-home + - working-directory: ./php + run: make lint + + lint-java: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + - working-directory: ./java + run: make lint + + lint-kt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + - working-directory: ./kt + run: ./gradlew detekt ktlintCheck + + lint-lua: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: leafo/gh-actions-lua@v10 + with: + luaVersion: "5.4" + - uses: leafo/gh-actions-luarocks@v4 + - run: luarocks install luacheck + - working-directory: ./lua + run: luacheck src test + + lint-zig: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v1 + with: + version: '0.13.0' + - working-directory: ./zig + run: zig fmt --check src test build.zig + + lint-cpp: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: sudo apt-get update && sudo apt-get install -y nlohmann-json3-dev clang-tidy clang-format + - working-directory: ./cpp + run: make lint + + lint-cs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + - working-directory: ./cs + run: dotnet build VoxgigStruct.csproj -warnaserror -nologo diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..2bbf5415 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,208 @@ +name: Security & Static Analysis + +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + schedule: + # Re-scan weekly so newly disclosed advisories are caught even without a push. + - cron: '0 6 * * 1' + +permissions: + contents: read + +env: + # actions/checkout and the setup-* actions still bundle the deprecated + # Node 20 runtime; opt them into Node 24 now (default from 2026-06-02). + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + # ---- Repo-wide scans (one tool over the whole tree) ---- + + secrets: + name: secret scan (gitleaks) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install gitleaks + run: | + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz" \ + | tar xz gitleaks && sudo mv gitleaks /usr/local/bin/ + - run: gitleaks detect --no-banner --redact --verbose + + dependencies: + name: dependency scan (osv-scanner) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + - run: | + go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest + "$(go env GOPATH)/bin/osv-scanner" scan --config=osv-scanner.toml --recursive . + + sast: + name: SAST (semgrep) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install semgrep + - run: semgrep scan --config p/security-audit --config p/secrets --metrics=off --error . + + workflows: + name: workflow lint (actionlint) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + - run: | + go install github.com/rhysd/actionlint/cmd/actionlint@latest + "$(go env GOPATH)/bin/actionlint" + + shell: + name: shell lint (shellcheck) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: sudo apt-get update && sudo apt-get install -y shellcheck + - run: git ls-files -z '*.sh' | xargs -0 -r shellcheck + + spelling: + name: spell check (cspell) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - run: npx --yes cspell --no-progress --no-summary --gitignore "**/*.md" + + markdown: + name: markdown lint (markdownlint) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - run: npx --yes markdownlint-cli '**/*.md' + + parity: + name: cross-port API parity + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python3 tools/check_parity.py + + # ---- Per-language dependency / security audits ---- + + audit-ts: + name: audit (ts — npm) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - working-directory: ./ts + run: npm install --package-lock-only && make audit + + audit-js: + name: audit (js — npm) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - working-directory: ./js + run: npm install --package-lock-only && make audit + + audit-py: + name: audit (py — pip-audit + bandit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install pip-audit bandit + - working-directory: ./py + run: make audit + + audit-go: + name: audit (go — govulncheck + gosec) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + - run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + go install github.com/securego/gosec/v2/cmd/gosec@latest + - working-directory: ./go + run: | + gobin="$(go env GOPATH)/bin" + PATH="$gobin:$PATH" make audit + + audit-rb: + name: audit (rb — bundler-audit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + working-directory: ./rb + - working-directory: ./rb + run: gem install bundler-audit && make audit + + audit-php: + name: audit (php — composer audit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + - working-directory: ./php + # Fresh COMPOSER_HOME: avoids the malformed github-oauth token that + # setup-php intermittently writes to its auth.json. + run: composer install && make audit + env: + COMPOSER_HOME: ${{ runner.temp }}/composer-home + + audit-rs: + name: audit (rs — cargo audit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo install cargo-audit --locked + - working-directory: ./rs + run: make audit + + audit-cs: + name: audit (cs — dotnet list --vulnerable) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + - working-directory: ./cs + run: make audit diff --git a/.gitignore b/.gitignore index e79b1669..c2391fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -186,3 +186,9 @@ cs/tests/obj/ rs/target/ rs/Cargo.lock +# Lint / static-analysis caches +.ruff_cache/ +.mypy_cache/ +.eslintcache +.detekt/ + diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..c83596da --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,36 @@ +# markdownlint configuration. +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +# +# This config keeps the rules that catch *broken* markdown (reversed links, +# empty links, bad reference definitions, ...) and disables the purely +# cosmetic whitespace/blank-line rules — the docs (READMEs, NOTES, REVIEW, +# PLAN files across every port) are hand-written and don't follow one house +# style for blank lines around headings/lists/tables. +default: true + +MD003: false # heading style (atx vs setext) — author's choice +MD004: false # unordered-list bullet style +MD007: false # unordered-list indentation +MD009: false # trailing spaces +MD012: false # multiple consecutive blank lines +MD013: false # line length — tables and code blocks are wide +MD020: false # space inside closed atx heading +MD022: false # blanks around headings +MD024: # duplicate headings — only flag true siblings + siblings_only: true +MD026: false # trailing punctuation in headings +MD029: false # ordered-list numbering style +MD030: false # spaces after list markers +MD031: false # blanks around fenced code blocks +MD032: false # blanks around lists +MD033: false # inline HTML +MD034: false # bare URLs (used freely in NOTES/REVIEW) +MD035: false # horizontal-rule style +MD036: false # emphasis used instead of a heading +MD040: false # language tag on fenced code blocks +MD041: false # first line must be a top-level heading +MD046: false # code-block style (fenced vs indented) +MD047: false # single trailing newline +MD056: false # table column count (some tables vary intentionally) +MD058: false # blanks around tables +MD060: false # table column style diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..844dc914 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,10 @@ +node_modules/ +**/node_modules/ +vendor/ +**/vendor/ +dist/ +dist-test/ +target/ +build/ +**/build/ +.gradle/ diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 00000000..228a0e4f --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,20 @@ +node_modules/ +vendor/ +dist/ +dist-test/ +target/ +build/ +.gradle/ +bin/ +obj/ +.zig-cache/ +zig-out/ +coverage/ +build/test/ +*.lock +package-lock.json +Cargo.lock +composer.lock +Gemfile.lock +gradlew +gradlew.bat diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 00000000..d7205da4 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,3 @@ +# shellcheck configuration. +# Treat warnings and errors as failures; info/style suggestions are advisory. +severity=warning diff --git a/Makefile b/Makefile index b6edc6a8..7331f044 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,25 @@ # Usage: # make test — run tests for all languages # make test-zig — run tests for one language +# make lint — run code-quality tooling (linters/formatters) for all languages +# make lint-go — run code-quality tooling for one language +# make audit — run dependency / supply-chain audits for all languages +# make scan — run repo-wide static analysis (secrets, SAST, deps, workflows, ...) +# make analyze — lint + audit + scan # make inspect — show version info for all languages # make clean — clean all build artifacts LANGS = ts js py go rb php lua zig java rs -.PHONY: all inspect build test clean reset +# Languages that ship a `make lint` target (the test/build aggregates above +# deliberately omit cpp/cs/kt, but their lint targets exist). +LINT_LANGS = ts js py go rb php lua zig java rs cpp cs kt + +# Languages whose ecosystem has a dependency / supply-chain audit tool wired up. +AUDIT_LANGS = ts js py go rb php rs cs + +.PHONY: all inspect build test lint audit scan analyze clean reset \ + scan-secrets scan-deps scan-sast scan-workflows scan-shell scan-spelling scan-docs scan-parity all: test @@ -26,6 +39,14 @@ test-%: @echo "======== $* ========" @$(MAKE) -C $* test +lint-%: + @echo "======== lint: $* ========" + @$(MAKE) -C $* lint + +audit-%: + @echo "======== audit: $* ========" + @$(MAKE) -C $* audit + clean-%: @echo "======== $* ========" @$(MAKE) -C $* clean 2>/dev/null || echo "(no clean target)" @@ -39,5 +60,49 @@ reset-%: inspect: $(LANGS:%=inspect-%) build: $(LANGS:%=build-%) test: $(LANGS:%=test-%) +lint: $(LINT_LANGS:%=lint-%) +audit: $(AUDIT_LANGS:%=audit-%) clean: $(LANGS:%=clean-%) reset: $(LANGS:%=reset-%) + +# ---- Repo-wide static analysis (not per-language) ---- +# These need their tools on PATH: +# gitleaks, osv-scanner, semgrep, actionlint, shellcheck, cspell, markdownlint + +scan: scan-secrets scan-deps scan-sast scan-workflows scan-shell scan-parity scan-spelling scan-docs + +scan-secrets: + @echo "======== scan: secrets (gitleaks) ========" + gitleaks detect --no-banner --redact --verbose + +scan-deps: + @echo "======== scan: dependencies (osv-scanner) ========" + osv-scanner scan --config=osv-scanner.toml --recursive . + +scan-sast: + @echo "======== scan: SAST (semgrep) ========" + semgrep scan --config p/security-audit --config p/secrets --metrics=off --error . + +scan-workflows: + @echo "======== scan: GitHub workflows (actionlint) ========" + actionlint + +scan-shell: + @echo "======== scan: shell scripts (shellcheck) ========" + @files=$$(git ls-files '*.sh'); \ + if [ -n "$$files" ]; then shellcheck $$files; else echo "(no shell scripts)"; fi + +scan-spelling: + @echo "======== scan: spelling (cspell) ========" + cspell --no-progress --no-summary --gitignore "**/*.md" + +scan-docs: + @echo "======== scan: markdown (markdownlint) ========" + markdownlint '**/*.md' + +scan-parity: + @echo "======== scan: cross-port API parity ========" + python3 tools/check_parity.py + +# Everything: linters/formatters + dependency audits + repo-wide scans. +analyze: lint audit scan diff --git a/README.md b/README.md index 1d62adcb..817be357 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ syntax), and language-specific notes: | Ruby | Complete | [`rb/README.md`](./rb/README.md) | | Lua | Complete | [`lua/README.md`](./lua/README.md) | | Rust | Complete | [`rs/README.md`](./rs/README.md) | -| C# | In progress| [`cs/README.md`](./cs/README.md) | -| Zig | In progress| [`zig/README.md`](./zig/README.md) | +| C# | Complete | [`cs/README.md`](./cs/README.md) | +| Zig | Complete | [`zig/README.md`](./zig/README.md) | | Java | Partial | [`java/README.md`](./java/README.md) | | C++ | Partial | [`cpp/README.md`](./cpp/README.md) | @@ -401,10 +401,57 @@ Each language directory contains: - the implementation source, - a test runner that consumes `build/test/*.jsonic`, -- a `Makefile` with at minimum a `make test` target, +- a `Makefile` with at minimum `make test` and `make lint` targets, - a `DOCS.md` with the per-language guide, - `NOTES.md` and `REVIEW.md` with implementation history. +`make lint` runs that language's industry-standard code-quality tooling +(linter + formatter check): + +| Language | Lint / static analysis | Format check | +|------------|-----------------------------------|-----------------------| +| TypeScript | ESLint (`typescript-eslint`) | Prettier | +| JavaScript | ESLint | Prettier | +| Python | Ruff, mypy | Ruff format | +| Go | golangci-lint, `go vet` | `gofmt` | +| Ruby | RuboCop | RuboCop | +| PHP | PHP_CodeSniffer (PSR-12), PHPStan | PHP_CodeSniffer | +| Rust | Clippy | `cargo fmt` | +| Java | Checkstyle, SpotBugs | Checkstyle | +| C++ | clang-tidy | clang-format | +| Lua | luacheck | StyLua | +| Zig | `zig build` (compiler) | `zig fmt` | +| C# | Roslyn analyzers | `dotnet format` | +| Kotlin | detekt | ktlint | + +Run everything with `make lint` at the repo root, or one language with +`make lint-` (e.g. `make lint-go`). + +Beyond linting there are two more analysis stages: + +- **`make audit`** — per-language dependency / supply-chain scanning: + `npm audit` (ts/js), `pip-audit` + Bandit (py), `govulncheck` + `gosec` + (go), `bundler-audit` (rb), `composer audit` (php), `cargo audit` (rs), + `dotnet list --vulnerable` (cs). +- **`make scan`** — repo-wide static analysis: secret scanning + ([gitleaks]), SAST ([Semgrep]), known-vulnerability scanning across all + lockfiles ([osv-scanner]), GitHub-workflow linting ([actionlint]), shell + linting ([shellcheck]), spell checking ([cspell]), markdown linting + ([markdownlint]), and a cross-port API-parity check + (`tools/check_parity.py`). + +`make analyze` runs all three (`lint` + `audit` + `scan`). CI runs these +in [`.github/workflows/lint.yml`](./.github/workflows/lint.yml) and +[`.github/workflows/security.yml`](./.github/workflows/security.yml). + +[gitleaks]: https://github.com/gitleaks/gitleaks +[Semgrep]: https://semgrep.dev/ +[osv-scanner]: https://google.github.io/osv-scanner/ +[actionlint]: https://github.com/rhysd/actionlint +[shellcheck]: https://www.shellcheck.net/ +[cspell]: https://cspell.org/ +[markdownlint]: https://github.com/DavidAnson/markdownlint + ## License diff --git a/REPORT.md b/REPORT.md index dd61427b..5445e6ef 100644 --- a/REPORT.md +++ b/REPORT.md @@ -2,7 +2,7 @@ **Date**: 2026-04-12 **Canonical**: TypeScript (`ts/`) -**Languages**: JS, Python, Go, PHP, Ruby, Lua, Java, C++ +**Languages**: JS, Python, Go, PHP, Ruby, Lua, Rust, Zig, C#, Java, C++ ## Summary @@ -20,6 +20,17 @@ | **java** | 40 | 15 | 2 | 1178/1178 corpus | Complete | | **cpp** | 40 | 15 | 2 | 1178/1178 corpus | Complete | | **cs** | 40 | 15 | 2 | 1178/1178 corpus | Complete | +| **zig** | 40 | 15 | 2 | 60/60 corpus sets | Complete | + +\*\* Zig: full TS-canonical parity, allocator-first signatures, a +pointer-stable `JsonValue` union with heap `MapRef`/`ListRef` wrappers. +All transform commands, validate checkers and select operators, the +`Injection` state machine, and the injection helpers +(`checkPlacement`/`injectorArgs`/`injectChild`) are wired; the full corpus +runs as 60 `test` blocks (`zig build test`; `zig fmt --check` clean). The +`test` runner crashes with SIGSEGV during arena teardown *after* all tests +pass — a known `*MapRef`/`*ListRef` cross-reference issue — so `make test` +treats "N/N tests passed" as success. \*\* Rust: full TS-canonical parity. Idiomatic `snake_case` API (`get_path`, `is_node`, …; see `rs/README.md` for the name table), `Rc` diff --git a/build/test.sh b/build/test.sh index f6615ecd..b78f521e 100755 --- a/build/test.sh +++ b/build/test.sh @@ -8,15 +8,13 @@ done SCRIPT_DIR=$(cd "$(dirname "$SCRIPT_PATH")" && pwd) echo -echo === TS === -cd $SCRIPT_DIR/../ts +echo "=== TS ===" +cd "$SCRIPT_DIR/../ts" || exit 1 npm test echo -echo === PY === -cd $SCRIPT_DIR/../py +echo "=== PY ===" +cd "$SCRIPT_DIR/../py" || exit 1 python -m unittest discover -s tests - echo - diff --git a/cpp/.clang-format b/cpp/.clang-format new file mode 100644 index 00000000..2d9e4219 --- /dev/null +++ b/cpp/.clang-format @@ -0,0 +1,12 @@ +# clang-format configuration for the C++ port. +# Base style with 2-space indent and a 100-column limit. +BasedOnStyle: LLVM +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +ColumnLimit: 100 +PointerAlignment: Left +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +SpaceAfterCStyleCast: true +SortIncludes: false diff --git a/cpp/.clang-tidy b/cpp/.clang-tidy new file mode 100644 index 00000000..019967d1 --- /dev/null +++ b/cpp/.clang-tidy @@ -0,0 +1,37 @@ +# clang-tidy configuration for the C++ port. +# A pragmatic check set: bug-prone patterns, portability, performance, +# and a subset of readability. Verbose / style-only checks are disabled. +# +# A few checks are disabled because they conflict with this being a faithful, +# line-by-line port of the canonical TypeScript implementation: +# - bugprone-branch-clone: several if/else dispatch chains intentionally +# share branch bodies to stay structurally aligned with the reference. +# - bugprone-exception-escape: the smoke / corpus `main()` deliberately lets +# exceptions propagate to the runtime (which reports them); wrapping every +# test entry point in try/catch adds noise without value. +# - performance-unnecessary-value-param / -copy-initialization: the port +# passes the small `Value` handle (and `std::function` callbacks) by value +# to mirror the reference signatures, and keeps deliberate defensive copies +# (e.g. the `$REF` original spec) that clang-tidy can't see are required. +# - performance-inefficient-string-concatenation: the URL-join / pretty-print +# helpers build strings in a way the reference does; the micro-optimisation +# isn't worth diverging from it. +Checks: > + bugprone-*, + performance-*, + portability-*, + modernize-use-nullptr, + modernize-use-override, + modernize-use-using, + readability-misleading-indentation, + readability-redundant-control-flow, + -bugprone-easily-swappable-parameters, + -bugprone-narrowing-conversions, + -bugprone-branch-clone, + -bugprone-exception-escape, + -performance-unnecessary-value-param, + -performance-unnecessary-copy-initialization, + -performance-inefficient-string-concatenation +WarningsAsErrors: '' +HeaderFilterRegex: 'src/.*' +FormatStyle: file diff --git a/cpp/Makefile b/cpp/Makefile index 1abb366d..f7d448b1 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -4,7 +4,9 @@ EXTRA_INC := -I $(JSON_INC) -I /opt/homebrew/include -I /usr/local/include CXXFLAGS_COMMON := --std=c++17 -Wall -Wextra -Wno-unused-parameter -.PHONY: build smoke test corpus check_leak sanitize clean reset inspect +CPP_SOURCES := $(wildcard src/*.hpp tests/*.hpp tests/*.cpp) + +.PHONY: build smoke test corpus check_leak sanitize lint tidy format-check clean reset inspect # Default: compile and run the smoke test plus the full corpus driver. build: smoke corpus @@ -26,6 +28,16 @@ check_leak: corpus sanitize: g++ tests/struct_corpus_test.cpp $(CXXFLAGS_COMMON) -fsanitize=address,undefined -g -I ./src -I ./tests $(EXTRA_INC) -o sanitize.out && ./sanitize.out +# Code quality: clang-tidy (static analysis) + clang-format check. +lint: tidy format-check + +tidy: + clang-tidy tests/struct_corpus_test.cpp tests/smoke.cpp -- \ + $(CXXFLAGS_COMMON) -I ./src -I ./tests $(EXTRA_INC) + +format-check: + clang-format --dry-run --Werror $(CPP_SOURCES) + clean: rm -f out.out corpus.out smoke.out sanitize.out corpus-scoreboard.json diff --git a/cpp/src/utility_decls.hpp b/cpp/src/utility_decls.hpp index b952de39..76ddc348 100644 --- a/cpp/src/utility_decls.hpp +++ b/cpp/src/utility_decls.hpp @@ -9,67 +9,63 @@ #include - using json = nlohmann::json; -// TODO: Don't use std::vector due to performance concerns as it is creating double copies, being the initializer_list first. However, this improvement is optimal due to the way the runner is written where arguments are read dynamically from parsed json +// TODO: Don't use std::vector due to performance concerns as it is creating double copies, being +// the initializer_list first. However, this improvement is optimal due to the way the runner is +// written where arguments are read dynamically from parsed json using args_container = std::vector; -using function_pointer = json(*)(args_container&&); +using function_pointer = json (*)(args_container&&); using JsonFunction = std::function; // NOTE: Standard Library for now -template -using hash_table = std::unordered_map; +template using hash_table = std::unordered_map; class Utility { - private: - hash_table table; - - public: - Utility() = default; +private: + hash_table table; - void set_key(const std::string&, function_pointer); +public: + Utility() = default; - function_pointer& get_key(const std::string&); + void set_key(const std::string&, function_pointer); - void set_table(hash_table&&); + function_pointer& get_key(const std::string&); - function_pointer& operator[](const std::string&); + void set_table(hash_table&&); - ~Utility() = default; + function_pointer& operator[](const std::string&); + ~Utility() = default; }; class Provider { - public: +public: + // NOTE: More dynamic approach compared to function overloading + Provider(const json&); - // NOTE: More dynamic approach compared to function overloading - Provider(const json&); + static Provider test(const json&); + static Provider test(void); - static Provider test(const json&); - static Provider test(void); - - hash_table utility(void); + hash_table utility(void); }; namespace Auxiliary { - void validate_int(const std::string& str) { - const char* const_str = str.c_str(); - - while(*const_str != 0) { - if(*const_str >= '0' && *const_str <= '9') { - const_str++; - continue; - } - - throw std::runtime_error("Invalid Integer"); +void validate_int(const std::string& str) { + const char* const_str = str.c_str(); + while (*const_str != 0) { + if (*const_str >= '0' && *const_str <= '9') { + const_str++; + continue; } + throw std::runtime_error("Invalid Integer"); } - } +} // namespace Auxiliary + #endif diff --git a/cpp/src/value.hpp b/cpp/src/value.hpp index 7d128029..6fa21b54 100644 --- a/cpp/src/value.hpp +++ b/cpp/src/value.hpp @@ -46,64 +46,54 @@ using Map = OrderedMap; // Injector: invoked for each `$NAME` reference (transform_*, validate_*, // select_*). Mirrors TS `Injector`. -using Injector = std::function; +using Injector = std::function; // Modify: optional value-mutation hook used by inject/transform/validate. // Mirrors TS `Modify`. -using Modify = std::function; +using Modify = std::function; // Sentinel: tagged marker compared by pointer identity. Two static instances // (SKIP / DELETE) are exposed below. struct Sentinel { - const char* name; // "SKIP" or "DELETE" + const char* name; // "SKIP" or "DELETE" }; // Type bit-flags. Mirrors TS lines 110–127. -constexpr int T_any = static_cast((1u << 31) - 1); -constexpr int T_noval = 1 << 30; // undefined / absent -constexpr int T_boolean = 1 << 29; -constexpr int T_decimal = 1 << 28; -constexpr int T_integer = 1 << 27; -constexpr int T_number = 1 << 26; -constexpr int T_string = 1 << 25; +constexpr int T_any = static_cast((1u << 31) - 1); +constexpr int T_noval = 1 << 30; // undefined / absent +constexpr int T_boolean = 1 << 29; +constexpr int T_decimal = 1 << 28; +constexpr int T_integer = 1 << 27; +constexpr int T_number = 1 << 26; +constexpr int T_string = 1 << 25; constexpr int T_function = 1 << 24; -constexpr int T_symbol = 1 << 23; -constexpr int T_null = 1 << 22; -constexpr int T_list = 1 << 14; -constexpr int T_map = 1 << 13; +constexpr int T_symbol = 1 << 23; +constexpr int T_null = 1 << 22; +constexpr int T_list = 1 << 14; +constexpr int T_map = 1 << 13; constexpr int T_instance = 1 << 12; -constexpr int T_scalar = 1 << 7; -constexpr int T_node = 1 << 6; +constexpr int T_scalar = 1 << 7; +constexpr int T_node = 1 << 6; // Index → name for typify→typename. Order must match TS TYPENAME table. inline const std::string& typename_table(int idx) { static const std::string TABLE[26] = { - "any", "nil", "boolean", "decimal", "integer", "number", "string", - "function", "symbol", "null", - "", "", "", - "", "", "", "", - "list", "map", "instance", - "", "", "", "", - "scalar", "node", + "any", "nil", "boolean", "decimal", "integer", "number", "string", "function", "symbol", + "null", "", "", "", "", "", "", "", "list", + "map", "instance", "", "", "", "", "scalar", "node", }; static const std::string EMPTY = ""; - if (idx < 0 || idx >= 26) return EMPTY; + if (idx < 0 || idx >= 26) + return EMPTY; return TABLE[idx]; } // Inject mode bitfield. -constexpr int M_KEYPRE = 1; +constexpr int M_KEYPRE = 1; constexpr int M_KEYPOST = 2; -constexpr int M_VAL = 4; +constexpr int M_VAL = 4; constexpr int MAXDEPTH = 32; @@ -112,20 +102,19 @@ constexpr int MAXDEPTH = 32; // =========================================================================== class Value { - public: - using Storage = std::variant< - std::monostate, // 0 - undefined / T_noval - std::nullptr_t, // 1 - JSON null - bool, // 2 - boolean - int64_t, // 3 - integer - double, // 4 - decimal - std::string, // 5 - string - std::shared_ptr, // 6 - list (reference-stable) - std::shared_ptr, // 7 - map (reference-stable) - Injector, // 8 - $-command handler - Modify, // 9 - modify hook - const Sentinel* // 10 - SKIP / DELETE marker - >; +public: + using Storage = std::variant, // 6 - list (reference-stable) + std::shared_ptr, // 7 - map (reference-stable) + Injector, // 8 - $-command handler + Modify, // 9 - modify hook + const Sentinel* // 10 - SKIP / DELETE marker + >; Storage storage; @@ -160,30 +149,34 @@ class Value { // ---- Type predicates ---- bool is_undef() const { return std::holds_alternative(storage); } - bool is_null() const { return std::holds_alternative(storage); } - bool is_bool() const { return std::holds_alternative(storage); } - bool is_int() const { return std::holds_alternative(storage); } - bool is_double() const{ return std::holds_alternative(storage); } - bool is_number() const{ return is_int() || is_double(); } - bool is_string() const{ return std::holds_alternative(storage); } - bool is_list() const { return std::holds_alternative>(storage); } - bool is_map() const { return std::holds_alternative>(storage); } - bool is_node() const { return is_list() || is_map(); } + bool is_null() const { return std::holds_alternative(storage); } + bool is_bool() const { return std::holds_alternative(storage); } + bool is_int() const { return std::holds_alternative(storage); } + bool is_double() const { return std::holds_alternative(storage); } + bool is_number() const { return is_int() || is_double(); } + bool is_string() const { return std::holds_alternative(storage); } + bool is_list() const { return std::holds_alternative>(storage); } + bool is_map() const { return std::holds_alternative>(storage); } + bool is_node() const { return is_list() || is_map(); } bool is_injector() const { return std::holds_alternative(storage); } - bool is_modify() const { return std::holds_alternative(storage); } - bool is_func() const { return is_injector() || is_modify(); } + bool is_modify() const { return std::holds_alternative(storage); } + bool is_func() const { return is_injector() || is_modify(); } bool is_sentinel() const { return std::holds_alternative(storage); } // ---- Accessors (assume the variant alternative is correct) ---- bool as_bool() const { return std::get(storage); } int64_t as_int() const { - if (is_int()) return std::get(storage); - if (is_double()) return static_cast(std::get(storage)); + if (is_int()) + return std::get(storage); + if (is_double()) + return static_cast(std::get(storage)); return 0; } double as_double() const { - if (is_double()) return std::get(storage); - if (is_int()) return static_cast(std::get(storage)); + if (is_double()) + return std::get(storage); + if (is_int()) + return static_cast(std::get(storage)); return 0.0; } const std::string& as_string() const { return std::get(storage); } @@ -214,8 +207,12 @@ inline const Sentinel& delete_inst() { return S; } -inline Value SKIP() { return Value(&skip_inst()); } -inline Value DELETE_V() { return Value(&delete_inst()); } +inline Value SKIP() { + return Value(&skip_inst()); +} +inline Value DELETE_V() { + return Value(&delete_inst()); +} inline bool is_skip(const Value& v) { return v.is_sentinel() && v.as_sentinel() == &skip_inst(); @@ -234,7 +231,7 @@ inline bool is_delete(const Value& v) { // hash randomisation. class OrderedMap { - public: +public: using Entry = std::pair; using Entries = std::vector; @@ -247,24 +244,25 @@ class OrderedMap { size_t size() const { return entries_.size(); } bool empty() const { return entries_.empty(); } - bool contains(const std::string& key) const { - return index_.find(key) != index_.end(); - } + bool contains(const std::string& key) const { return index_.find(key) != index_.end(); } Value* find(const std::string& key) { auto it = index_.find(key); - if (it == index_.end()) return nullptr; + if (it == index_.end()) + return nullptr; return &entries_[it->second].second; } const Value* find(const std::string& key) const { auto it = index_.find(key); - if (it == index_.end()) return nullptr; + if (it == index_.end()) + return nullptr; return &entries_[it->second].second; } Value& operator[](const std::string& key) { auto it = index_.find(key); - if (it != index_.end()) return entries_[it->second].second; + if (it != index_.end()) + return entries_[it->second].second; index_.emplace(key, entries_.size()); entries_.emplace_back(key, Value()); return entries_.back().second; @@ -282,7 +280,8 @@ class OrderedMap { bool erase(const std::string& key) { auto it = index_.find(key); - if (it == index_.end()) return false; + if (it == index_.end()) + return false; size_t idx = it->second; entries_.erase(entries_.begin() + idx); rebuild_index_(); @@ -296,27 +295,30 @@ class OrderedMap { // Iteration is over the insertion-ordered entries. Entries::iterator begin() { return entries_.begin(); } - Entries::iterator end() { return entries_.end(); } + Entries::iterator end() { return entries_.end(); } Entries::const_iterator begin() const { return entries_.begin(); } - Entries::const_iterator end() const { return entries_.end(); } + Entries::const_iterator end() const { return entries_.end(); } const Entries& entries() const { return entries_; } Entries& entries() { return entries_; } bool operator==(const OrderedMap& other) const { - if (entries_.size() != other.entries_.size()) return false; + if (entries_.size() != other.entries_.size()) + return false; // Order-sensitive equality. For test comparison we usually want // order-insensitive, but Value::operator== on shared_ptr calls this; // the test runner uses its own normalising deep_equal. for (size_t i = 0; i < entries_.size(); i++) { - if (entries_[i].first != other.entries_[i].first) return false; - if (entries_[i].second != other.entries_[i].second) return false; + if (entries_[i].first != other.entries_[i].first) + return false; + if (entries_[i].second != other.entries_[i].second) + return false; } return true; } bool operator!=(const OrderedMap& other) const { return !(*this == other); } - private: +private: Entries entries_; std::unordered_map index_; @@ -344,8 +346,7 @@ inline Value Value::map() { inline bool operator==(const Value& a, const Value& b) { // Sentinel alternative: pointer identity only. if (a.is_sentinel() || b.is_sentinel()) { - return a.is_sentinel() && b.is_sentinel() - && a.as_sentinel() == b.as_sentinel(); + return a.is_sentinel() && b.is_sentinel() && a.as_sentinel() == b.as_sentinel(); } // Number cross-type equality: integer-valued double == int. if (a.is_number() && b.is_number()) { @@ -363,19 +364,25 @@ inline bool operator==(const Value& a, const Value& b) { if (a.is_list()) { auto la = a.as_list(); auto lb = b.as_list(); - if (la == lb) return true; // same shared instance - if (!la || !lb) return false; - if (la->size() != lb->size()) return false; + if (la == lb) + return true; // same shared instance + if (!la || !lb) + return false; + if (la->size() != lb->size()) + return false; for (size_t i = 0; i < la->size(); i++) { - if ((*la)[i] != (*lb)[i]) return false; + if ((*la)[i] != (*lb)[i]) + return false; } return true; } if (a.is_map()) { auto ma = a.as_map(); auto mb = b.as_map(); - if (ma == mb) return true; - if (!ma || !mb) return false; + if (ma == mb) + return true; + if (!ma || !mb) + return false; return *ma == *mb; } // Function alternatives: not meaningfully comparable; return false unless @@ -389,10 +396,14 @@ inline bool operator==(const Value& a, const Value& b) { // std::function alternatives have a deleted operator== under libc++ (C++17+), // which causes a hard compile error even though those branches are unreachable // here (we already short-circuited above). - if (a.is_undef()) return true; // both monostate - if (a.is_null()) return true; // both nullptr_t - if (a.is_bool()) return a.as_bool() == b.as_bool(); - if (a.is_string()) return a.as_string() == b.as_string(); + if (a.is_undef()) + return true; // both monostate + if (a.is_null()) + return true; // both nullptr_t + if (a.is_bool()) + return a.as_bool() == b.as_bool(); + if (a.is_string()) + return a.as_string() == b.as_string(); // Numbers were already handled by the is_number() branch above. return false; } @@ -402,31 +413,44 @@ inline bool operator==(const Value& a, const Value& b) { // =========================================================================== inline int typify(const Value& v) { - if (v.is_undef()) return T_noval; - if (v.is_null()) return T_scalar | T_null; - if (v.is_bool()) return T_scalar | T_boolean; - if (v.is_int()) return T_scalar | T_number | T_integer; + if (v.is_undef()) + return T_noval; + if (v.is_null()) + return T_scalar | T_null; + if (v.is_bool()) + return T_scalar | T_boolean; + if (v.is_int()) + return T_scalar | T_number | T_integer; if (v.is_double()) { double d = v.as_double(); - if (std::isnan(d)) return T_noval; + if (std::isnan(d)) + return T_noval; return T_scalar | T_number | T_decimal; } - if (v.is_string()) return T_scalar | T_string; - if (v.is_list()) return T_node | T_list; - if (v.is_map()) return T_node | T_map; - if (v.is_func()) return T_scalar | T_function; - if (v.is_sentinel()) return T_node | T_map; // SKIP/DELETE behave as maps + if (v.is_string()) + return T_scalar | T_string; + if (v.is_list()) + return T_node | T_list; + if (v.is_map()) + return T_node | T_map; + if (v.is_func()) + return T_scalar | T_function; + if (v.is_sentinel()) + return T_node | T_map; // SKIP/DELETE behave as maps return T_any; } // Highest set bit wins (smallest leading-zero count). Mirrors TS typename. inline std::string typename_str(int t) { - if (t == 0) return "any"; + if (t == 0) + return "any"; // Equivalent to Math.clz32(t) on a 32-bit unsigned. uint32_t u = static_cast(t); int idx = 0; - while (idx < 32 && (u & (1u << (31 - idx))) == 0) idx++; - if (idx >= 26) return "any"; + while (idx < 32 && (u & (1u << (31 - idx))) == 0) + idx++; + if (idx >= 26) + return "any"; const std::string& s = typename_table(idx); return s.empty() ? "any" : s; } @@ -446,24 +470,31 @@ inline std::string typename_str(const Value& v) { inline Value clone(const Value& v); inline std::shared_ptr clone_list(const std::shared_ptr& src) { - if (!src) return nullptr; + if (!src) + return nullptr; auto out = std::make_shared(); out->reserve(src->size()); - for (const auto& e : *src) out->push_back(clone(e)); + for (const auto& e : *src) + out->push_back(clone(e)); return out; } inline std::shared_ptr clone_map(const std::shared_ptr& src) { - if (!src) return nullptr; + if (!src) + return nullptr; auto out = std::shared_ptr(new Map()); - for (const auto& [k, v] : *src) out->set(k, clone(v)); + for (const auto& [k, v] : *src) + out->set(k, clone(v)); return out; } inline Value clone(const Value& v) { - if (v.is_sentinel()) return v; // preserve identity - if (v.is_list()) return Value(clone_list(v.as_list())); - if (v.is_map()) return Value(clone_map(v.as_map())); + if (v.is_sentinel()) + return v; // preserve identity + if (v.is_list()) + return Value(clone_list(v.as_list())); + if (v.is_map()) + return Value(clone_map(v.as_map())); // Scalars / undefined / null / function: copy variant alternative. return v; } @@ -473,16 +504,22 @@ inline Value clone(const Value& v) { // =========================================================================== inline int64_t size_of(const Value& v) { - if (v.is_list()) return static_cast(v.as_list()->size()); - if (v.is_map()) return static_cast(v.as_map()->size()); - if (v.is_string()) return static_cast(v.as_string().size()); - if (v.is_int()) return static_cast(std::floor(static_cast(v.as_int()))); - if (v.is_double()) return static_cast(std::floor(v.as_double())); - if (v.is_bool()) return v.as_bool() ? 1 : 0; + if (v.is_list()) + return static_cast(v.as_list()->size()); + if (v.is_map()) + return static_cast(v.as_map()->size()); + if (v.is_string()) + return static_cast(v.as_string().size()); + if (v.is_int()) + return static_cast(std::floor(static_cast(v.as_int()))); + if (v.is_double()) + return static_cast(std::floor(v.as_double())); + if (v.is_bool()) + return v.as_bool() ? 1 : 0; return 0; } -} // namespace structlib -} // namespace voxgig +} // namespace structlib +} // namespace voxgig -#endif // VOXGIG_STRUCT_VALUE_HPP +#endif // VOXGIG_STRUCT_VALUE_HPP diff --git a/cpp/src/value_io.hpp b/cpp/src/value_io.hpp index 35244dd8..279a23e4 100644 --- a/cpp/src/value_io.hpp +++ b/cpp/src/value_io.hpp @@ -23,16 +23,23 @@ namespace structlib { // ---- nlohmann::json -> Value ---- inline Value from_njson(const nlohmann::json& j) { - if (j.is_null()) return Value(nullptr); - if (j.is_boolean()) return Value(j.get()); - if (j.is_number_integer()) return Value(j.get()); - if (j.is_number_unsigned()) return Value(static_cast(j.get())); - if (j.is_number_float()) return Value(j.get()); - if (j.is_string()) return Value(j.get()); + if (j.is_null()) + return Value(nullptr); + if (j.is_boolean()) + return Value(j.get()); + if (j.is_number_integer()) + return Value(j.get()); + if (j.is_number_unsigned()) + return Value(static_cast(j.get())); + if (j.is_number_float()) + return Value(j.get()); + if (j.is_string()) + return Value(j.get()); if (j.is_array()) { auto out = std::make_shared(); out->reserve(j.size()); - for (const auto& el : j) out->push_back(from_njson(el)); + for (const auto& el : j) + out->push_back(from_njson(el)); return Value(std::move(out)); } if (j.is_object()) { @@ -42,26 +49,34 @@ inline Value from_njson(const nlohmann::json& j) { } return Value(std::move(out)); } - return Value(); // undefined fallback + return Value(); // undefined fallback } // ---- Value -> nlohmann::json (for serialisation; drops functions) ---- inline nlohmann::json to_njson(const Value& v) { - if (v.is_undef()) return nullptr; // serialise undefined as null - if (v.is_null()) return nullptr; - if (v.is_bool()) return v.as_bool(); - if (v.is_int()) return v.as_int(); - if (v.is_double()) return v.as_double(); - if (v.is_string()) return v.as_string(); + if (v.is_undef()) + return nullptr; // serialise undefined as null + if (v.is_null()) + return nullptr; + if (v.is_bool()) + return v.as_bool(); + if (v.is_int()) + return v.as_int(); + if (v.is_double()) + return v.as_double(); + if (v.is_string()) + return v.as_string(); if (v.is_list()) { nlohmann::json out = nlohmann::json::array(); - for (const auto& e : *v.as_list()) out.push_back(to_njson(e)); + for (const auto& e : *v.as_list()) + out.push_back(to_njson(e)); return out; } if (v.is_map()) { nlohmann::json out = nlohmann::json::object(); - for (const auto& [k, e] : *v.as_map()) out[k] = to_njson(e); + for (const auto& [k, e] : *v.as_map()) + out[k] = to_njson(e); return out; } if (v.is_sentinel()) { @@ -81,7 +96,8 @@ inline Value parse_json(const std::string& text) { inline Value parse_json_file(const std::string& path) { std::ifstream f(path); - if (!f) throw std::runtime_error("Cannot open " + path); + if (!f) + throw std::runtime_error("Cannot open " + path); std::stringstream ss; ss << f.rdbuf(); return parse_json(ss.str()); @@ -91,7 +107,7 @@ inline std::string dump_json(const Value& v, int indent = -1) { return to_njson(v).dump(indent); } -} // namespace structlib -} // namespace voxgig +} // namespace structlib +} // namespace voxgig -#endif // VOXGIG_STRUCT_VALUE_IO_HPP +#endif // VOXGIG_STRUCT_VALUE_IO_HPP diff --git a/cpp/src/voxgig_struct.hpp b/cpp/src/voxgig_struct.hpp index 137cc5c4..9d7bc115 100644 --- a/cpp/src/voxgig_struct.hpp +++ b/cpp/src/voxgig_struct.hpp @@ -32,17 +32,50 @@ namespace structlib { // String / regex constants // =========================================================================== -inline const std::string& S_MT() { static const std::string s = ""; return s; } -inline const std::string& S_DT() { static const std::string s = "."; return s; } -inline const std::string& S_DTOP() { static const std::string s = "$TOP"; return s; } -inline const std::string& S_DSPEC() { static const std::string s = "$SPEC"; return s; } -inline const std::string& S_DKEY() { static const std::string s = "$KEY"; return s; } -inline const std::string& S_DERRS() { static const std::string s = "$ERRS"; return s; } -inline const std::string& S_BKEY() { static const std::string s = "`$KEY`"; return s; } -inline const std::string& S_BANNO() { static const std::string s = "`$ANNO`"; return s; } -inline const std::string& S_BVAL() { static const std::string s = "`$VAL`"; return s; } -inline const std::string& S_BEXACT(){ static const std::string s = "`$EXACT`"; return s; } -inline const std::string& S_BOPEN() { static const std::string s = "`$OPEN`"; return s; } +inline const std::string& S_MT() { + static const std::string s = ""; + return s; +} +inline const std::string& S_DT() { + static const std::string s = "."; + return s; +} +inline const std::string& S_DTOP() { + static const std::string s = "$TOP"; + return s; +} +inline const std::string& S_DSPEC() { + static const std::string s = "$SPEC"; + return s; +} +inline const std::string& S_DKEY() { + static const std::string s = "$KEY"; + return s; +} +inline const std::string& S_DERRS() { + static const std::string s = "$ERRS"; + return s; +} +inline const std::string& S_BKEY() { + static const std::string s = "`$KEY`"; + return s; +} +inline const std::string& S_BANNO() { + static const std::string s = "`$ANNO`"; + return s; +} +inline const std::string& S_BVAL() { + static const std::string s = "`$VAL`"; + return s; +} +inline const std::string& S_BEXACT() { + static const std::string s = "`$EXACT`"; + return s; +} +inline const std::string& S_BOPEN() { + static const std::string s = "`$OPEN`"; + return s; +} inline const std::regex& R_INTEGER_KEY() { static const std::regex r("^[-0-9]+$"); @@ -85,33 +118,51 @@ inline const std::regex& R_ESCAPE_REGEXP() { // Predicates (most also exposed via Value methods) // =========================================================================== -inline bool isnode(const Value& v) { return v.is_node() || v.is_sentinel(); } -inline bool ismap(const Value& v) { return v.is_map() || v.is_sentinel(); } -inline bool islist(const Value& v) { return v.is_list(); } +inline bool isnode(const Value& v) { + return v.is_node() || v.is_sentinel(); +} +inline bool ismap(const Value& v) { + return v.is_map() || v.is_sentinel(); +} +inline bool islist(const Value& v) { + return v.is_list(); +} inline bool iskey(const Value& v) { - if (v.is_string()) return !v.as_string().empty(); - if (v.is_int()) return true; - if (v.is_double()) return true; + if (v.is_string()) + return !v.as_string().empty(); + if (v.is_int()) + return true; + if (v.is_double()) + return true; return false; } inline bool isempty(const Value& v) { - if (v.is_undef() || v.is_null()) return true; - if (v.is_string()) return v.as_string().empty(); - if (v.is_list()) return v.as_list()->empty(); - if (v.is_map()) return v.as_map()->empty(); + if (v.is_undef() || v.is_null()) + return true; + if (v.is_string()) + return v.as_string().empty(); + if (v.is_list()) + return v.as_list()->empty(); + if (v.is_map()) + return v.as_map()->empty(); return false; } -inline bool isfunc(const Value& v) { return v.is_func(); } +inline bool isfunc(const Value& v) { + return v.is_func(); +} inline Value getdef(const Value& v, const Value& alt) { - if (v.is_undef()) return alt; + if (v.is_undef()) + return alt; return v; } -inline int64_t size(const Value& v) { return size_of(v); } +inline int64_t size(const Value& v) { + return size_of(v); +} // =========================================================================== // Type helpers re-exported (defined in value.hpp) @@ -136,11 +187,15 @@ inline bool haskey(const Value& v, const Value& key); inline std::vector items(const Value& v); inline std::string stringify(const Value& v, int maxlen = -1); inline std::string pathify(const Value& v, int startin = 0, int endin = 0); -inline Value slice(const Value& v, const Value& start = Value::undef(), const Value& end = Value::undef()); -inline Value walk_v(const Value& val, - std::function&)> before, - std::function&)> after, - int maxdepth = MAXDEPTH); +inline Value slice(const Value& v, const Value& start = Value::undef(), + const Value& end = Value::undef()); +inline Value walk_v( + const Value& val, + std::function&)> + before, + std::function&)> + after, + int maxdepth = MAXDEPTH); inline Value merge_v(const Value& list, int maxdepth = MAXDEPTH); inline Value getpath_v(const Value& store, const Value& path, Injection* inj = nullptr); inline Value setpath_v(const Value& store, const Value& path, const Value& val); @@ -154,48 +209,64 @@ inline std::vector select(const Value& children, const Value& query); // =========================================================================== inline std::string strkey(const Value& key) { - if (key.is_undef()) return S_MT(); - if (key.is_string()) return key.as_string(); - if (key.is_bool()) return S_MT(); - if (key.is_int()) return std::to_string(key.as_int()); + if (key.is_undef()) + return S_MT(); + if (key.is_string()) + return key.as_string(); + if (key.is_bool()) + return S_MT(); + if (key.is_int()) + return std::to_string(key.as_int()); if (key.is_double()) { double d = key.as_double(); - if (std::floor(d) == d) return std::to_string(static_cast(d)); + if (std::floor(d) == d) + return std::to_string(static_cast(d)); return std::to_string(static_cast(std::floor(d))); } return S_MT(); } inline Value getprop(const Value& val, const Value& key, const Value& alt) { - if (val.is_undef() || key.is_undef()) return alt; + if (val.is_undef() || key.is_undef()) + return alt; if (val.is_map()) { auto m = val.as_map(); - if (!m) return alt; + if (!m) + return alt; std::string sk = strkey(key); Value* found = m->find(sk); - if (!found) return alt; - if (found->is_undef()) return alt; + if (!found) + return alt; + if (found->is_undef()) + return alt; return *found; } if (val.is_list()) { auto l = val.as_list(); - if (!l) return alt; + if (!l) + return alt; int idx; if (key.is_int()) { idx = static_cast(key.as_int()); } else if (key.is_string()) { // Match TS: only accept strings that match /^[-0-9]+$/ entirely. - if (!std::regex_match(key.as_string(), R_INTEGER_KEY())) return alt; - try { idx = std::stoi(key.as_string()); } - catch (...) { return alt; } + if (!std::regex_match(key.as_string(), R_INTEGER_KEY())) + return alt; + try { + idx = std::stoi(key.as_string()); + } catch (...) { + return alt; + } } else if (key.is_double()) { idx = static_cast(key.as_double()); } else { return alt; } - if (idx < 0 || idx >= static_cast(l->size())) return alt; + if (idx < 0 || idx >= static_cast(l->size())) + return alt; Value v = (*l)[idx]; - if (v.is_undef()) return alt; + if (v.is_undef()) + return alt; return v; } return alt; @@ -203,7 +274,8 @@ inline Value getprop(const Value& val, const Value& key, const Value& alt) { // Match TS regex /^[-0-9]+$/ check. inline bool is_integer_key_string(const Value& key) { - if (key.is_int() || key.is_double()) return true; + if (key.is_int() || key.is_double()) + return true; if (key.is_string()) { return std::regex_match(key.as_string(), R_INTEGER_KEY()); } @@ -213,7 +285,8 @@ inline bool is_integer_key_string(const Value& key) { inline Value getelem(const Value& val, const Value& key, const Value& alt) { Value out = Value::undef(); if (val.is_undef() || key.is_undef()) { - if (alt.is_func() && alt.is_injector()) return alt; + if (alt.is_func() && alt.is_injector()) + return alt; return alt; } if (val.is_list()) { @@ -222,24 +295,32 @@ inline Value getelem(const Value& val, const Value& key, const Value& alt) { if (key.is_int()) { nkey = static_cast(key.as_int()); } else if (key.is_string()) { - if (!is_integer_key_string(key)) return alt; - try { nkey = std::stoi(key.as_string()); } - catch (...) { return alt; } + if (!is_integer_key_string(key)) + return alt; + try { + nkey = std::stoi(key.as_string()); + } catch (...) { + return alt; + } } else if (key.is_double()) { nkey = static_cast(key.as_double()); } else { return alt; } - if (nkey < 0) nkey = static_cast(l->size()) + nkey; - if (nkey < 0 || nkey >= static_cast(l->size())) return alt; + if (nkey < 0) + nkey = static_cast(l->size()) + nkey; + if (nkey < 0 || nkey >= static_cast(l->size())) + return alt; out = (*l)[nkey]; } - if (out.is_undef()) return alt; + if (out.is_undef()) + return alt; return out; } inline Value setprop(Value parent, const Value& key, const Value& val) { - if (!iskey(key)) return parent; + if (!iskey(key)) + return parent; if (parent.is_map()) { auto m = parent.as_map(); std::string sk = strkey(key); @@ -249,11 +330,18 @@ inline Value setprop(Value parent, const Value& key, const Value& val) { if (parent.is_list()) { auto l = parent.as_list(); int idx; - if (key.is_int()) idx = static_cast(key.as_int()); + if (key.is_int()) + idx = static_cast(key.as_int()); else if (key.is_string()) { - try { idx = std::stoi(key.as_string()); } catch (...) { return parent; } - } else if (key.is_double()) idx = static_cast(key.as_double()); - else return parent; + try { + idx = std::stoi(key.as_string()); + } catch (...) { + return parent; + } + } else if (key.is_double()) + idx = static_cast(key.as_double()); + else + return parent; if (idx >= 0) { if (idx >= static_cast(l->size())) { @@ -270,7 +358,8 @@ inline Value setprop(Value parent, const Value& key, const Value& val) { } inline Value delprop(Value parent, const Value& key) { - if (!iskey(key)) return parent; + if (!iskey(key)) + return parent; if (parent.is_map()) { parent.as_map()->erase(strkey(key)); return parent; @@ -278,11 +367,18 @@ inline Value delprop(Value parent, const Value& key) { if (parent.is_list()) { auto l = parent.as_list(); int idx; - if (key.is_int()) idx = static_cast(key.as_int()); + if (key.is_int()) + idx = static_cast(key.as_int()); else if (key.is_string()) { - try { idx = std::stoi(key.as_string()); } catch (...) { return parent; } - } else if (key.is_double()) idx = static_cast(key.as_double()); - else return parent; + try { + idx = std::stoi(key.as_string()); + } catch (...) { + return parent; + } + } else if (key.is_double()) + idx = static_cast(key.as_double()); + else + return parent; if (idx >= 0 && idx < static_cast(l->size())) { l->erase(l->begin() + idx); } @@ -297,13 +393,16 @@ inline Value delprop(Value parent, const Value& key) { inline std::vector keysof(const Value& v) { std::vector out; - if (!v.is_node()) return out; + if (!v.is_node()) + return out; if (v.is_map()) { - for (const auto& [k, _] : *v.as_map()) out.push_back(k); + for (const auto& [k, _] : *v.as_map()) + out.push_back(k); } else { auto l = v.as_list(); out.reserve(l->size()); - for (size_t i = 0; i < l->size(); i++) out.push_back(std::to_string(i)); + for (size_t i = 0; i < l->size(); i++) + out.push_back(std::to_string(i)); } return out; } @@ -315,7 +414,8 @@ inline bool haskey(const Value& v, const Value& key) { // items returns list of [key, value] pairs as Value-list-of-Value-lists. inline Value items_v(const Value& v) { auto out = std::make_shared(); - if (!v.is_node()) return Value(out); + if (!v.is_node()) + return Value(out); for (const auto& key : keysof(v)) { auto pair = std::make_shared(); pair->push_back(Value(key)); @@ -327,7 +427,8 @@ inline Value items_v(const Value& v) { inline std::vector items(const Value& v) { std::vector out; - if (!v.is_node()) return out; + if (!v.is_node()) + return out; for (const auto& key : keysof(v)) { auto pair = std::make_shared(); pair->push_back(Value(key)); @@ -342,15 +443,18 @@ inline std::vector items(const Value& v) { // =========================================================================== inline Value flatten(const Value& list, int depth = 1) { - if (!list.is_list()) return list; + if (!list.is_list()) + return list; auto out = std::make_shared(); - std::function&, int)> rec = - [&](const std::shared_ptr& src, int d) { - for (const auto& e : *src) { - if (d > 0 && e.is_list()) rec(e.as_list(), d - 1); - else out->push_back(e); - } - }; + std::function&, int)> rec = [&](const std::shared_ptr& src, + int d) { + for (const auto& e : *src) { + if (d > 0 && e.is_list()) + rec(e.as_list(), d - 1); + else + out->push_back(e); + } + }; rec(list.as_list(), depth); return Value(std::move(out)); } @@ -380,41 +484,49 @@ inline Value slice(const Value& val, const Value& start_v, const Value& end_v) { double n = val.as_double(); int64_t lo = std::numeric_limits::min(); int64_t hi = std::numeric_limits::max(); - if (start_v.is_number()) lo = start_v.as_int(); - if (end_v.is_number()) hi = end_v.as_int() - 1; - if (n < lo) n = lo; - if (n > hi) n = hi; - if (val.is_int()) return Value(static_cast(n)); + if (start_v.is_number()) + lo = start_v.as_int(); + if (end_v.is_number()) + hi = end_v.as_int() - 1; + if (n < lo) + n = lo; + if (n > hi) + n = hi; + if (val.is_int()) + return Value(static_cast(n)); return Value(n); } int vlen = static_cast(size(val)); bool has_start = !start_v.is_undef(); - bool has_end = !end_v.is_undef(); + bool has_end = !end_v.is_undef(); int start = has_start ? static_cast(start_v.as_int()) : 0; - int end = has_end ? static_cast(end_v.as_int()) : vlen; + int end = has_end ? static_cast(end_v.as_int()) : vlen; if (has_end && !has_start) { start = 0; - has_start = true; // proceed into slice block even if only end was given + has_start = true; // proceed into slice block even if only end was given } if (has_start) { if (start < 0) { end = vlen + start; - if (end < 0) end = 0; + if (end < 0) + end = 0; start = 0; } else if (has_end) { if (end < 0) { end = vlen + end; - if (end < 0) end = 0; + if (end < 0) + end = 0; } else if (vlen < end) { end = vlen; } } else { end = vlen; } - if (vlen < start) start = vlen; + if (vlen < start) + start = vlen; if (start >= 0 && start <= end && end <= vlen) { if (val.is_list()) { @@ -426,8 +538,10 @@ inline Value slice(const Value& val, const Value& start_v, const Value& end_v) { return Value(val.as_string().substr(start, end - start)); } } else { - if (val.is_list()) return Value(std::make_shared()); - if (val.is_string()) return Value(std::string("")); + if (val.is_list()) + return Value(std::make_shared()); + if (val.is_string()) + return Value(std::string("")); } } return val; @@ -450,7 +564,8 @@ inline std::string pad(const Value& v, int padding = 44, const std::string& padc } // Convenience overload: pad with default args using Value parameters. -inline std::string pad(const Value& v, const Value& padding_v, const Value& padchar_v = Value::undef()) { +inline std::string pad(const Value& v, const Value& padding_v, + const Value& padchar_v = Value::undef()) { int p = padding_v.is_number() ? static_cast(padding_v.as_int()) : 44; std::string pc = padchar_v.is_string() ? padchar_v.as_string() : " "; return pad(v, p, pc); @@ -466,7 +581,8 @@ inline std::string escre(const Value& v) { } inline std::string escurl(const Value& v) { - if (v.is_undef()) return ""; + if (v.is_undef()) + return ""; std::string s = v.is_string() ? v.as_string() : stringify(v); std::ostringstream out; out.fill('0'); @@ -484,9 +600,12 @@ inline std::string escurl(const Value& v) { // JS-style scalar -> string: integer-valued doubles render without ".0". inline std::string js_string(const Value& v) { - if (v.is_undef() || v.is_null()) return "null"; - if (v.is_bool()) return v.as_bool() ? "true" : "false"; - if (v.is_int()) return std::to_string(v.as_int()); + if (v.is_undef() || v.is_null()) + return "null"; + if (v.is_bool()) + return v.as_bool() ? "true" : "false"; + if (v.is_int()) + return std::to_string(v.as_int()); if (v.is_double()) { double d = v.as_double(); if (std::isfinite(d) && std::floor(d) == d) { @@ -496,18 +615,21 @@ inline std::string js_string(const Value& v) { oss << d; return oss.str(); } - if (v.is_string()) return v.as_string(); + if (v.is_string()) + return v.as_string(); return stringify(v); } inline std::string join(const Value& arr, const std::string& sep = ",", bool url = false) { - if (!arr.is_list()) return ""; + if (!arr.is_list()) + return ""; // Filter to non-empty strings. std::vector parts; auto src = arr.as_list(); for (size_t i = 0; i < src->size(); i++) { const Value& v = (*src)[i]; - if (!v.is_string() || v.as_string().empty()) continue; + if (!v.is_string() || v.as_string().empty()) + continue; parts.push_back(v.as_string()); } size_t sarr = parts.size(); @@ -520,7 +642,8 @@ inline std::string join(const Value& arr, const std::string& sep = ",", bool url if (url && i == 0) { s = std::regex_replace(s, std::regex(sepre + "+$"), ""); } else { - if (i > 0) s = std::regex_replace(s, std::regex("^" + sepre + "+"), ""); + if (i > 0) + s = std::regex_replace(s, std::regex("^" + sepre + "+"), ""); if (i < sarr - 1 || !url) { s = std::regex_replace(s, std::regex(sepre + "+$"), ""); } @@ -528,11 +651,13 @@ inline std::string join(const Value& arr, const std::string& sep = ",", bool url std::string("$1") + sep + "$2"); } } - if (!s.empty()) cleaned.push_back(s); + if (!s.empty()) + cleaned.push_back(s); } std::string out; for (size_t i = 0; i < cleaned.size(); i++) { - if (i > 0) out += sep; + if (i > 0) + out += sep; out += cleaned[i]; } return out; @@ -549,7 +674,8 @@ inline std::string join(const Value& arr, const Value& sep_v, const Value& url_v // =========================================================================== inline std::string jsonify(const Value& v, int indent = 2) { - if (v.is_undef()) return "null"; + if (v.is_undef()) + return "null"; try { return to_njson(v).dump(indent < 0 ? -1 : indent); } catch (...) { @@ -563,8 +689,10 @@ inline std::string jsonify(const Value& v, const Value& flags) { if (flags.is_map()) { Value iv = getprop(flags, Value("indent")); Value ov = getprop(flags, Value("offset")); - if (iv.is_int()) indent = static_cast(iv.as_int()); - if (ov.is_int()) offset = static_cast(ov.as_int()); + if (iv.is_int()) + indent = static_cast(iv.as_int()); + if (ov.is_int()) + offset = static_cast(ov.as_int()); } std::string out = jsonify(v, indent); if (offset > 0 && out.find('\n') != std::string::npos) { @@ -575,9 +703,13 @@ inline std::string jsonify(const Value& v, const Value& flags) { while (pos < out.size()) { size_t nl = out.find('\n', pos); std::string line = (nl == std::string::npos) ? out.substr(pos) : out.substr(pos, nl - pos); - if (first) { padded += line; first = false; } - else padded += "\n" + pad_str + line; - if (nl == std::string::npos) break; + if (first) { + padded += line; + first = false; + } else + padded += "\n" + pad_str + line; + if (nl == std::string::npos) + break; pos = nl + 1; } out = padded; @@ -589,7 +721,8 @@ inline std::string jsonify(const Value& v, const Value& flags) { // strings (mirrors TS stringify which strips quotes for human friendliness). inline std::string stringify(const Value& v, int maxlen) { std::string valstr; - if (v.is_undef()) return ""; + if (v.is_undef()) + return ""; if (v.is_string()) { valstr = v.as_string(); } else { @@ -601,7 +734,9 @@ inline std::string stringify(const Value& v, int maxlen) { // Strip double-quotes (TS regex /"/g). std::string out; out.reserve(valstr.size()); - for (char c : valstr) if (c != '"') out.push_back(c); + for (char c : valstr) + if (c != '"') + out.push_back(c); valstr = out; } catch (...) { valstr = "__STRINGIFY_FAILED__"; @@ -623,13 +758,17 @@ inline std::string pathify(const Value& v, int startin, int endin) { valid = true; for (const auto& e : *v.as_list()) { if (iskey(e)) { - if (e.is_int()) parts.push_back(std::to_string(e.as_int())); - else if (e.is_double()) parts.push_back(std::to_string(static_cast(std::floor(e.as_double())))); + if (e.is_int()) + parts.push_back(std::to_string(e.as_int())); + else if (e.is_double()) + parts.push_back(std::to_string(static_cast(std::floor(e.as_double())))); else { std::string s = e.as_string(); // Strip dots — TS regex /\./g. std::string filtered; - for (char c : s) if (c != '.') filtered += c; + for (char c : s) + if (c != '.') + filtered += c; parts.push_back(filtered); } } @@ -640,22 +779,26 @@ inline std::string pathify(const Value& v, int startin, int endin) { } else if (v.is_number()) { valid = true; parts.push_back(v.is_int() ? std::to_string(v.as_int()) - : std::to_string(static_cast(std::floor(v.as_double())))); + : std::to_string(static_cast(std::floor(v.as_double())))); } int start = std::max(0, startin); - int end = std::max(0, endin); + int end = std::max(0, endin); if (valid) { int total = static_cast(parts.size()); int new_end = total - end; - if (new_end < start) new_end = start; - if (new_end > total) new_end = total; + if (new_end < start) + new_end = start; + if (new_end > total) + new_end = total; std::vector sliced(parts.begin() + std::min(start, total), parts.begin() + new_end); - if (sliced.empty()) return ""; + if (sliced.empty()) + return ""; std::string out; for (size_t i = 0; i < sliced.size(); i++) { - if (i > 0) out += "."; + if (i > 0) + out += "."; out += sliced[i]; } return out; @@ -678,10 +821,15 @@ inline Value jm(std::initializer_list kv) { size_t i = 0; while (it != kv.end()) { std::string k = it->is_string() ? it->as_string() : stringify(*it); - if (k.empty()) k = "$KEY" + std::to_string(i); - ++it; i++; + if (k.empty()) + k = "$KEY" + std::to_string(i); + ++it; + i++; Value v = (it != kv.end()) ? *it : Value(nullptr); - if (it != kv.end()) { ++it; i++; } + if (it != kv.end()) { + ++it; + i++; + } m->set(k, v); } return Value(std::move(m)); @@ -699,22 +847,13 @@ inline Value jt(std::initializer_list v) { // Depth-first walk, applying optional `before` (on descend) and `after` // (on ascend) callbacks. Mirrors TS walk lines 915–975. -using WalkApply = std::function& path)>; +using WalkApply = std::function& path)>; namespace detail { -inline Value walk_descend( - Value val, - const WalkApply& before, - const WalkApply& after, - int maxdepth, - const Value& key, - const Value& parent, - std::vector& path) { +inline Value walk_descend(Value val, const WalkApply& before, const WalkApply& after, int maxdepth, + const Value& key, const Value& parent, std::vector& path) { Value out = before ? before(key, val, parent, path) : val; int depth = static_cast(path.size()); if (maxdepth == 0 || (maxdepth > 0 && maxdepth <= depth)) { @@ -733,16 +872,16 @@ inline Value walk_descend( path.pop_back(); } } - if (after) out = after(key, out, parent, path); + if (after) + out = after(key, out, parent, path); return out; } -} // namespace detail +} // namespace detail inline Value walk_v(const Value& val, WalkApply before, WalkApply after, int maxdepth) { std::vector path; - return detail::walk_descend(val, before, after, maxdepth, - Value::undef(), Value::undef(), path); + return detail::walk_descend(val, before, after, maxdepth, Value::undef(), Value::undef(), path); } inline Value walk_v(const Value& val, WalkApply before) { @@ -759,10 +898,13 @@ inline Value walk_v(const Value& val, WalkApply before) { inline Value merge_v(const Value& list, int maxdepth) { int md = std::max(0, std::min(maxdepth, MAXDEPTH)); - if (!list.is_list()) return list; + if (!list.is_list()) + return list; auto src = list.as_list(); - if (src->empty()) return Value(nullptr); - if (src->size() == 1) return (*src)[0]; + if (src->empty()) + return Value(nullptr); + if (src->size() == 1) + return (*src)[0]; Value out = (*src)[0]; if (out.is_undef()) { @@ -781,8 +923,8 @@ inline Value merge_v(const Value& list, int maxdepth) { cur[0] = out; dst[0] = out; - WalkApply before = [&](const Value& key, const Value& val, - const Value& parent, const std::vector& path) -> Value { + WalkApply before = [&](const Value& key, const Value& val, const Value& parent, + const std::vector& path) -> Value { int pI = static_cast(path.size()); if (md <= pI) { if (!key.is_undef()) { @@ -797,23 +939,24 @@ inline Value merge_v(const Value& list, int maxdepth) { // Descending into a node. if (pI > 0 && !key.is_undef()) { dst[pI] = getprop(dst[pI - 1], key); - if (dst[pI].is_undef()) dst[pI] = Value::undef(); + if (dst[pI].is_undef()) + dst[pI] = Value::undef(); } Value tval = dst[pI]; if (tval.is_undef() && (typify(val) & T_instance) == 0) { cur[pI] = val.is_list() ? Value(std::make_shared()) - : Value(std::shared_ptr(new Map())); + : Value(std::shared_ptr(new Map())); } else if (typify(val) == typify(tval)) { cur[pI] = tval; } else { cur[pI] = val; - return Value::undef(); // skip descent + return Value::undef(); // skip descent } return val; }; - WalkApply after = [&](const Value& key, const Value& val_unused, - const Value& parent, const std::vector& path) -> Value { + WalkApply after = [&](const Value& key, const Value& val_unused, const Value& parent, + const std::vector& path) -> Value { int cI = static_cast(path.size()); if (key.is_undef() || cI <= 0) { return cur[0]; @@ -829,9 +972,12 @@ inline Value merge_v(const Value& list, int maxdepth) { if (md == 0) { Value last = src->back(); - if (last.is_list()) out = Value(std::make_shared()); - else if (last.is_map()) out = Value(std::shared_ptr(new Map())); - else out = last; + if (last.is_list()) + out = Value(std::make_shared()); + else if (last.is_map()) + out = Value(std::shared_ptr(new Map())); + else + out = last; } return out; } @@ -864,9 +1010,12 @@ inline std::vector path_parts(const Value& path) { if (path.is_list()) { auto l = path.as_list(); for (const auto& e : *l) { - if (e.is_string()) out.push_back(e.as_string()); - else if (e.is_number()) out.push_back(strkey(e)); - else out.push_back(strkey(e)); + if (e.is_string()) + out.push_back(e.as_string()); + else if (e.is_number()) + out.push_back(strkey(e)); + else + out.push_back(strkey(e)); } return out; } @@ -876,7 +1025,7 @@ inline std::vector path_parts(const Value& path) { } return {}; } -} // namespace detail +} // namespace detail // =========================================================================== // setpath @@ -885,12 +1034,18 @@ inline std::vector path_parts(const Value& path) { inline Value setpath_v(const Value& store, const Value& path, const Value& val) { std::vector parts; bool path_is_list = path.is_list(); - if (path.is_undef() || path.is_null()) return Value::undef(); - if (path.is_string()) parts = detail::path_parts(path); - else if (path.is_list()) parts = detail::path_parts(path); - else if (path.is_number()) parts.push_back(strkey(path)); - else return Value::undef(); - if (parts.empty()) return Value::undef(); + if (path.is_undef() || path.is_null()) + return Value::undef(); + if (path.is_string()) + parts = detail::path_parts(path); + else if (path.is_list()) + parts = detail::path_parts(path); + else if (path.is_number()) + parts.push_back(strkey(path)); + else + return Value::undef(); + if (parts.empty()) + return Value::undef(); // String paths only create maps (TS: "Use a string list to create list parts"). // For list paths, decide list-vs-map by whether the next part is integer. @@ -902,10 +1057,10 @@ inline Value setpath_v(const Value& store, const Value& path, const Value& val) if (path_is_list) { // Check the original list for whether parts[i+1] came from a number. Value el = getprop(path, Value(static_cast(i + 1))); - if (el.is_number()) make_list = true; + if (el.is_number()) + make_list = true; } - next = make_list ? Value(std::make_shared()) - : Value(std::shared_ptr(new Map())); + next = make_list ? Value(std::make_shared()) : Value(std::shared_ptr(new Map())); setprop(parent, Value(parts[i]), next); } parent = next; @@ -924,11 +1079,11 @@ inline Value setpath_v(const Value& store, const Value& path, const Value& val) // =========================================================================== class Injection { - public: +public: int mode = M_VAL; bool full = false; int keyI = 0; - std::shared_ptr> keys; // shared with prior + std::shared_ptr> keys; // shared with prior std::string key; Value val; Value parent; @@ -944,8 +1099,7 @@ class Injection { Injection* prior = nullptr; Value extra; - Injection(const Value& val_in, const Value& parent_in) - : val(val_in), parent(parent_in) { + Injection(const Value& val_in, const Value& parent_in) : val(val_in), parent(parent_in) { keys = std::make_shared>(); keys->push_back(S_DTOP()); key = S_DTOP(); @@ -963,10 +1117,12 @@ class Injection { int64_t d = 0; if (auto* m = meta.get()) { Value* dv = m->find("__d"); - if (dv && dv->is_int()) d = dv->as_int(); + if (dv && dv->is_int()) + d = dv->as_int(); m->set("__d", Value(d + 1)); } - if (path.size() < 2) return dparent; + if (path.size() < 2) + return dparent; const std::string& parentkey = path[path.size() - 2]; if (dparent.is_undef()) { @@ -978,7 +1134,8 @@ class Injection { std::string lastpart = dpath.empty() ? "" : dpath.back(); std::string marker = "$:" + parentkey; if (marker == lastpart) { - if (!dpath.empty()) dpath.pop_back(); + if (!dpath.empty()) + dpath.pop_back(); } else { dpath.push_back(parentkey); } @@ -987,14 +1144,14 @@ class Injection { } std::unique_ptr child(int keyI_in, - const std::shared_ptr>& keys_in) { + const std::shared_ptr>& keys_in) { std::string ck = strkey(Value((*keys_in)[keyI_in])); Value child_val = getprop(val, Value(ck)); auto cinj = std::unique_ptr(new Injection(child_val, val)); cinj->keyI = keyI_in; - cinj->keys = keys_in; // shared reference + cinj->keys = keys_in; // shared reference cinj->key = ck; - cinj->path = path; // copy + cinj->path = path; // copy cinj->path.push_back(ck); cinj->nodes = std::make_shared>(*nodes); cinj->nodes->push_back(val); @@ -1002,10 +1159,10 @@ class Injection { cinj->handler = handler; cinj->modify = modify; cinj->base = base; - cinj->meta = meta; // shared reference - cinj->errs = errs; // shared reference + cinj->meta = meta; // shared reference + cinj->errs = errs; // shared reference cinj->prior = this; - cinj->dpath = dpath; // copy + cinj->dpath = dpath; // copy cinj->dparent = dparent; return cinj; } @@ -1025,7 +1182,8 @@ class Injection { // Use nodes[-ancestor] and path[-ancestor] int idx_n = static_cast(nodes->size()) - ancestor; int idx_p = static_cast(path.size()) - ancestor; - if (idx_n < 0 || idx_p < 0) return Value::undef(); + if (idx_n < 0 || idx_p < 0) + return Value::undef(); Value aval = (*nodes)[idx_n]; Value akey(path[idx_p]); if (v.is_undef()) { @@ -1044,21 +1202,27 @@ class Injection { namespace detail { -inline Value getpath_inner( - const Value& store, const Value& path, - std::vector& parts, - Injection* inj); +inline Value getpath_inner(const Value& store, const Value& path, std::vector& parts, + Injection* inj); -} // namespace detail +} // namespace detail inline Value getpath_v(const Value& store, const Value& path, Injection* inj) { std::vector parts; bool valid_path = false; - if (path.is_string()) { parts = detail::path_parts(path); valid_path = true; } - else if (path.is_list()) { parts = detail::path_parts(path); valid_path = true; } - else if (path.is_number()) { parts.push_back(strkey(path)); valid_path = true; } + if (path.is_string()) { + parts = detail::path_parts(path); + valid_path = true; + } else if (path.is_list()) { + parts = detail::path_parts(path); + valid_path = true; + } else if (path.is_number()) { + parts.push_back(strkey(path)); + valid_path = true; + } // Note: undef / null / bool / map paths are invalid (matches TS lines 1147-1153). - if (!valid_path) return Value::undef(); + if (!valid_path) + return Value::undef(); Value val = detail::getpath_inner(store, path, parts, inj); if (inj && inj->handler) { std::string ref = path.is_undef() ? "" : pathify(path); @@ -1069,10 +1233,8 @@ inline Value getpath_v(const Value& store, const Value& path, Injection* inj) { namespace detail { -inline Value getpath_inner( - const Value& store, const Value& path, - std::vector& parts, - Injection* inj) { +inline Value getpath_inner(const Value& store, const Value& path, std::vector& parts, + Injection* inj) { if (path.is_undef() || path.is_null()) { return store; } @@ -1145,21 +1307,23 @@ inline Value getpath_inner( pI++; } if (inj && ascends > 0) { - if (pI == numparts - 1) ascends--; + if (pI == numparts - 1) + ascends--; if (ascends == 0) { val = dparent; } else if (dpath) { int dlen = static_cast(dpath->size()); int cut = dlen - ascends; - if (cut < 0) cut = 0; - std::vector fullpath(dpath->begin(), - dpath->begin() + cut); + if (cut < 0) + cut = 0; + std::vector fullpath(dpath->begin(), dpath->begin() + cut); for (int j = pI + 1; j < numparts; j++) { fullpath.push_back(parts[j]); } if (ascends <= dlen) { auto fp = std::make_shared(); - for (auto& p : fullpath) fp->push_back(Value(p)); + for (auto& p : fullpath) + fp->push_back(Value(p)); val = getpath_v(store, Value(std::move(fp))); } else { val = Value::undef(); @@ -1177,22 +1341,22 @@ inline Value getpath_inner( return val; } -} // namespace detail +} // namespace detail // =========================================================================== // _invalidTypeMsg / _injectstr / _injecthandler // =========================================================================== inline std::string invalid_type_msg(const std::vector& path, - const std::string& need_type, - int vt, const Value& v, - const std::string& whence = "") { - (void)whence; + const std::string& need_type, int vt, const Value& v, + const std::string& whence = "") { + (void) whence; std::string vs = (v.is_undef() || v.is_null()) ? "no value" : stringify(v); std::string out = "Expected "; if (path.size() > 1) { auto pl = std::make_shared(); - for (auto& p : path) pl->push_back(Value(p)); + for (auto& p : path) + pl->push_back(Value(p)); out += "field " + pathify(Value(pl), 1, 0) + " to be "; } out += need_type + ", but found "; @@ -1203,13 +1367,12 @@ inline std::string invalid_type_msg(const std::vector& path, return out; } -inline std::string invalid_type_msg(const Value& path_v, - const std::string& need_type, - int vt, const Value& v, - const std::string& whence = "") { +inline std::string invalid_type_msg(const Value& path_v, const std::string& need_type, int vt, + const Value& v, const std::string& whence = "") { std::vector p; if (path_v.is_list()) { - for (const auto& e : *path_v.as_list()) p.push_back(strkey(e)); + for (const auto& e : *path_v.as_list()) + p.push_back(strkey(e)); } else if (path_v.is_string()) { p.push_back(path_v.as_string()); } @@ -1219,31 +1382,40 @@ inline std::string invalid_type_msg(const Value& path_v, // MODENAME / PLACEMENT helpers. inline std::string modename(int mode) { switch (mode) { - case M_VAL: return "val"; - case M_KEYPRE: return "key:pre"; - case M_KEYPOST: return "key:post"; - default: return ""; + case M_VAL: + return "val"; + case M_KEYPRE: + return "key:pre"; + case M_KEYPOST: + return "key:post"; + default: + return ""; } } inline std::string placement(int mode) { switch (mode) { - case M_VAL: return "value"; - case M_KEYPRE: - case M_KEYPOST: return "key"; - default: return ""; + case M_VAL: + return "value"; + case M_KEYPRE: + case M_KEYPOST: + return "key"; + default: + return ""; } } // _injecthandler: forward declared, defined below after Injection helpers. -inline Value injecthandler(Injection& inj, const Value& val, - const std::string& ref, const Value& store); +inline Value injecthandler(Injection& inj, const Value& val, const std::string& ref, + const Value& store); inline Value injectstr(const std::string& val, const Value& store, Injection* inj) { - if (val.empty()) return Value(std::string("")); + if (val.empty()) + return Value(std::string("")); std::smatch m; if (std::regex_match(val, m, R_INJECTION_FULL())) { - if (inj) inj->full = true; + if (inj) + inj->full = true; std::string pathref = m[1].str(); if (pathref.size() > 3) { pathref = std::regex_replace(pathref, R_BT_ESCAPE(), "`"); @@ -1265,7 +1437,8 @@ inline Value injectstr(const std::string& val, const Value& store, Injection* in ref = std::regex_replace(ref, R_BT_ESCAPE(), "`"); ref = std::regex_replace(ref, R_DS_ESCAPE(), "$"); } - if (inj) inj->full = false; + if (inj) + inj->full = false; Value found = getpath_v(store, Value(ref), inj); if (found.is_undef()) { // append nothing @@ -1274,7 +1447,11 @@ inline Value injectstr(const std::string& val, const Value& store, Injection* in } else if (found.is_string()) { out += found.as_string(); } else { - try { out += to_njson(found).dump(); } catch (...) { out += stringify(found); } + try { + out += to_njson(found).dump(); + } catch (...) { + out += stringify(found); + } } cursor = pos + it->length(0); } @@ -1288,8 +1465,8 @@ inline Value injectstr(const std::string& val, const Value& store, Injection* in return out_val; } -inline Value injecthandler(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value injecthandler(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { Value out = val; bool iscmd = isfunc(val) && (ref.empty() || ref[0] == '$'); if (iscmd) { @@ -1306,28 +1483,28 @@ inline Value injecthandler(Injection& inj, const Value& val, // checkPlacement / injectorArgs / injectChild // =========================================================================== -inline bool checkPlacement(int modes, const std::string& ijname, - int parent_types, Injection& inj) { +inline bool checkPlacement(int modes, const std::string& ijname, int parent_types, Injection& inj) { if ((modes & inj.mode) == 0) { std::string allowed; int first = 1; for (int m : {M_KEYPRE, M_KEYPOST, M_VAL}) { if (modes & m) { - if (!first) allowed += ","; + if (!first) + allowed += ","; allowed += placement(m); first = 0; } } - inj.errs->push_back(Value("$" + ijname + ": invalid placement as " + - placement(inj.mode) + ", expected: " + allowed + ".")); + inj.errs->push_back(Value("$" + ijname + ": invalid placement as " + placement(inj.mode) + + ", expected: " + allowed + ".")); return false; } if (parent_types != 0) { int ptype = typify(inj.parent); if ((parent_types & ptype) == 0) { - inj.errs->push_back(Value( - "$" + ijname + ": invalid placement in parent " + - typename_str(ptype) + ", expected: " + typename_str(parent_types) + ".")); + inj.errs->push_back(Value("$" + ijname + ": invalid placement in parent " + + typename_str(ptype) + ", expected: " + typename_str(parent_types) + + ".")); return false; } } @@ -1344,10 +1521,9 @@ inline std::vector injectorArgs(const std::vector& arg_types, Value arg = i < args.size() ? args[i] : Value::undef(); int argType = typify(arg); if ((arg_types[i] & argType) == 0) { - out[0] = Value("invalid argument: " + stringify(arg, 22) + - " (" + typename_str(argType) + " at position " + - std::to_string(1 + i) + ") is not of type: " + - typename_str(arg_types[i]) + "."); + out[0] = Value("invalid argument: " + stringify(arg, 22) + " (" + typename_str(argType) + + " at position " + std::to_string(1 + i) + + ") is not of type: " + typename_str(arg_types[i]) + "."); return out; } out[i + 1] = arg; @@ -1356,11 +1532,11 @@ inline std::vector injectorArgs(const std::vector& arg_types, } // Forward decl: inject is defined below. -inline std::unique_ptr injectChild_helper( - const Value& child, const Value& store, Injection& inj); +inline std::unique_ptr injectChild_helper(const Value& child, const Value& store, + Injection& inj); inline Injection& injectChild(const Value& child, const Value& store, - Injection& inj); // signature only; inline below + Injection& inj); // signature only; inline below // =========================================================================== // inject @@ -1398,20 +1574,28 @@ inline Value inject(const Value& val, const Value& store, Injection* injdef) { // Simpler approach: don't share; copy on initial setup, push back at // end. The corpus tests pass errs externally via the options map, so // this is wired in transform()/validate() instead. - (void)sl; + (void) sl; } inj_owner->meta->set("__d", Value(int64_t(0))); if (injdef) { - if (injdef->modify) inj_owner->modify = injdef->modify; - if (!injdef->extra.is_undef()) inj_owner->extra = injdef->extra; - if (injdef->meta) inj_owner->meta = injdef->meta; - if (injdef->handler) inj_owner->handler = injdef->handler; - if (!injdef->base.empty()) inj_owner->base = injdef->base; - if (!injdef->dparent.is_undef()) inj_owner->dparent = injdef->dparent; - if (injdef->errs && !injdef->errs->empty()) inj_owner->errs = injdef->errs; + if (injdef->modify) + inj_owner->modify = injdef->modify; + if (!injdef->extra.is_undef()) + inj_owner->extra = injdef->extra; + if (injdef->meta) + inj_owner->meta = injdef->meta; + if (injdef->handler) + inj_owner->handler = injdef->handler; + if (!injdef->base.empty()) + inj_owner->base = injdef->base; + if (!injdef->dparent.is_undef()) + inj_owner->dparent = injdef->dparent; + if (injdef->errs && !injdef->errs->empty()) + inj_owner->errs = injdef->errs; // Always honor an external errs list pointer if one is supplied. - if (injdef->errs) inj_owner->errs = injdef->errs; + if (injdef->errs) + inj_owner->errs = injdef->errs; } if (!inj_owner->handler) { inj_owner->handler = injecthandler; @@ -1435,10 +1619,12 @@ inline Value inject(const Value& val, const Value& store, Injection* injdef) { if (cur.is_map()) { // $-suffix ordering: non-$ first, then $. for (const auto& k : node_keys_vec) { - if (k.find('$') == std::string::npos) nodekeys->push_back(k); + if (k.find('$') == std::string::npos) + nodekeys->push_back(k); } for (const auto& k : node_keys_vec) { - if (k.find('$') != std::string::npos) nodekeys->push_back(k); + if (k.find('$') != std::string::npos) + nodekeys->push_back(k); } } else { *nodekeys = node_keys_vec; @@ -1461,9 +1647,6 @@ inline Value inject(const Value& val, const Value& store, Injection* injdef) { cinj->mode = M_VAL; inject(cinj->val, store, cinj); - nkI = cinj->keyI; - nodekeys = cinj->keys; - cinj->mode = M_KEYPOST; injectstr(nodekey, store, cinj); @@ -1492,8 +1675,7 @@ inline Value inject(const Value& val, const Value& store, Injection* injdef) { } // injectChild definition (after inject is declared). -inline Injection& injectChild(const Value& child, const Value& store, - Injection& inj) { +inline Injection& injectChild(const Value& child, const Value& store, Injection& inj) { static thread_local std::unique_ptr hold; Injection* cinj = &inj; std::unique_ptr owner; @@ -1522,23 +1704,23 @@ inline Injection& injectChild(const Value& child, const Value& store, namespace transforms { -inline Value DELETE_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value DELETE_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { inj.setval(Value::undef()); return Value::undef(); } -inline Value COPY_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (!checkPlacement(M_VAL, "COPY", T_any, inj)) return Value::undef(); +inline Value COPY_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (!checkPlacement(M_VAL, "COPY", T_any, inj)) + return Value::undef(); Value out = getprop(inj.dparent, Value(inj.key)); inj.setval(out); return out; } -inline Value KEY_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_VAL) return Value::undef(); +inline Value KEY_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_VAL) + return Value::undef(); Value keyspec = getprop(inj.parent, Value(S_BKEY())); if (!keyspec.is_undef()) { delprop(inj.parent, Value(S_BKEY())); @@ -1549,21 +1731,23 @@ inline Value KEY_FN(Injection& inj, const Value& val, return getprop(anno, Value("KEY"), alt); } -inline Value ANNO_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value ANNO_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { delprop(inj.parent, Value(S_BANNO())); return Value::undef(); } -inline Value MERGE_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode == M_KEYPRE) return Value(inj.key); - if (inj.mode != M_KEYPOST) return Value::undef(); +inline Value MERGE_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { + if (inj.mode == M_KEYPRE) + return Value(inj.key); + if (inj.mode != M_KEYPOST) + return Value::undef(); Value args = getprop(inj.parent, Value(inj.key)); auto arg_list = std::make_shared(); if (args.is_list()) { - for (const auto& e : *args.as_list()) arg_list->push_back(e); + for (const auto& e : *args.as_list()) + arg_list->push_back(e); } else { arg_list->push_back(args); } @@ -1571,7 +1755,8 @@ inline Value MERGE_FN(Injection& inj, const Value& val, auto merge_args = std::make_shared(); merge_args->push_back(inj.parent); - for (const auto& a : *arg_list) merge_args->push_back(a); + for (const auto& a : *arg_list) + merge_args->push_back(a); merge_args->push_back(clone(inj.parent)); merge_v(Value(merge_args)); return Value(inj.key); @@ -1585,12 +1770,14 @@ inline std::unordered_map>& form std::function rec = [&](const Value& x) -> Value { if (x.is_list()) { auto out = std::make_shared(); - for (const auto& e : *x.as_list()) out->push_back(rec(e)); + for (const auto& e : *x.as_list()) + out->push_back(rec(e)); return Value(out); } if (x.is_map()) { auto out = std::shared_ptr(new Map()); - for (const auto& [k, e] : *x.as_map()) out->set(k, rec(e)); + for (const auto& [k, e] : *x.as_map()) + out->set(k, rec(e)); return Value(out); } return f(x); @@ -1600,50 +1787,67 @@ inline std::unordered_map>& form F["identity"] = [](const Value& v) { return v; }; F["upper"] = [deep_apply](const Value& v) { return deep_apply(v, [](const Value& x) -> Value { - if (isnode(x)) return x; + if (isnode(x)) + return x; std::string s = js_string(x); - for (auto& c : s) c = static_cast(std::toupper(static_cast(c))); + for (auto& c : s) + c = static_cast(std::toupper(static_cast(c))); return Value(s); }); }; F["lower"] = [deep_apply](const Value& v) { return deep_apply(v, [](const Value& x) -> Value { - if (isnode(x)) return x; + if (isnode(x)) + return x; std::string s = js_string(x); - for (auto& c : s) c = static_cast(std::tolower(static_cast(c))); + for (auto& c : s) + c = static_cast(std::tolower(static_cast(c))); return Value(s); }); }; F["string"] = [deep_apply](const Value& v) { return deep_apply(v, [](const Value& x) -> Value { - if (isnode(x)) return x; + if (isnode(x)) + return x; return Value(js_string(x)); }); }; F["number"] = [deep_apply](const Value& v) { return deep_apply(v, [](const Value& x) -> Value { - if (isnode(x)) return x; - if (x.is_number()) return x; + if (isnode(x)) + return x; + if (x.is_number()) + return x; try { double d = std::stod(js_string(x)); - if (std::isnan(d)) return Value(int64_t(0)); - if (std::floor(d) == d) return Value(static_cast(d)); + if (std::isnan(d)) + return Value(int64_t(0)); + if (std::floor(d) == d) + return Value(static_cast(d)); return Value(d); - } catch (...) { return Value(int64_t(0)); } + } catch (...) { + return Value(int64_t(0)); + } }); }; F["integer"] = [deep_apply](const Value& v) { return deep_apply(v, [](const Value& x) -> Value { - if (isnode(x)) return x; - try { return Value(static_cast(std::stod(js_string(x)))); } - catch (...) { return Value(int64_t(0)); } + if (isnode(x)) + return x; + try { + return Value(static_cast(std::stod(js_string(x)))); + } catch (...) { + return Value(int64_t(0)); + } }); }; F["concat"] = [](const Value& v) -> Value { - if (!v.is_list()) return v; + if (!v.is_list()) + return v; std::string out; for (const auto& e : *v.as_list()) { - if (!isnode(e)) out += js_string(e); + if (!isnode(e)) + out += js_string(e); } return Value(out); }; @@ -1651,13 +1855,14 @@ inline std::unordered_map>& form return F; } -inline Value FORMAT_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value FORMAT_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { // Truncate keys to single element. if (inj.keys && inj.keys->size() > 1) { inj.keys->resize(1); } - if (inj.mode != M_VAL) return Value::undef(); + if (inj.mode != M_VAL) + return Value::undef(); Value name = getprop(inj.parent, Value(int64_t(1))); Value child = getprop(inj.parent, Value(int64_t(2))); @@ -1685,13 +1890,15 @@ inline Value FORMAT_FN(Injection& inj, const Value& val, return out; } -inline Value APPLY_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (!checkPlacement(M_VAL, "APPLY", T_list, inj)) return Value::undef(); +inline Value APPLY_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { + if (!checkPlacement(M_VAL, "APPLY", T_list, inj)) + return Value::undef(); std::vector args; if (inj.parent.is_list()) { auto pl = inj.parent.as_list(); - for (size_t i = 1; i < pl->size(); i++) args.push_back((*pl)[i]); + for (size_t i = 1; i < pl->size(); i++) + args.push_back((*pl)[i]); } auto checked = injectorArgs({T_function, T_any}, args); if (!checked[0].is_undef()) { @@ -1722,16 +1929,17 @@ inline Value APPLY_FN(Injection& inj, const Value& val, return out; } -inline Value EACH_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (!checkPlacement(M_VAL, "EACH", T_list, inj)) return Value::undef(); +inline Value EACH_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (!checkPlacement(M_VAL, "EACH", T_list, inj)) + return Value::undef(); if (inj.keys && inj.keys->size() > 1) { inj.keys->resize(1); } std::vector args; if (inj.parent.is_list()) { auto pl = inj.parent.as_list(); - for (size_t i = 1; i < pl->size(); i++) args.push_back((*pl)[i]); + for (size_t i = 1; i < pl->size(); i++) + args.push_back((*pl)[i]); } auto checked = injectorArgs({T_string, T_any}, args); if (!checked[0].is_undef()) { @@ -1756,7 +1964,8 @@ inline Value EACH_FN(Injection& inj, const Value& val, auto tval = std::make_shared(); if ((T_list & srctype) && src.is_list()) { auto sl = src.as_list(); - for (size_t i = 0; i < sl->size(); i++) tval->push_back(clone(child)); + for (size_t i = 0; i < sl->size(); i++) + tval->push_back(clone(child)); } else if ((T_map & srctype) && src.is_map()) { auto sm = src.as_map(); for (const auto& [k, v] : *sm) { @@ -1779,11 +1988,13 @@ inline Value EACH_FN(Injection& inj, const Value& val, Value tcur; if (src.is_list()) { auto tcur_list = std::make_shared(); - for (const auto& e : *src.as_list()) tcur_list->push_back(e); + for (const auto& e : *src.as_list()) + tcur_list->push_back(e); tcur = Value(tcur_list); } else if (src.is_map()) { auto tcur_list = std::make_shared(); - for (const auto& [_, e] : *src.as_map()) tcur_list->push_back(e); + for (const auto& [_, e] : *src.as_map()) + tcur_list->push_back(e); tcur = Value(tcur_list); } @@ -1797,7 +2008,10 @@ inline Value EACH_FN(Injection& inj, const Value& val, size_t pos = 0; while (pos <= srcpath.size()) { size_t dot = srcpath.find('.', pos); - if (dot == std::string::npos) { dpath.push_back(srcpath.substr(pos)); break; } + if (dot == std::string::npos) { + dpath.push_back(srcpath.substr(pos)); + break; + } dpath.push_back(srcpath.substr(pos, dot - pos)); pos = dot + 1; } @@ -1820,8 +2034,7 @@ inline Value EACH_FN(Injection& inj, const Value& val, auto tinj_owner = inj.child(0, single_keys); Injection* tinj = tinj_owner.get(); tinj->path = tpath; - tinj->nodes = std::make_shared>( - inj.nodes->begin(), inj.nodes->end() - 1); + tinj->nodes = std::make_shared>(inj.nodes->begin(), inj.nodes->end() - 1); tinj->parent = tinj->nodes->empty() ? Value::undef() : tinj->nodes->back(); setprop(tinj->parent, Value(ckey), Value(tval)); tinj->val = Value(tval); @@ -1839,14 +2052,15 @@ inline Value EACH_FN(Injection& inj, const Value& val, return Value::undef(); } -inline Value PACK_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (!checkPlacement(M_KEYPRE, "PACK", T_map, inj)) return Value::undef(); +inline Value PACK_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (!checkPlacement(M_KEYPRE, "PACK", T_map, inj)) + return Value::undef(); Value args = getprop(inj.parent, Value(inj.key)); std::vector arg_list; if (args.is_list()) { - for (const auto& e : *args.as_list()) arg_list.push_back(e); + for (const auto& e : *args.as_list()) + arg_list.push_back(e); } auto checked = injectorArgs({T_string, T_any}, arg_list); if (!checked[0].is_undef()) { @@ -1871,7 +2085,8 @@ inline Value PACK_FN(Injection& inj, const Value& val, std::vector srcList; if (src.is_list()) { - for (const auto& e : *src.as_list()) srcList.push_back(e); + for (const auto& e : *src.as_list()) + srcList.push_back(e); } else if (src.is_map()) { for (const auto& [k, e] : *src.as_map()) { if (isnode(e)) { @@ -1903,7 +2118,8 @@ inline Value PACK_FN(Injection& inj, const Value& val, if (keypath.is_undef()) { Value dk = getprop(item, Value(S_DKEY())); outKey = dk.is_undef() ? std::to_string(i) : strkey(dk); - } else if (keypath.is_string() && !keypath.as_string().empty() && keypath.as_string()[0] == '`') { + } else if (keypath.is_string() && !keypath.as_string().empty() && + keypath.as_string()[0] == '`') { // Inject keypath against {$TOP: srcnode} merged into store. auto mergeList = std::make_shared(); mergeList->push_back(Value(std::shared_ptr(new Map()))); @@ -1938,7 +2154,8 @@ inline Value PACK_FN(Injection& inj, const Value& val, std::string kn; if (keypath.is_undef()) { kn = std::to_string(i); - } else if (keypath.is_string() && !keypath.as_string().empty() && keypath.as_string()[0] == '`') { + } else if (keypath.is_string() && !keypath.as_string().empty() && + keypath.as_string()[0] == '`') { auto mergeList = std::make_shared(); mergeList->push_back(Value(std::shared_ptr(new Map()))); mergeList->push_back(store); @@ -1964,7 +2181,10 @@ inline Value PACK_FN(Injection& inj, const Value& val, size_t pos = 0; while (pos <= srcpath.size()) { size_t dot = srcpath.find('.', pos); - if (dot == std::string::npos) { dpath.push_back(srcpath.substr(pos)); break; } + if (dot == std::string::npos) { + dpath.push_back(srcpath.substr(pos)); + break; + } dpath.push_back(srcpath.substr(pos, dot - pos)); pos = dot + 1; } @@ -1987,24 +2207,24 @@ inline Value PACK_FN(Injection& inj, const Value& val, auto tinj_owner = inj.child(0, single_keys); Injection* tinj = tinj_owner.get(); tinj->path = tpath; - tinj->nodes = std::make_shared>( - inj.nodes->begin(), inj.nodes->end() - 1); + tinj->nodes = std::make_shared>(inj.nodes->begin(), inj.nodes->end() - 1); tinj->parent = tinj->nodes->empty() ? Value::undef() : tinj->nodes->back(); tinj->val = Value(tval); tinj->dpath = dpath; tinj->dparent = tcur_out; inject(Value(tval), store, tinj); - if (tinj->val.is_map()) rval = tinj->val; + if (tinj->val.is_map()) + rval = tinj->val; } setprop(target, tkey, rval); return Value::undef(); } -inline Value REF_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_VAL) return Value::undef(); +inline Value REF_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_VAL) + return Value::undef(); Value refpath = getprop(inj.parent, Value(int64_t(1))); inj.keyI = static_cast(inj.keys ? inj.keys->size() : 0); @@ -2019,7 +2239,8 @@ inline Value REF_FN(Injection& inj, const Value& val, Injection refInj(Value::undef(), Value::undef()); refInj.dpath = dpath_slice; auto fp = std::make_shared(); - for (auto& p : dpath_slice) fp->push_back(Value(p)); + for (auto& p : dpath_slice) + fp->push_back(Value(p)); refInj.dparent = getpath_v(spec, Value(fp)); refInj.handler = injecthandler; Value refResolved = getpath_v(spec, refpath, &refInj); @@ -2027,26 +2248,29 @@ inline Value REF_FN(Injection& inj, const Value& val, Value tref = clone(refResolved); bool hasSubRef = false; if (isnode(tref)) { - walk_v(tref, [&](const Value&, const Value& v, const Value&, - const std::vector&) -> Value { - if (v.is_string() && v.as_string() == "`$REF`") hasSubRef = true; - return v; - }); + walk_v( + tref, + [&](const Value&, const Value& v, const Value&, const std::vector&) -> Value { + if (v.is_string() && v.as_string() == "`$REF`") + hasSubRef = true; + return v; + }); } std::vector cpath_v; if (inj.path.size() >= 3) { - cpath_v = std::vector(inj.path.begin(), - inj.path.begin() + (inj.path.size() - 3)); + cpath_v = std::vector(inj.path.begin(), inj.path.begin() + (inj.path.size() - 3)); } std::vector tpath_v; if (!inj.path.empty()) { tpath_v = std::vector(inj.path.begin(), inj.path.end() - 1); } auto cpath_list = std::make_shared(); - for (auto& p : cpath_v) cpath_list->push_back(Value(p)); + for (auto& p : cpath_v) + cpath_list->push_back(Value(p)); auto tpath_list = std::make_shared(); - for (auto& p : tpath_v) tpath_list->push_back(Value(p)); + for (auto& p : tpath_v) + tpath_list->push_back(Value(p)); Value tval_at = getpath_v(store, Value(tpath_list)); Value rval = Value::undef(); @@ -2059,8 +2283,7 @@ inline Value REF_FN(Injection& inj, const Value& val, Injection* tinj = tinj_owner.get(); tinj->path = tpath_v; if (inj.nodes && inj.nodes->size() >= 1) { - tinj->nodes = std::make_shared>( - inj.nodes->begin(), inj.nodes->end() - 1); + tinj->nodes = std::make_shared>(inj.nodes->begin(), inj.nodes->end() - 1); } if (inj.nodes && inj.nodes->size() >= 2) { tinj->parent = (*inj.nodes)[inj.nodes->size() - 2]; @@ -2080,7 +2303,7 @@ inline Value REF_FN(Injection& inj, const Value& val, return val; } -} // namespace transforms +} // namespace transforms // =========================================================================== // transform() @@ -2100,7 +2323,8 @@ inline Value transform(const Value& data, const Value& spec, const Value& option std::shared_ptr> errs; if (collect) { errs = std::make_shared>(); - for (const auto& e : *errsRaw.as_list()) errs->push_back(e); + for (const auto& e : *errsRaw.as_list()) + errs->push_back(e); } else { errs = std::make_shared>(); } @@ -2110,48 +2334,49 @@ inline Value transform(const Value& data, const Value& spec, const Value& option auto extraData = std::shared_ptr(new Map()); if (extra.is_map()) { for (const auto& [k, v] : *extra.as_map()) { - if (!k.empty() && k[0] == '$') extraTransforms->set(k, v); - else extraData->set(k, v); + if (!k.empty() && k[0] == '$') + extraTransforms->set(k, v); + else + extraData->set(k, v); } } auto dataMergeList = std::make_shared(); - if (!extraData->empty()) dataMergeList->push_back(Value(extraData)); + if (!extraData->empty()) + dataMergeList->push_back(Value(extraData)); dataMergeList->push_back(clone(data)); Value dataClone = merge_v(Value(dataMergeList)); auto baseStore = std::shared_ptr(new Map()); baseStore->set(S_DTOP(), dataClone); // $SPEC as Injector that returns origspec. - Injector spec_supplier = [origspec](Injection&, const Value&, const std::string&, const Value&) -> Value { - return origspec; - }; + Injector spec_supplier = [origspec](Injection&, const Value&, const std::string&, + const Value&) -> Value { return origspec; }; baseStore->set(S_DSPEC(), Value(spec_supplier)); // $BT, $DS, $WHEN as Injectors (called via _injecthandler when matched). - baseStore->set("$BT", Value(Injector([](Injection&, const Value&, const std::string&, const Value&) -> Value { - return Value("`"); - }))); - baseStore->set("$DS", Value(Injector([](Injection&, const Value&, const std::string&, const Value&) -> Value { - return Value("$"); - }))); - baseStore->set("$WHEN", Value(Injector([](Injection&, const Value&, const std::string&, const Value&) -> Value { - // ISO timestamp. - auto t = std::time(nullptr); - auto* tm = std::gmtime(&t); - char buf[40]; - std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.000Z", tm); - return Value(std::string(buf)); - }))); + baseStore->set("$BT", Value(Injector([](Injection&, const Value&, const std::string&, + const Value&) -> Value { return Value("`"); }))); + baseStore->set("$DS", Value(Injector([](Injection&, const Value&, const std::string&, + const Value&) -> Value { return Value("$"); }))); + baseStore->set("$WHEN", Value(Injector([](Injection&, const Value&, const std::string&, + const Value&) -> Value { + // ISO timestamp. + auto t = std::time(nullptr); + auto* tm = std::gmtime(&t); + char buf[40]; + std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.000Z", tm); + return Value(std::string(buf)); + }))); baseStore->set("$DELETE", Value(Injector(transforms::DELETE_FN))); - baseStore->set("$COPY", Value(Injector(transforms::COPY_FN))); - baseStore->set("$KEY", Value(Injector(transforms::KEY_FN))); - baseStore->set("$ANNO", Value(Injector(transforms::ANNO_FN))); - baseStore->set("$MERGE", Value(Injector(transforms::MERGE_FN))); - baseStore->set("$EACH", Value(Injector(transforms::EACH_FN))); - baseStore->set("$PACK", Value(Injector(transforms::PACK_FN))); - baseStore->set("$REF", Value(Injector(transforms::REF_FN))); + baseStore->set("$COPY", Value(Injector(transforms::COPY_FN))); + baseStore->set("$KEY", Value(Injector(transforms::KEY_FN))); + baseStore->set("$ANNO", Value(Injector(transforms::ANNO_FN))); + baseStore->set("$MERGE", Value(Injector(transforms::MERGE_FN))); + baseStore->set("$EACH", Value(Injector(transforms::EACH_FN))); + baseStore->set("$PACK", Value(Injector(transforms::PACK_FN))); + baseStore->set("$REF", Value(Injector(transforms::REF_FN))); baseStore->set("$FORMAT", Value(Injector(transforms::FORMAT_FN))); - baseStore->set("$APPLY", Value(Injector(transforms::APPLY_FN))); + baseStore->set("$APPLY", Value(Injector(transforms::APPLY_FN))); auto storeMergeList = std::make_shared(); storeMergeList->push_back(Value(baseStore)); @@ -2168,20 +2393,25 @@ inline Value transform(const Value& data, const Value& spec, const Value& option Injection injdef(workspec, Value::undef()); injdef.prior = nullptr; - if (modifyRaw.is_modify()) injdef.modify = modifyRaw.as_modify(); - if (handlerRaw.is_injector()) injdef.handler = handlerRaw.as_injector(); - if (metaRaw.is_map()) injdef.meta = metaRaw.as_map(); + if (modifyRaw.is_modify()) + injdef.modify = modifyRaw.as_modify(); + if (handlerRaw.is_injector()) + injdef.handler = handlerRaw.as_injector(); + if (metaRaw.is_map()) + injdef.meta = metaRaw.as_map(); injdef.errs = errs; Value out = inject(workspec, store, &injdef); // Sync $ERRS list back to errs. - for (const auto& e : *errsListPtr) errs->push_back(e); + for (const auto& e : *errsListPtr) + errs->push_back(e); if (!errs->empty() && !collect) { std::string msg; for (size_t i = 0; i < errs->size(); i++) { - if (i > 0) msg += " | "; + if (i > 0) + msg += " | "; msg += stringify((*errs)[i]); } throw std::runtime_error(msg); @@ -2190,7 +2420,8 @@ inline Value transform(const Value& data, const Value& spec, const Value& option if (collect && errsRaw.is_list()) { auto rawList = errsRaw.as_list(); rawList->clear(); - for (const auto& e : *errs) rawList->push_back(e); + for (const auto& e : *errs) + rawList->push_back(e); } return out; } @@ -2201,8 +2432,8 @@ inline Value transform(const Value& data, const Value& spec, const Value& option namespace validators { -inline Value STRING_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value STRING_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { Value out = getprop(inj.dparent, Value(inj.key)); int t = typify(out); if ((T_string & t) == 0) { @@ -2211,22 +2442,27 @@ inline Value STRING_FN(Injection& inj, const Value& val, } if (out.as_string().empty()) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); + for (auto& p : inj.path) + pl->push_back(Value(p)); inj.errs->push_back(Value("Empty string at " + pathify(Value(pl), 1, 0))); return Value::undef(); } return out; } -inline Value TYPE_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value TYPE_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { std::string tname = ref.size() >= 2 ? ref.substr(1) : ""; - for (auto& c : tname) c = static_cast(std::tolower(static_cast(c))); + for (auto& c : tname) + c = static_cast(std::tolower(static_cast(c))); int idx = -1; for (int i = 0; i < 26; i++) { - if (typename_table(i) == tname) { idx = i; break; } + if (typename_table(i) == tname) { + idx = i; + break; + } } - if (idx < 0) return Value::undef(); + if (idx < 0) + return Value::undef(); int typev = 1 << (31 - idx); Value out = getprop(inj.dparent, Value(inj.key)); int t = typify(out); @@ -2237,13 +2473,12 @@ inline Value TYPE_FN(Injection& inj, const Value& val, return out; } -inline Value ANY_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value ANY_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { return getprop(inj.dparent, Value(inj.key)); } -inline Value CHILD_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value CHILD_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { if (inj.mode == M_KEYPRE) { Value childtm = getprop(inj.parent, Value(inj.key)); Value pkey = inj.path.size() >= 2 ? Value(inj.path[inj.path.size() - 2]) : Value::undef(); @@ -2253,14 +2488,17 @@ inline Value CHILD_FN(Injection& inj, const Value& val, tval = Value(std::shared_ptr(new Map())); } else if (!ismap(tval)) { auto pl = std::make_shared(); - for (size_t i = 0; i + 1 < inj.path.size(); i++) pl->push_back(Value(inj.path[i])); - inj.errs->push_back(Value(invalid_type_msg(Value(pl), "object", typify(tval), tval, "V0220"))); + for (size_t i = 0; i + 1 < inj.path.size(); i++) + pl->push_back(Value(inj.path[i])); + inj.errs->push_back( + Value(invalid_type_msg(Value(pl), "object", typify(tval), tval, "V0220"))); return Value::undef(); } auto ckeys = keysof(tval); for (const auto& ck : ckeys) { setprop(inj.parent, Value(ck), clone(childtm)); - if (inj.keys) inj.keys->push_back(ck); + if (inj.keys) + inj.keys->push_back(ck); } inj.setval(Value::undef()); return Value::undef(); @@ -2277,8 +2515,10 @@ inline Value CHILD_FN(Injection& inj, const Value& val, } if (!inj.dparent.is_list()) { auto pl = std::make_shared(); - for (size_t i = 0; i + 1 < inj.path.size(); i++) pl->push_back(Value(inj.path[i])); - inj.errs->push_back(Value(invalid_type_msg(Value(pl), "list", typify(inj.dparent), inj.dparent, "V0230"))); + for (size_t i = 0; i + 1 < inj.path.size(); i++) + pl->push_back(Value(inj.path[i])); + inj.errs->push_back( + Value(invalid_type_msg(Value(pl), "list", typify(inj.dparent), inj.dparent, "V0230"))); inj.keyI = static_cast(size(inj.parent)); return inj.dparent; } @@ -2287,36 +2527,41 @@ inline Value CHILD_FN(Injection& inj, const Value& val, for (size_t i = 0; i < dpl->size(); i++) { setprop(inj.parent, Value(static_cast(i)), clone(childtm)); } - while (pl->size() > dpl->size()) pl->pop_back(); + while (pl->size() > dpl->size()) + pl->pop_back(); inj.keyI = 0; return getprop(inj.dparent, Value(int64_t(0))); } return Value::undef(); } -inline Value ONE_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_VAL) return Value::undef(); +inline Value ONE_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_VAL) + return Value::undef(); if (!inj.parent.is_list() || inj.keyI != 0) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); + for (auto& p : inj.path) + pl->push_back(Value(p)); inj.errs->push_back(Value("The $ONE validator at field " + pathify(Value(pl), 1, 1) + " must be the first element of an array.")); return Value::undef(); } inj.keyI = static_cast(inj.keys ? inj.keys->size() : 0); inj.setval(inj.dparent, 2); - if (!inj.path.empty()) inj.path.pop_back(); + if (!inj.path.empty()) + inj.path.pop_back(); inj.key = inj.path.empty() ? "" : inj.path.back(); std::vector tvals; if (inj.parent.is_list()) { auto pl = inj.parent.as_list(); - for (size_t i = 1; i < pl->size(); i++) tvals.push_back((*pl)[i]); + for (size_t i = 1; i < pl->size(); i++) + tvals.push_back((*pl)[i]); } if (tvals.empty()) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); + for (auto& p : inj.path) + pl->push_back(Value(p)); inj.errs->push_back(Value("The $ONE validator at field " + pathify(Value(pl), 1, 1) + " must have at least one argument.")); return Value::undef(); @@ -2325,14 +2570,16 @@ inline Value ONE_FN(Injection& inj, const Value& val, auto terrs = std::make_shared>(); auto vstore = std::shared_ptr(new Map()); if (store.is_map()) { - for (const auto& [k, v] : *store.as_map()) vstore->set(k, v); + for (const auto& [k, v] : *store.as_map()) + vstore->set(k, v); } vstore->set(S_DTOP(), inj.dparent); auto opts = std::shared_ptr(new Map()); auto terrs_list = std::make_shared(); opts->set("extra", Value(vstore)); opts->set("errs", Value(terrs_list)); - if (inj.meta) opts->set("meta", Value(inj.meta)); + if (inj.meta) + opts->set("meta", Value(inj.meta)); Value vcurrent; try { vcurrent = validate(inj.dparent, tval, Value(opts)); @@ -2341,53 +2588,60 @@ inline Value ONE_FN(Injection& inj, const Value& val, vcurrent = inj.dparent; } inj.setval(vcurrent, -2); - if (terrs_list->empty()) return Value::undef(); + if (terrs_list->empty()) + return Value::undef(); } // No match. std::string valdesc; for (size_t i = 0; i < tvals.size(); i++) { - if (i > 0) valdesc += ", "; + if (i > 0) + valdesc += ", "; if (tvals[i].is_string()) { std::smatch m; const std::string& s = tvals[i].as_string(); if (std::regex_match(s, m, R_TRANSFORM_NAME())) { std::string lower = m[1].str(); - for (auto& c : lower) c = static_cast(std::tolower(static_cast(c))); + for (auto& c : lower) + c = static_cast(std::tolower(static_cast(c))); valdesc += lower; continue; } } valdesc += stringify(tvals[i]); } - inj.errs->push_back(Value(invalid_type_msg( - inj.path, (tvals.size() > 1 ? "one of " : "") + valdesc, - typify(inj.dparent), inj.dparent, "V0210"))); + inj.errs->push_back( + Value(invalid_type_msg(inj.path, (tvals.size() > 1 ? "one of " : "") + valdesc, + typify(inj.dparent), inj.dparent, "V0210"))); return Value::undef(); } -inline Value EXACT_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value EXACT_FN(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { if (inj.mode == M_VAL) { if (!inj.parent.is_list() || inj.keyI != 0) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); + for (auto& p : inj.path) + pl->push_back(Value(p)); inj.errs->push_back(Value("The $EXACT validator at field " + pathify(Value(pl), 1, 1) + " must be the first element of an array.")); return Value::undef(); } inj.keyI = static_cast(inj.keys ? inj.keys->size() : 0); inj.setval(inj.dparent, 2); - if (!inj.path.empty()) inj.path.pop_back(); + if (!inj.path.empty()) + inj.path.pop_back(); inj.key = inj.path.empty() ? "" : inj.path.back(); std::vector tvals; if (inj.parent.is_list()) { auto pl = inj.parent.as_list(); - for (size_t i = 1; i < pl->size(); i++) tvals.push_back((*pl)[i]); + for (size_t i = 1; i < pl->size(); i++) + tvals.push_back((*pl)[i]); } if (tvals.empty()) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); + for (auto& p : inj.path) + pl->push_back(Value(p)); inj.errs->push_back(Value("The $EXACT validator at field " + pathify(Value(pl), 1, 1) + " must have at least one argument.")); return Value::undef(); @@ -2397,32 +2651,38 @@ inline Value EXACT_FN(Injection& inj, const Value& val, for (const auto& tval : tvals) { bool exactmatch = (tval == inj.dparent); if (!exactmatch && isnode(tval)) { - if (!currentstr_set) { currentstr = stringify(inj.dparent); currentstr_set = true; } + if (!currentstr_set) { + currentstr = stringify(inj.dparent); + currentstr_set = true; + } std::string ts = stringify(tval); exactmatch = (ts == currentstr); } - if (exactmatch) return Value::undef(); + if (exactmatch) + return Value::undef(); } std::string valdesc; for (size_t i = 0; i < tvals.size(); i++) { - if (i > 0) valdesc += ", "; + if (i > 0) + valdesc += ", "; if (tvals[i].is_string()) { std::smatch m; const std::string& s = tvals[i].as_string(); if (std::regex_match(s, m, R_TRANSFORM_NAME())) { std::string lower = m[1].str(); - for (auto& c : lower) c = static_cast(std::tolower(static_cast(c))); + for (auto& c : lower) + c = static_cast(std::tolower(static_cast(c))); valdesc += lower; continue; } } valdesc += stringify(tvals[i]); } - inj.errs->push_back(Value(invalid_type_msg( - inj.path, - std::string(inj.path.size() > 1 ? "" : "value ") + - "exactly equal to " + (tvals.size() == 1 ? "" : "one of ") + valdesc, - typify(inj.dparent), inj.dparent, "V0110"))); + inj.errs->push_back(Value(invalid_type_msg(inj.path, + std::string(inj.path.size() > 1 ? "" : "value ") + + "exactly equal to " + + (tvals.size() == 1 ? "" : "one of ") + valdesc, + typify(inj.dparent), inj.dparent, "V0110"))); return Value::undef(); } else { delprop(inj.parent, Value(inj.key)); @@ -2430,22 +2690,25 @@ inline Value EXACT_FN(Injection& inj, const Value& val, } } -} // namespace validators +} // namespace validators // =========================================================================== // _validation Modify // =========================================================================== -inline void _validation(const Value& pval, const Value& key, const Value& parent, - Injection& inj, const Value& store) { - if (is_skip(pval)) return; +inline void _validation(const Value& pval, const Value& key, const Value& parent, Injection& inj, + const Value& store) { + if (is_skip(pval)) + return; bool exact = false; if (inj.meta) { Value* e = inj.meta->find(S_BEXACT()); - if (e && e->is_bool()) exact = e->as_bool(); + if (e && e->is_bool()) + exact = e->as_bool(); } Value cval = getprop(inj.dparent, key); - if (!exact && (cval.is_undef() || cval.is_null())) return; + if (!exact && (cval.is_undef() || cval.is_null())) + return; int ptype = typify(pval); if ((T_string & ptype) && pval.is_string() && pval.as_string().find('$') != std::string::npos) { @@ -2453,13 +2716,15 @@ inline void _validation(const Value& pval, const Value& key, const Value& parent } int ctype = typify(cval); if (ptype != ctype && !pval.is_undef()) { - inj.errs->push_back(Value(invalid_type_msg(inj.path, typename_str(ptype), ctype, cval, "V0010"))); + inj.errs->push_back( + Value(invalid_type_msg(inj.path, typename_str(ptype), ctype, cval, "V0010"))); return; } if (ismap(cval)) { if (!ismap(pval)) { - inj.errs->push_back(Value(invalid_type_msg(inj.path, typename_str(ptype), ctype, cval, "V0020"))); + inj.errs->push_back( + Value(invalid_type_msg(inj.path, typename_str(ptype), ctype, cval, "V0020"))); return; } auto ckeys = keysof(cval); @@ -2469,44 +2734,52 @@ inline void _validation(const Value& pval, const Value& key, const Value& parent if (!pkeys.empty() && !is_open) { std::vector badkeys; for (auto& ck : ckeys) { - if (!haskey(pval, Value(ck))) badkeys.push_back(ck); + if (!haskey(pval, Value(ck))) + badkeys.push_back(ck); } if (!badkeys.empty()) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); + for (auto& p : inj.path) + pl->push_back(Value(p)); std::string joined; for (size_t i = 0; i < badkeys.size(); i++) { - if (i > 0) joined += ", "; + if (i > 0) + joined += ", "; joined += badkeys[i]; } - inj.errs->push_back(Value("Unexpected keys at field " + pathify(Value(pl), 1, 0) + ": " + joined)); + inj.errs->push_back( + Value("Unexpected keys at field " + pathify(Value(pl), 1, 0) + ": " + joined)); } } else { auto args_l = std::make_shared(); args_l->push_back(pval); args_l->push_back(cval); merge_v(Value(args_l)); - if (isnode(pval)) delprop(pval, Value(S_BOPEN())); + if (isnode(pval)) + delprop(pval, Value(S_BOPEN())); } } else if (islist(cval)) { if (!islist(pval)) { - inj.errs->push_back(Value(invalid_type_msg(inj.path, typename_str(ptype), ctype, cval, "V0030"))); + inj.errs->push_back( + Value(invalid_type_msg(inj.path, typename_str(ptype), ctype, cval, "V0030"))); } } else if (exact) { if (cval != pval) { auto pl = std::make_shared(); - for (auto& p : inj.path) pl->push_back(Value(p)); - std::string pathmsg = inj.path.size() > 1 ? "at field " + pathify(Value(pl), 1, 0) + ": " : ""; - inj.errs->push_back(Value("Value " + pathmsg + js_string(cval) + - " should equal " + js_string(pval) + ".")); + for (auto& p : inj.path) + pl->push_back(Value(p)); + std::string pathmsg = + inj.path.size() > 1 ? "at field " + pathify(Value(pl), 1, 0) + ": " : ""; + inj.errs->push_back( + Value("Value " + pathmsg + js_string(cval) + " should equal " + js_string(pval) + ".")); } } else { setprop(parent, key, cval); } } -inline Value _validatehandler(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { +inline Value _validatehandler(Injection& inj, const Value& val, const std::string& ref, + const Value& store) { if (!ref.empty()) { std::smatch m; if (std::regex_match(ref, m, R_META_PATH())) { @@ -2532,43 +2805,45 @@ inline Value _validatehandler(Injection& inj, const Value& val, inline Value validate(const Value& data, const Value& spec, const Value& options) { Value extraRaw = options.is_map() ? getprop(options, Value("extra")) : Value::undef(); - Value errsRaw = options.is_map() ? getprop(options, Value("errs")) : Value::undef(); - Value metaRaw = options.is_map() ? getprop(options, Value("meta")) : Value::undef(); + Value errsRaw = options.is_map() ? getprop(options, Value("errs")) : Value::undef(); + Value metaRaw = options.is_map() ? getprop(options, Value("meta")) : Value::undef(); bool collect = errsRaw.is_list(); std::shared_ptr> errs = std::make_shared>(); if (collect) { - for (const auto& e : *errsRaw.as_list()) errs->push_back(e); + for (const auto& e : *errsRaw.as_list()) + errs->push_back(e); } auto baseStore = std::shared_ptr(new Map()); baseStore->set("$DELETE", Value(nullptr)); - baseStore->set("$COPY", Value(nullptr)); - baseStore->set("$KEY", Value(nullptr)); - baseStore->set("$META", Value(nullptr)); - baseStore->set("$MERGE", Value(nullptr)); - baseStore->set("$EACH", Value(nullptr)); - baseStore->set("$PACK", Value(nullptr)); - - baseStore->set("$STRING", Value(Injector(validators::STRING_FN))); - baseStore->set("$NUMBER", Value(Injector(validators::TYPE_FN))); - baseStore->set("$INTEGER", Value(Injector(validators::TYPE_FN))); - baseStore->set("$DECIMAL", Value(Injector(validators::TYPE_FN))); - baseStore->set("$BOOLEAN", Value(Injector(validators::TYPE_FN))); - baseStore->set("$NULL", Value(Injector(validators::TYPE_FN))); - baseStore->set("$NIL", Value(Injector(validators::TYPE_FN))); - baseStore->set("$MAP", Value(Injector(validators::TYPE_FN))); - baseStore->set("$LIST", Value(Injector(validators::TYPE_FN))); + baseStore->set("$COPY", Value(nullptr)); + baseStore->set("$KEY", Value(nullptr)); + baseStore->set("$META", Value(nullptr)); + baseStore->set("$MERGE", Value(nullptr)); + baseStore->set("$EACH", Value(nullptr)); + baseStore->set("$PACK", Value(nullptr)); + + baseStore->set("$STRING", Value(Injector(validators::STRING_FN))); + baseStore->set("$NUMBER", Value(Injector(validators::TYPE_FN))); + baseStore->set("$INTEGER", Value(Injector(validators::TYPE_FN))); + baseStore->set("$DECIMAL", Value(Injector(validators::TYPE_FN))); + baseStore->set("$BOOLEAN", Value(Injector(validators::TYPE_FN))); + baseStore->set("$NULL", Value(Injector(validators::TYPE_FN))); + baseStore->set("$NIL", Value(Injector(validators::TYPE_FN))); + baseStore->set("$MAP", Value(Injector(validators::TYPE_FN))); + baseStore->set("$LIST", Value(Injector(validators::TYPE_FN))); baseStore->set("$FUNCTION", Value(Injector(validators::TYPE_FN))); baseStore->set("$INSTANCE", Value(Injector(validators::TYPE_FN))); - baseStore->set("$ANY", Value(Injector(validators::ANY_FN))); - baseStore->set("$CHILD", Value(Injector(validators::CHILD_FN))); - baseStore->set("$ONE", Value(Injector(validators::ONE_FN))); - baseStore->set("$EXACT", Value(Injector(validators::EXACT_FN))); + baseStore->set("$ANY", Value(Injector(validators::ANY_FN))); + baseStore->set("$CHILD", Value(Injector(validators::CHILD_FN))); + baseStore->set("$ONE", Value(Injector(validators::ONE_FN))); + baseStore->set("$EXACT", Value(Injector(validators::EXACT_FN))); auto mergeList = std::make_shared(); mergeList->push_back(Value(baseStore)); - if (extraRaw.is_map()) mergeList->push_back(extraRaw); + if (extraRaw.is_map()) + mergeList->push_back(extraRaw); auto errsListPtr = std::make_shared(); auto errsHolder = std::shared_ptr(new Map()); errsHolder->set(S_DERRS(), Value(errsListPtr)); @@ -2578,11 +2853,13 @@ inline Value validate(const Value& data, const Value& spec, const Value& options std::shared_ptr meta; if (metaRaw.is_map()) { meta = std::shared_ptr(new Map()); - for (const auto& [k, v] : *metaRaw.as_map()) meta->set(k, v); + for (const auto& [k, v] : *metaRaw.as_map()) + meta->set(k, v); } else { meta = std::shared_ptr(new Map()); } - if (!meta->find(S_BEXACT())) meta->set(S_BEXACT(), Value(false)); + if (!meta->find(S_BEXACT())) + meta->set(S_BEXACT(), Value(false)); auto opts = std::shared_ptr(new Map()); opts->set("meta", Value(meta)); @@ -2590,19 +2867,22 @@ inline Value validate(const Value& data, const Value& spec, const Value& options opts->set("modify", Value(Modify(_validation))); opts->set("handler", Value(Injector(_validatehandler))); auto opt_errs_list = std::make_shared(); - for (const auto& e : *errs) opt_errs_list->push_back(e); + for (const auto& e : *errs) + opt_errs_list->push_back(e); opts->set("errs", Value(opt_errs_list)); Value out = transform(data, spec, Value(opts)); // Sync opt_errs_list back to errs. errs->clear(); - for (const auto& e : *opt_errs_list) errs->push_back(e); + for (const auto& e : *opt_errs_list) + errs->push_back(e); if (!errs->empty() && !collect) { std::string msg; for (size_t i = 0; i < errs->size(); i++) { - if (i > 0) msg += " | "; + if (i > 0) + msg += " | "; msg += stringify((*errs)[i]); } throw std::runtime_error(msg); @@ -2610,7 +2890,8 @@ inline Value validate(const Value& data, const Value& spec, const Value& options if (collect && errsRaw.is_list()) { auto rl = errsRaw.as_list(); rl->clear(); - for (const auto& e : *errs) rl->push_back(e); + for (const auto& e : *errs) + rl->push_back(e); } return out; } @@ -2622,11 +2903,11 @@ inline Value validate(const Value& data, const Value& spec, const Value& options namespace selectors { inline std::shared_ptr recOpts(const Value& store, const Value& point, - std::shared_ptr meta, - std::shared_ptr errs_list) { + std::shared_ptr meta, std::shared_ptr errs_list) { auto vstore = std::shared_ptr(new Map()); if (store.is_map()) { - for (const auto& [k, v] : *store.as_map()) vstore->set(k, v); + for (const auto& [k, v] : *store.as_map()) + vstore->set(k, v); } vstore->set(S_DTOP(), point); auto opts = std::shared_ptr(new Map()); @@ -2636,120 +2917,146 @@ inline std::shared_ptr recOpts(const Value& store, const Value& point, return opts; } -inline Value AND_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_KEYPRE) return Value::undef(); +inline Value AND_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_KEYPRE) + return Value::undef(); Value terms = getprop(inj.parent, Value(inj.key)); - if (!terms.is_list()) return Value::undef(); + if (!terms.is_list()) + return Value::undef(); auto ppath = std::make_shared(); - for (size_t i = 0; i + 1 < inj.path.size(); i++) ppath->push_back(Value(inj.path[i])); + for (size_t i = 0; i + 1 < inj.path.size(); i++) + ppath->push_back(Value(inj.path[i])); Value point = getpath_v(store, Value(ppath)); for (const auto& term : *terms.as_list()) { auto terrs = std::make_shared(); auto opts = recOpts(store, point, inj.meta, terrs); - try { validate(point, term, Value(opts)); } - catch (const std::exception& e) { terrs->push_back(Value(std::string(e.what()))); } + try { + validate(point, term, Value(opts)); + } catch (const std::exception& e) { + terrs->push_back(Value(std::string(e.what()))); + } if (!terrs->empty()) { - inj.errs->push_back(Value("AND:" + pathify(Value(ppath)) + ": " + - stringify(point) + " fail:" + stringify(terms))); + inj.errs->push_back(Value("AND:" + pathify(Value(ppath)) + ": " + stringify(point) + + " fail:" + stringify(terms))); } } Value gkey = inj.path.size() >= 2 ? Value(inj.path[inj.path.size() - 2]) : Value::undef(); Value gp; - if (inj.nodes && inj.nodes->size() >= 2) gp = (*inj.nodes)[inj.nodes->size() - 2]; + if (inj.nodes && inj.nodes->size() >= 2) + gp = (*inj.nodes)[inj.nodes->size() - 2]; setprop(gp, gkey, point); return Value::undef(); } -inline Value OR_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_KEYPRE) return Value::undef(); +inline Value OR_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_KEYPRE) + return Value::undef(); Value terms = getprop(inj.parent, Value(inj.key)); - if (!terms.is_list()) return Value::undef(); + if (!terms.is_list()) + return Value::undef(); auto ppath = std::make_shared(); - for (size_t i = 0; i + 1 < inj.path.size(); i++) ppath->push_back(Value(inj.path[i])); + for (size_t i = 0; i + 1 < inj.path.size(); i++) + ppath->push_back(Value(inj.path[i])); Value point = getpath_v(store, Value(ppath)); for (const auto& term : *terms.as_list()) { auto terrs = std::make_shared(); auto opts = recOpts(store, point, inj.meta, terrs); - try { validate(point, term, Value(opts)); } - catch (const std::exception& e) { terrs->push_back(Value(std::string(e.what()))); } + try { + validate(point, term, Value(opts)); + } catch (const std::exception& e) { + terrs->push_back(Value(std::string(e.what()))); + } if (terrs->empty()) { Value gkey = inj.path.size() >= 2 ? Value(inj.path[inj.path.size() - 2]) : Value::undef(); Value gp; - if (inj.nodes && inj.nodes->size() >= 2) gp = (*inj.nodes)[inj.nodes->size() - 2]; + if (inj.nodes && inj.nodes->size() >= 2) + gp = (*inj.nodes)[inj.nodes->size() - 2]; setprop(gp, gkey, point); return Value::undef(); } } - inj.errs->push_back(Value("OR:" + pathify(Value(ppath)) + ": " + - stringify(point) + " fail:" + stringify(terms))); + inj.errs->push_back( + Value("OR:" + pathify(Value(ppath)) + ": " + stringify(point) + " fail:" + stringify(terms))); return Value::undef(); } -inline Value NOT_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_KEYPRE) return Value::undef(); +inline Value NOT_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_KEYPRE) + return Value::undef(); Value term = getprop(inj.parent, Value(inj.key)); auto ppath = std::make_shared(); - for (size_t i = 0; i + 1 < inj.path.size(); i++) ppath->push_back(Value(inj.path[i])); + for (size_t i = 0; i + 1 < inj.path.size(); i++) + ppath->push_back(Value(inj.path[i])); Value point = getpath_v(store, Value(ppath)); auto terrs = std::make_shared(); auto opts = recOpts(store, point, inj.meta, terrs); - try { validate(point, term, Value(opts)); } - catch (const std::exception& e) { terrs->push_back(Value(std::string(e.what()))); } + try { + validate(point, term, Value(opts)); + } catch (const std::exception& e) { + terrs->push_back(Value(std::string(e.what()))); + } if (terrs->empty()) { - inj.errs->push_back(Value("NOT:" + pathify(Value(ppath)) + ": " + - stringify(point) + " fail:" + stringify(term))); + inj.errs->push_back(Value("NOT:" + pathify(Value(ppath)) + ": " + stringify(point) + + " fail:" + stringify(term))); } Value gkey = inj.path.size() >= 2 ? Value(inj.path[inj.path.size() - 2]) : Value::undef(); Value gp; - if (inj.nodes && inj.nodes->size() >= 2) gp = (*inj.nodes)[inj.nodes->size() - 2]; + if (inj.nodes && inj.nodes->size() >= 2) + gp = (*inj.nodes)[inj.nodes->size() - 2]; setprop(gp, gkey, point); return Value::undef(); } -inline Value CMP_FN(Injection& inj, const Value& val, - const std::string& ref, const Value& store) { - if (inj.mode != M_KEYPRE) return Value::undef(); +inline Value CMP_FN(Injection& inj, const Value& val, const std::string& ref, const Value& store) { + if (inj.mode != M_KEYPRE) + return Value::undef(); Value term = getprop(inj.parent, Value(inj.key)); Value gkey = inj.path.size() >= 2 ? Value(inj.path[inj.path.size() - 2]) : Value::undef(); auto ppath = std::make_shared(); - for (size_t i = 0; i + 1 < inj.path.size(); i++) ppath->push_back(Value(inj.path[i])); + for (size_t i = 0; i + 1 < inj.path.size(); i++) + ppath->push_back(Value(inj.path[i])); Value point = getpath_v(store, Value(ppath)); bool pass = false; if (point.is_number() && term.is_number()) { double a = point.as_double(); double b = term.as_double(); - if (ref == "$GT") pass = a > b; - else if (ref == "$LT") pass = a < b; - else if (ref == "$GTE") pass = a >= b; - else if (ref == "$LTE") pass = a <= b; + if (ref == "$GT") + pass = a > b; + else if (ref == "$LT") + pass = a < b; + else if (ref == "$GTE") + pass = a >= b; + else if (ref == "$LTE") + pass = a <= b; } else if (ref == "$LIKE" && term.is_string()) { try { std::regex pat(term.as_string()); pass = std::regex_search(stringify(point), pat); - } catch (...) { pass = false; } + } catch (...) { + pass = false; + } } if (pass) { Value gp; - if (inj.nodes && inj.nodes->size() >= 2) gp = (*inj.nodes)[inj.nodes->size() - 2]; + if (inj.nodes && inj.nodes->size() >= 2) + gp = (*inj.nodes)[inj.nodes->size() - 2]; setprop(gp, gkey, point); } else { - inj.errs->push_back(Value("CMP: " + pathify(Value(ppath)) + ": " + - stringify(point) + " fail:" + ref + " " + stringify(term))); + inj.errs->push_back(Value("CMP: " + pathify(Value(ppath)) + ": " + stringify(point) + + " fail:" + ref + " " + stringify(term))); } return Value::undef(); } -} // namespace selectors +} // namespace selectors inline std::vector select(const Value& children, const Value& query) { - if (!isnode(children)) return {}; + if (!isnode(children)) + return {}; std::vector childList; if (ismap(children)) { @@ -2775,23 +3082,23 @@ inline std::vector select(const Value& children, const Value& query) { auto extra = std::shared_ptr(new Map()); extra->set("$AND", Value(Injector(selectors::AND_FN))); - extra->set("$OR", Value(Injector(selectors::OR_FN))); + extra->set("$OR", Value(Injector(selectors::OR_FN))); extra->set("$NOT", Value(Injector(selectors::NOT_FN))); - extra->set("$GT", Value(Injector(selectors::CMP_FN))); - extra->set("$LT", Value(Injector(selectors::CMP_FN))); + extra->set("$GT", Value(Injector(selectors::CMP_FN))); + extra->set("$LT", Value(Injector(selectors::CMP_FN))); extra->set("$GTE", Value(Injector(selectors::CMP_FN))); extra->set("$LTE", Value(Injector(selectors::CMP_FN))); - extra->set("$LIKE",Value(Injector(selectors::CMP_FN))); + extra->set("$LIKE", Value(Injector(selectors::CMP_FN))); Value q = clone(query); - walk_v(q, [](const Value&, const Value& v, const Value&, - const std::vector&) -> Value { - if (ismap(v)) { - Value cur = getprop(v, Value(S_BOPEN())); - setprop(v, Value(S_BOPEN()), cur.is_undef() ? Value(true) : cur); - } - return v; - }); + walk_v(q, + [](const Value&, const Value& v, const Value&, const std::vector&) -> Value { + if (ismap(v)) { + Value cur = getprop(v, Value(S_BOPEN())); + setprop(v, Value(S_BOPEN()), cur.is_undef() ? Value(true) : cur); + } + return v; + }); std::vector results; for (const auto& child : childList) { @@ -2800,14 +3107,18 @@ inline std::vector select(const Value& children, const Value& query) { opts->set("errs", Value(errs_list)); opts->set("meta", Value(meta)); opts->set("extra", Value(extra)); - try { validate(child, clone(q), Value(opts)); } - catch (const std::exception& e) { errs_list->push_back(Value(std::string(e.what()))); } - if (errs_list->empty()) results.push_back(child); + try { + validate(child, clone(q), Value(opts)); + } catch (const std::exception& e) { + errs_list->push_back(Value(std::string(e.what()))); + } + if (errs_list->empty()) + results.push_back(child); } return results; } -} // namespace structlib -} // namespace voxgig +} // namespace structlib +} // namespace voxgig -#endif // VOXGIG_STRUCT_HPP +#endif // VOXGIG_STRUCT_HPP diff --git a/cpp/tests/runner.hpp b/cpp/tests/runner.hpp index 90d89788..7215e501 100644 --- a/cpp/tests/runner.hpp +++ b/cpp/tests/runner.hpp @@ -20,9 +20,18 @@ namespace voxgig { namespace structlib { namespace runner { -inline const std::string& NULLMARK() { static const std::string s = "__NULL__"; return s; } -inline const std::string& UNDEFMARK() { static const std::string s = "__UNDEF__"; return s; } -inline const std::string& EXISTSMARK() { static const std::string s = "__EXISTS__"; return s; } +inline const std::string& NULLMARK() { + static const std::string s = "__NULL__"; + return s; +} +inline const std::string& UNDEFMARK() { + static const std::string s = "__UNDEF__"; + return s; +} +inline const std::string& EXISTSMARK() { + static const std::string s = "__EXISTS__"; + return s; +} using Subject = std::function; @@ -50,22 +59,27 @@ inline Value get_spec(const std::string& category, const std::string& name) { // Normalise: integer-valued doubles -> int64; map keys sorted; sentinels and // undefined collapse to null for stable comparison. inline Value normalize(const Value& v) { - if (v.is_undef() || v.is_null()) return Value(nullptr); + if (v.is_undef() || v.is_null()) + return Value(nullptr); if (v.is_double()) { double d = v.as_double(); - if (std::isfinite(d) && std::floor(d) == d) return Value(static_cast(d)); + if (std::isfinite(d) && std::floor(d) == d) + return Value(static_cast(d)); return v; } if (v.is_list()) { auto out = std::make_shared(); - for (const auto& e : *v.as_list()) out->push_back(normalize(e)); + for (const auto& e : *v.as_list()) + out->push_back(normalize(e)); return Value(out); } if (v.is_map()) { std::map sorted; - for (const auto& [k, e] : *v.as_map()) sorted[k] = normalize(e); + for (const auto& [k, e] : *v.as_map()) + sorted[k] = normalize(e); auto out = std::shared_ptr(new Map()); - for (const auto& [k, e] : sorted) out->set(k, e); + for (const auto& [k, e] : sorted) + out->set(k, e); return Value(out); } return v; @@ -77,29 +91,36 @@ inline bool deep_equal(const Value& a, const Value& b) { inline std::string brief(const Value& v) { std::string s; - if (v.is_undef()) return UNDEFMARK(); - try { s = to_njson(v).dump(); } - catch (...) { s = stringify(v); } - if (s.size() > 200) s = s.substr(0, 197) + "..."; + if (v.is_undef()) + return UNDEFMARK(); + try { + s = to_njson(v).dump(); + } catch (...) { + s = stringify(v); + } + if (s.size() > 200) + s = s.substr(0, 197) + "..."; return s; } -inline Result runsetflags(const std::string& full_name, const Value& testspec, - bool null_flag, const Subject& subject) { +inline Result runsetflags(const std::string& full_name, const Value& testspec, bool null_flag, + const Subject& subject) { Result res; res.name = full_name; Value set_v = getprop(testspec, Value("set")); - if (!set_v.is_list()) return res; + if (!set_v.is_list()) + return res; auto set = set_v.as_list(); for (size_t i = 0; i < set->size(); i++) { const Value& eo = (*set)[i]; - if (!eo.is_map()) continue; + if (!eo.is_map()) + continue; bool has_in = haskey(eo, Value("in")); Value in_raw = getprop(eo, Value("in")); Value in = has_in ? clone(in_raw) : Value::undef(); bool has_out = haskey(eo, Value("out")); - Value expected = has_out ? getprop(eo, Value("out")) - : (null_flag ? Value(nullptr) : Value::undef()); + Value expected = + has_out ? getprop(eo, Value("out")) : (null_flag ? Value(nullptr) : Value::undef()); Value err_v = haskey(eo, Value("err")) ? getprop(eo, Value("err")) : Value::undef(); res.total++; @@ -117,30 +138,37 @@ inline Result runsetflags(const std::string& full_name, const Value& testspec, // err entry: either accept any thrown error, or substring match. if (threw) { bool match = false; - if (err_v.is_bool() && err_v.as_bool()) match = true; + if (err_v.is_bool() && err_v.as_bool()) + match = true; else if (err_v.is_string()) { std::string es = err_v.as_string(); - if (es.empty()) match = true; - else if (thrown_msg.find(es) != std::string::npos) match = true; + if (es.empty()) + match = true; + else if (thrown_msg.find(es) != std::string::npos) + match = true; else { std::string lo_msg = thrown_msg; std::string lo_es = es; - for (auto& c : lo_msg) c = static_cast(std::tolower(static_cast(c))); - for (auto& c : lo_es) c = static_cast(std::tolower(static_cast(c))); - if (lo_msg.find(lo_es) != std::string::npos) match = true; + for (auto& c : lo_msg) + c = static_cast(std::tolower(static_cast(c))); + for (auto& c : lo_es) + c = static_cast(std::tolower(static_cast(c))); + if (lo_msg.find(lo_es) != std::string::npos) + match = true; } } - if (match) res.passed++; + if (match) + res.passed++; else { std::ostringstream oss; - oss << "[" << i << "] err mismatch: expected '" << brief(err_v) - << "' got '" << thrown_msg << "'"; + oss << "[" << i << "] err mismatch: expected '" << brief(err_v) << "' got '" << thrown_msg + << "'"; res.failures.push_back(oss.str()); } } else { std::ostringstream oss; - oss << "[" << i << "] expected err='" << brief(err_v) - << "' but call returned " << brief(got); + oss << "[" << i << "] expected err='" << brief(err_v) << "' but call returned " + << brief(got); res.failures.push_back(oss.str()); } continue; @@ -157,8 +185,7 @@ inline Result runsetflags(const std::string& full_name, const Value& testspec, res.passed++; } else { std::ostringstream oss; - oss << "[" << i << "] in=" << brief(in_raw) - << " expected=" << brief(expected) + oss << "[" << i << "] in=" << brief(in_raw) << " expected=" << brief(expected) << " got=" << brief(got); res.failures.push_back(oss.str()); } @@ -166,13 +193,12 @@ inline Result runsetflags(const std::string& full_name, const Value& testspec, return res; } -inline Result runset(const std::string& full_name, const Value& testspec, - const Subject& subject) { +inline Result runset(const std::string& full_name, const Value& testspec, const Subject& subject) { return runsetflags(full_name, testspec, true, subject); } -} // namespace runner -} // namespace structlib -} // namespace voxgig +} // namespace runner +} // namespace structlib +} // namespace voxgig -#endif // VOXGIG_STRUCT_RUNNER_HPP +#endif // VOXGIG_STRUCT_RUNNER_HPP diff --git a/cpp/tests/smoke.cpp b/cpp/tests/smoke.cpp index 7b32f395..240ae309 100644 --- a/cpp/tests/smoke.cpp +++ b/cpp/tests/smoke.cpp @@ -9,7 +9,13 @@ using namespace voxgig::structlib; -#define CHECK(expr) do { if (!(expr)) { std::cerr << "FAIL: " << #expr << " at " << __LINE__ << "\n"; ok = false; } } while (0) +#define CHECK(expr) \ + do { \ + if (!(expr)) { \ + std::cerr << "FAIL: " << #expr << " at " << __LINE__ << "\n"; \ + ok = false; \ + } \ + } while (0) int main() { bool ok = true; @@ -94,8 +100,8 @@ int main() { // walk identity. Value tree = jm({"a", jm({"b", "B"})}); - Value walked = walk_v(tree, - [](const Value&, const Value& v, const Value&, const std::vector&) { return v; }); + Value walked = walk_v(tree, [](const Value&, const Value& v, const Value&, + const std::vector&) { return v; }); CHECK(walked == tree); // merge. diff --git a/cpp/tests/struct_corpus_test.cpp b/cpp/tests/struct_corpus_test.cpp index 6571ae94..a08bee32 100644 --- a/cpp/tests/struct_corpus_test.cpp +++ b/cpp/tests/struct_corpus_test.cpp @@ -17,11 +17,11 @@ #include "runner.hpp" using namespace voxgig::structlib; +using runner::get_spec; using runner::Result; -using runner::Subject; using runner::runset; using runner::runsetflags; -using runner::get_spec; +using runner::Subject; namespace { @@ -29,20 +29,15 @@ std::map SCOREBOARD; const std::map& category_to_file() { static const std::map M = { - {"minor", "minor.jsonic"}, - {"walk", "walk.jsonic"}, - {"merge", "merge.jsonic"}, - {"getpath", "getpath.jsonic"}, - {"inject", "inject.jsonic"}, - {"transform", "transform.jsonic"}, - {"validate", "validate.jsonic"}, - {"select", "select.jsonic"}, + {"minor", "minor.jsonic"}, {"walk", "walk.jsonic"}, + {"merge", "merge.jsonic"}, {"getpath", "getpath.jsonic"}, + {"inject", "inject.jsonic"}, {"transform", "transform.jsonic"}, + {"validate", "validate.jsonic"}, {"select", "select.jsonic"}, }; return M; } -void run(const std::string& cat, const std::string& name, bool null_flag, - const Subject& s) { +void run(const std::string& cat, const std::string& name, bool null_flag, const Subject& s) { std::string full = cat + "." + name; Value spec = get_spec(cat, name); Result r = runsetflags(full, spec, null_flag, s); @@ -56,47 +51,47 @@ inline Value getpDef(const Value& in, const std::string& k, const Value& def) { return haskey(in, Value(k)) ? getprop(in, Value(k)) : def; } -} // namespace +} // namespace int main() { // ===== minor ===== - run("minor", "isnode", true, [](const Value& in) { return Value(isnode(in)); }); - run("minor", "ismap", true, [](const Value& in) { return Value(ismap(in)); }); - run("minor", "islist", true, [](const Value& in) { return Value(islist(in)); }); - run("minor", "iskey", false, [](const Value& in) { return Value(iskey(in)); }); - run("minor", "strkey", false, [](const Value& in) { return Value(strkey(in)); }); + run("minor", "isnode", true, [](const Value& in) { return Value(isnode(in)); }); + run("minor", "ismap", true, [](const Value& in) { return Value(ismap(in)); }); + run("minor", "islist", true, [](const Value& in) { return Value(islist(in)); }); + run("minor", "iskey", false, [](const Value& in) { return Value(iskey(in)); }); + run("minor", "strkey", false, [](const Value& in) { return Value(strkey(in)); }); run("minor", "isempty", false, [](const Value& in) { return Value(isempty(in)); }); - run("minor", "isfunc", true, [](const Value& in) { return Value(isfunc(in)); }); - run("minor", "getprop", true, [](const Value& in) { + run("minor", "isfunc", true, [](const Value& in) { return Value(isfunc(in)); }); + run("minor", "getprop", true, [](const Value& in) { Value alt = getpDef(in, "alt", Value::undef()); - return alt.is_undef() - ? getprop(getp(in, "val"), getp(in, "key")) - : getprop(getp(in, "val"), getp(in, "key"), alt); + return alt.is_undef() ? getprop(getp(in, "val"), getp(in, "key")) + : getprop(getp(in, "val"), getp(in, "key"), alt); }); - run("minor", "getelem", true, [](const Value& in) { + run("minor", "getelem", true, [](const Value& in) { Value alt = getpDef(in, "alt", Value::undef()); - return alt.is_undef() - ? getelem(getp(in, "val"), getp(in, "key")) - : getelem(getp(in, "val"), getp(in, "key"), alt); + return alt.is_undef() ? getelem(getp(in, "val"), getp(in, "key")) + : getelem(getp(in, "val"), getp(in, "key"), alt); }); - run("minor", "clone", false, [](const Value& in) { return clone(in); }); - run("minor", "items", true, [](const Value& in) { return items_v(in); }); - run("minor", "keysof", true, [](const Value& in) { + run("minor", "clone", false, [](const Value& in) { return clone(in); }); + run("minor", "items", true, [](const Value& in) { return items_v(in); }); + run("minor", "keysof", true, [](const Value& in) { auto out = std::make_shared(); - for (const auto& k : keysof(in)) out->push_back(Value(k)); + for (const auto& k : keysof(in)) + out->push_back(Value(k)); return Value(out); }); - run("minor", "haskey", true, [](const Value& in) { - return Value(haskey(getp(in, "src"), getp(in, "key"))); - }); + run("minor", "haskey", true, + [](const Value& in) { return Value(haskey(getp(in, "src"), getp(in, "key"))); }); run("minor", "setprop", true, [](const Value& in) { Value parent = getpDef(in, "parent", Value::undef()); - if (parent.is_undef()) parent = Value(nullptr); + if (parent.is_undef()) + parent = Value(nullptr); return setprop(parent, getp(in, "key"), getp(in, "val")); }); run("minor", "delprop", true, [](const Value& in) { Value parent = getpDef(in, "parent", Value::undef()); - if (parent.is_undef()) parent = Value(nullptr); + if (parent.is_undef()) + parent = Value(nullptr); return delprop(parent, getp(in, "key")); }); run("minor", "stringify", true, [](const Value& in) { @@ -105,22 +100,22 @@ int main() { int m = max.is_int() ? static_cast(max.as_int()) : -1; return Value(stringify(val, m)); }); - run("minor", "jsonify", true, [](const Value& in) { + run("minor", "jsonify", true, [](const Value& in) { Value val = getp(in, "val"); Value flags = getp(in, "flags"); return Value(jsonify(val, flags)); }); - run("minor", "pathify", true, [](const Value& in) { + run("minor", "pathify", true, [](const Value& in) { Value path = getpDef(in, "path", Value::undef()); Value from = getp(in, "from"); Value to = getp(in, "to"); int f = from.is_int() ? static_cast(from.as_int()) : 0; - int t = to.is_int() ? static_cast(to.as_int()) : 0; + int t = to.is_int() ? static_cast(to.as_int()) : 0; return Value(pathify(path, f, t)); }); - run("minor", "escre", true, [](const Value& in) { return Value(escre(in)); }); - run("minor", "escurl", true, [](const Value& in) { return Value(escurl(in)); }); - run("minor", "join", true, [](const Value& in) { + run("minor", "escre", true, [](const Value& in) { return Value(escre(in)); }); + run("minor", "escurl", true, [](const Value& in) { return Value(escurl(in)); }); + run("minor", "join", true, [](const Value& in) { Value val = getp(in, "val"); Value sep = getp(in, "sep"); Value url = getp(in, "url"); @@ -132,7 +127,7 @@ int main() { int d = depth.is_int() ? static_cast(depth.as_int()) : 1; return flatten(val, d); }); - run("minor", "filter", true, [](const Value& in) { + run("minor", "filter", true, [](const Value& in) { Value val = getp(in, "val"); std::string check = getp(in, "check").is_string() ? getp(in, "check").as_string() : ""; ItemCheck pred; @@ -150,28 +145,27 @@ int main() { return filter(val, pred); }); run("minor", "typename", true, [](const Value& in) { - if (in.is_int()) return Value(typename_str(static_cast(in.as_int()))); + if (in.is_int()) + return Value(typename_str(static_cast(in.as_int()))); return Value(typename_str(in)); }); - run("minor", "typify", true, [](const Value& in) { - return Value(static_cast(typify(in))); - }); - run("minor", "size", true, [](const Value& in) -> Value { - return Value(voxgig::structlib::size(in)); - }); - run("minor", "slice", true, [](const Value& in) { + run("minor", "typify", true, + [](const Value& in) { return Value(static_cast(typify(in))); }); + run("minor", "size", true, + [](const Value& in) -> Value { return Value(voxgig::structlib::size(in)); }); + run("minor", "slice", true, [](const Value& in) { Value val = getp(in, "val"); Value start = getp(in, "start"); Value end = getp(in, "end"); return slice(val, start, end); }); - run("minor", "pad", true, [](const Value& in) { + run("minor", "pad", true, [](const Value& in) { Value val = getp(in, "val"); Value p = getp(in, "pad"); Value c = getp(in, "char"); return Value(pad(val, p, c)); }); - run("minor", "setpath", false, [](const Value& in) { + run("minor", "setpath", false, [](const Value& in) { Value store = getp(in, "store"); Value path = getp(in, "path"); Value val = getp(in, "val"); @@ -181,18 +175,19 @@ int main() { // ===== walk ===== run("walk", "basic", true, [](const Value& in) { return walk_v(in, - [](const Value& key, const Value& val, const Value&, - const std::vector& path) -> Value { - if (val.is_string()) { - std::string out = val.as_string() + "~"; - for (size_t i = 0; i < path.size(); i++) { - if (i > 0) out += "."; - out += path[i]; - } - return Value(out); - } - return val; - }); + [](const Value& key, const Value& val, const Value&, + const std::vector& path) -> Value { + if (val.is_string()) { + std::string out = val.as_string() + "~"; + for (size_t i = 0; i < path.size(); i++) { + if (i > 0) + out += "."; + out += path[i]; + } + return Value(out); + } + return val; + }); }); run("walk", "depth", false, [](const Value& in) { Value src = getp(in, "src"); @@ -201,12 +196,13 @@ int main() { Value top = Value::undef(); Value cur = Value::undef(); auto do_walk = [&](const Value& key, const Value& val, const Value&, - const std::vector& path) -> Value { + const std::vector& path) -> Value { if (key.is_undef() || isnode(val)) { Value child = val.is_list() ? Value(std::make_shared()) - : Value(std::shared_ptr(new Map())); + : Value(std::shared_ptr(new Map())); if (key.is_undef()) { - top = child; cur = child; + top = child; + cur = child; } else { setprop(cur, key, child); cur = child; @@ -224,16 +220,15 @@ int main() { auto do_walk = [&](const Value& key, const Value& val, const Value&, const std::vector& path) -> Value { if (key.is_undef()) { - cur[0] = val.is_map() ? Value(std::shared_ptr(new Map())) - : val.is_list() ? Value(std::make_shared()) - : val; + cur[0] = val.is_map() ? Value(std::shared_ptr(new Map())) + : val.is_list() ? Value(std::make_shared()) + : val; return val; } Value v = val; size_t i = path.size(); if (isnode(v)) { - v = v.is_map() ? Value(std::shared_ptr(new Map())) - : Value(std::make_shared()); + v = v.is_map() ? Value(std::shared_ptr(new Map())) : Value(std::make_shared()); cur[i] = v; } setprop(cur[i - 1], key, v); @@ -244,10 +239,10 @@ int main() { }); // ===== merge ===== - run("merge", "cases", true, [](const Value& in) { return merge_v(in); }); - run("merge", "array", true, [](const Value& in) { return merge_v(in); }); + run("merge", "cases", true, [](const Value& in) { return merge_v(in); }); + run("merge", "array", true, [](const Value& in) { return merge_v(in); }); run("merge", "integrity", true, [](const Value& in) { return merge_v(in); }); - run("merge", "depth", true, [](const Value& in) { + run("merge", "depth", true, [](const Value& in) { Value val = getp(in, "val"); Value depth = getp(in, "depth"); int d = depth.is_int() ? static_cast(depth.as_int()) : MAXDEPTH; @@ -255,24 +250,28 @@ int main() { }); // ===== getpath ===== - run("getpath", "basic", true, [](const Value& in) { - return getpath_v(getp(in, "store"), getp(in, "path")); - }); + run("getpath", "basic", true, + [](const Value& in) { return getpath_v(getp(in, "store"), getp(in, "path")); }); run("getpath", "relative", true, [](const Value& in) { Injection inj(Value::undef(), Value::undef()); - if (haskey(in, Value("dparent"))) inj.dparent = getp(in, "dparent"); + if (haskey(in, Value("dparent"))) + inj.dparent = getp(in, "dparent"); if (haskey(in, Value("dpath"))) { Value dp = getp(in, "dpath"); inj.dpath.clear(); if (dp.is_list()) { - for (const auto& p : *dp.as_list()) inj.dpath.push_back(strkey(p)); + for (const auto& p : *dp.as_list()) + inj.dpath.push_back(strkey(p)); } else if (dp.is_string()) { // Split on '.'. const std::string& s = dp.as_string(); size_t pos = 0; while (pos <= s.size()) { size_t dot = s.find('.', pos); - if (dot == std::string::npos) { inj.dpath.push_back(s.substr(pos)); break; } + if (dot == std::string::npos) { + inj.dpath.push_back(s.substr(pos)); + break; + } inj.dpath.push_back(s.substr(pos, dot - pos)); pos = dot + 1; } @@ -280,54 +279,55 @@ int main() { } if (haskey(in, Value("base"))) { Value b = getp(in, "base"); - if (b.is_string()) inj.base = b.as_string(); + if (b.is_string()) + inj.base = b.as_string(); } - bool any = haskey(in, Value("dparent")) || haskey(in, Value("dpath")) || haskey(in, Value("base")); + bool any = + haskey(in, Value("dparent")) || haskey(in, Value("dpath")) || haskey(in, Value("base")); return getpath_v(getp(in, "store"), getp(in, "path"), any ? &inj : nullptr); }); run("getpath", "special", true, [](const Value& in) { Value injv = getp(in, "inj"); - if (!injv.is_map()) return getpath_v(getp(in, "store"), getp(in, "path")); + if (!injv.is_map()) + return getpath_v(getp(in, "store"), getp(in, "path")); Injection inj(Value::undef(), Value::undef()); Value k = getp(injv, "key"); - if (k.is_string()) inj.key = k.as_string(); + if (k.is_string()) + inj.key = k.as_string(); Value m = getp(injv, "meta"); - if (m.is_map()) inj.meta = m.as_map(); + if (m.is_map()) + inj.meta = m.as_map(); Value dp = getp(injv, "dparent"); - if (!dp.is_undef()) inj.dparent = dp; + if (!dp.is_undef()) + inj.dparent = dp; Value dpa = getp(injv, "dpath"); if (dpa.is_list()) { inj.dpath.clear(); - for (const auto& p : *dpa.as_list()) inj.dpath.push_back(strkey(p)); + for (const auto& p : *dpa.as_list()) + inj.dpath.push_back(strkey(p)); } return getpath_v(getp(in, "store"), getp(in, "path"), &inj); }); // ===== inject ===== - run("inject", "string", true, [](const Value& in) { - return inject(getp(in, "val"), getp(in, "store")); - }); - run("inject", "deep", true, [](const Value& in) { - return inject(getp(in, "val"), getp(in, "store")); - }); + run("inject", "string", true, + [](const Value& in) { return inject(getp(in, "val"), getp(in, "store")); }); + run("inject", "deep", true, + [](const Value& in) { return inject(getp(in, "val"), getp(in, "store")); }); // ===== transform ===== - run("transform", "paths", true, [](const Value& in) { - return transform(getp(in, "data"), getp(in, "spec")); - }); - run("transform", "cmds", true, [](const Value& in) { - return transform(getp(in, "data"), getp(in, "spec")); - }); - run("transform", "each", true, [](const Value& in) { - return transform(getp(in, "data"), getp(in, "spec")); - }); - run("transform", "pack", true, [](const Value& in) { - return transform(getp(in, "data"), getp(in, "spec")); - }); + run("transform", "paths", true, + [](const Value& in) { return transform(getp(in, "data"), getp(in, "spec")); }); + run("transform", "cmds", true, + [](const Value& in) { return transform(getp(in, "data"), getp(in, "spec")); }); + run("transform", "each", true, + [](const Value& in) { return transform(getp(in, "data"), getp(in, "spec")); }); + run("transform", "pack", true, + [](const Value& in) { return transform(getp(in, "data"), getp(in, "spec")); }); run("transform", "modify", true, [](const Value& in) { auto opts = std::shared_ptr(new Map()); - Modify mod = [](const Value& val, const Value& key, const Value& parent, - Injection& inj, const Value& store) { + Modify mod = [](const Value& val, const Value& key, const Value& parent, Injection& inj, + const Value& store) { if (!key.is_undef() && parent.is_map() && val.is_string()) { parent.as_map()->set(strkey(key), Value("@" + val.as_string())); } @@ -335,19 +335,19 @@ int main() { opts->set("modify", Value(mod)); return transform(getp(in, "data"), getp(in, "spec"), Value(opts)); }); - run("transform", "ref", true, [](const Value& in) { - return transform(getp(in, "data"), getp(in, "spec")); - }); - run("transform", "format", false, [](const Value& in) { - return transform(getp(in, "data"), getp(in, "spec")); - }); + run("transform", "ref", true, + [](const Value& in) { return transform(getp(in, "data"), getp(in, "spec")); }); + run("transform", "format", false, + [](const Value& in) { return transform(getp(in, "data"), getp(in, "spec")); }); run("transform", "apply", true, [](const Value& in) { auto opts = std::shared_ptr(new Map()); auto extra = std::shared_ptr(new Map()); - Injector apply_fn = [](Injection&, const Value& val, const std::string&, const Value&) -> Value { + Injector apply_fn = [](Injection&, const Value& val, const std::string&, + const Value&) -> Value { if (val.is_string()) { std::string s = val.as_string(); - for (auto& c : s) c = static_cast(std::toupper(static_cast(c))); + for (auto& c : s) + c = static_cast(std::toupper(static_cast(c))); return Value(s); } return val; @@ -358,11 +358,16 @@ int main() { }); // ===== validate ===== - run("validate", "basic", true, [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); - run("validate", "child", true, [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); - run("validate", "one", true, [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); - run("validate", "exact", true, [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); - run("validate", "invalid", true, [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); + run("validate", "basic", true, + [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); + run("validate", "child", true, + [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); + run("validate", "one", true, + [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); + run("validate", "exact", true, + [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); + run("validate", "invalid", true, + [](const Value& in) { return validate(getp(in, "data"), getp(in, "spec")); }); run("validate", "special", true, [](const Value& in) { Value inj_v = getp(in, "inj"); return inj_v.is_map() ? validate(getp(in, "data"), getp(in, "spec"), inj_v) @@ -370,24 +375,28 @@ int main() { }); // ===== select ===== - run("select", "basic", true, [](const Value& in) { + run("select", "basic", true, [](const Value& in) { auto out = std::make_shared(); - for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) out->push_back(v); + for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) + out->push_back(v); return Value(out); }); run("select", "operators", true, [](const Value& in) { auto out = std::make_shared(); - for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) out->push_back(v); + for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) + out->push_back(v); return Value(out); }); - run("select", "edge", true, [](const Value& in) { + run("select", "edge", true, [](const Value& in) { auto out = std::make_shared(); - for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) out->push_back(v); + for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) + out->push_back(v); return Value(out); }); - run("select", "alts", true, [](const Value& in) { + run("select", "alts", true, [](const Value& in) { auto out = std::make_shared(); - for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) out->push_back(v); + for (const auto& v : select(getp(in, "obj"), getp(in, "query"))) + out->push_back(v); return Value(out); }); @@ -399,7 +408,7 @@ int main() { std::string cat = key.substr(0, key.find('.')); auto it = category_to_file().find(cat); std::string file = it == category_to_file().end() ? cat + ".jsonic" : it->second; - by_file[file].first += r.passed; + by_file[file].first += r.passed; by_file[file].second += r.total; details[file].push_back({key, std::to_string(r.passed) + "/" + std::to_string(r.total)}); totalP += r.passed; @@ -419,12 +428,16 @@ int main() { // Optionally print failure details when CORPUS_VERBOSE is set. if (const char* v = std::getenv("CORPUS_VERBOSE"); v && std::string(v) != "0") { for (const auto& [name, r] : SCOREBOARD) { - if (r.failures.empty()) continue; + if (r.failures.empty()) + continue; std::cerr << "\n--- " << name << " (" << r.passed << "/" << r.total << ") ---\n"; int shown = 0; for (const auto& f : r.failures) { std::cerr << " " << f << "\n"; - if (++shown >= 5) { std::cerr << " ... " << (r.failures.size() - shown) << " more\n"; break; } + if (++shown >= 5) { + std::cerr << " ... " << (r.failures.size() - shown) << " more\n"; + break; + } } } } @@ -435,13 +448,13 @@ int main() { out << "{\n \"files\": {\n"; bool first = true; for (const auto& [file, pt] : by_file) { - if (!first) out << ",\n"; + if (!first) + out << ",\n"; first = false; - out << " \"" << file << "\": {\"passed\": " << pt.first - << ", \"total\": " << pt.second << "}"; + out << " \"" << file << "\": {\"passed\": " << pt.first << ", \"total\": " << pt.second + << "}"; } - out << "\n },\n \"total\": {\"passed\": " << totalP - << ", \"total\": " << totalT << "}\n}\n"; + out << "\n },\n \"total\": {\"passed\": " << totalP << ", \"total\": " << totalT << "}\n}\n"; } return 0; diff --git a/cs/.editorconfig b/cs/.editorconfig new file mode 100644 index 00000000..70e5ceb2 --- /dev/null +++ b/cs/.editorconfig @@ -0,0 +1,55 @@ +# EditorConfig + Roslyn analyzer configuration for the C# port. +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/configuration-files +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space + +[*.cs] +indent_size = 4 +max_line_length = 120 + +# Treat .NET / C# analyzer diagnostics as warnings by default. +dotnet_analyzer_diagnostic.category-Style.severity = warning +dotnet_analyzer_diagnostic.category-Design.severity = warning +dotnet_analyzer_diagnostic.category-Performance.severity = warning +dotnet_analyzer_diagnostic.category-Reliability.severity = warning +dotnet_analyzer_diagnostic.category-Usage.severity = warning + +# This is a faithful port of the canonical TypeScript source; a few rules +# that fight the cross-language layout are relaxed. +dotnet_diagnostic.IDE0008.severity = none # prefer 'var' style choice +dotnet_diagnostic.IDE0058.severity = none # expression value never used +dotnet_diagnostic.CA1707.severity = none # underscores in identifiers (T_string, M_VAL, ...) +dotnet_diagnostic.CA1822.severity = suggestion + +# IDE1006: the port intentionally uses TS-style names: T_string / M_VAL constants +# and `_`-prefixed private helpers (_InjectHandler, _TransformPack, _ValidateType, ...) +# that mirror the canonical source. Matches the CA1707 relaxation above. +dotnet_diagnostic.IDE1006.severity = none +# CA1708: S_key ("key") and S_KEY ("KEY") are deliberate, distinct cross-language +# string constants that differ only by case. +dotnet_diagnostic.CA1708.severity = none +# CA1720: T.Decimal / T.Integer are type bit-flag members named to match the +# canonical TypeScript type codes; renaming would break cross-language parity. +dotnet_diagnostic.CA1720.severity = none +# IDE0046/IDE0045/IDE0075: "use conditional expression" style preferences. The +# fixer produces deeply-nested ternaries that hurt readability in this ported +# code; keep the explicit if/return form. Purely stylistic, not correctness. +dotnet_diagnostic.IDE0046.severity = none +dotnet_diagnostic.IDE0045.severity = none +dotnet_diagnostic.IDE0075.severity = none +# IDE0060: `current` is retained on the public GetPath(store, path, current, state) +# signature for parity with the other language ports, even though this port does +# not consume it directly. +dotnet_diagnostic.IDE0060.severity = none + +[*.{csproj,props,targets}] +indent_size = 2 + +[*.{json,jsonic}] +indent_size = 2 diff --git a/cs/Makefile b/cs/Makefile new file mode 100644 index 00000000..cb7bbe53 --- /dev/null +++ b/cs/Makefile @@ -0,0 +1,33 @@ +.PHONY: inspect build test lint format-check clean reset audit + +inspect: + @echo ".NET SDK version:" + @dotnet --version + +build: + dotnet build VoxgigStruct.csproj + +test: + dotnet test tests/VoxgigStructTest.csproj + +# Code quality: Roslyn analyzers (run as part of build, warnings-as-info) + +# `dotnet format` style verification. +lint: + dotnet build VoxgigStruct.csproj -warnaserror -nologo + dotnet format VoxgigStruct.csproj --verify-no-changes --severity warn + +format-check: + dotnet format VoxgigStruct.csproj --verify-no-changes --severity warn + +clean: + dotnet clean VoxgigStruct.csproj 2>/dev/null || true + rm -rf bin obj tests/bin tests/obj + +reset: clean + +# Supply-chain: fail on any NuGet dependency with a known vulnerability. +audit: + dotnet restore tests/VoxgigStructTest.csproj >/dev/null + @out=$$(dotnet list tests/VoxgigStructTest.csproj package --vulnerable --include-transitive 2>&1); \ + echo "$$out"; \ + if echo "$$out" | grep -q "has the following vulnerable packages"; then exit 1; fi diff --git a/cs/Struct.cs b/cs/Struct.cs index df6c96f5..42984035 100644 --- a/cs/Struct.cs +++ b/cs/Struct.cs @@ -36,2381 +36,2767 @@ * - Join: join parts of a string, merging sep chars as needed. */ -using System.Text; +using System.Globalization; using System.Text.Json; using System.Text.RegularExpressions; -namespace Voxgig.Struct; - - -// Type bit-flags (matching TypeScript). -public static class T +namespace Voxgig.Struct { - private static int _t = 31; - public static readonly int Any = (1 << _t--) - 1; - public static readonly int NoVal = 1 << _t--; - public static readonly int Boolean = 1 << _t--; - public static readonly int Decimal = 1 << _t--; - public static readonly int Integer = 1 << _t--; - public static readonly int Number = 1 << _t--; - public static readonly int Str = 1 << _t--; - public static readonly int Func = 1 << _t--; - public static readonly int Symbol = 1 << _t--; - public static readonly int Null = 1 << _t--; - - static T() { _t -= 7; } - - private static int _t2 = 31 - 10 - 7; - public static readonly int List = 1 << (_t2--); - public static readonly int Map = 1 << (_t2--); - public static readonly int Instance = 1 << (_t2--); - - private static int _t3 = 31 - 10 - 7 - 3 - 4; - public static readonly int Scalar = 1 << (_t3--); - public static readonly int Node = 1 << (_t3--); -} + // Type bit-flags (matching TypeScript). + public static class T + { + private static int _t = 31; + public static readonly int Any = (1 << _t--) - 1; + public static readonly int NoVal = 1 << _t--; + public static readonly int Boolean = 1 << _t--; + public static readonly int Decimal = 1 << _t--; + public static readonly int Integer = 1 << _t--; + public static readonly int Number = 1 << _t--; + public static readonly int Str = 1 << _t--; + public static readonly int Func = 1 << _t--; + public static readonly int Symbol = 1 << _t--; + public static readonly int Null = 1 << _t--; + + static T() { _t -= 7; } + + private static int _t2 = 31 - 10 - 7; + public static readonly int List = 1 << (_t2--); + public static readonly int Map = 1 << (_t2--); + public static readonly int Instance = 1 << (_t2--); + + private static int _t3 = 31 - 10 - 7 - 3 - 4; + public static readonly int Scalar = 1 << (_t3--); + public static readonly int Node = 1 << (_t3--); + } -/// -/// Callback invoked for each node visited by . -/// -/// -/// The argument is a single mutable list per depth, -/// shared across all callback invocations for the lifetime of the top-level -/// Walk call. Callbacks that need to retain the path MUST clone it -/// (e.g. new List<object?>(path) or path.ToList()); -/// otherwise its contents will be overwritten by subsequent visits. -/// -public delegate object? WalkApply(object? key, object? val, object? parent, List path); -public delegate object? Modify(object? val, object? key, object? parent, object? inj, object? store); -public delegate object? Injector(InjectState inj, object? val, string? refStr, object? store); + /// + /// Callback invoked for each node visited by . + /// + /// + /// The argument is a single mutable list per depth, + /// shared across all callback invocations for the lifetime of the top-level + /// Walk call. Callbacks that need to retain the path MUST clone it + /// (e.g. new List<object?>(path) or path.ToList()); + /// otherwise its contents will be overwritten by subsequent visits. + /// + public delegate object? WalkApply(object? key, object? val, object? parent, List path); + public delegate object? Modify(object? val, object? key, object? parent, object? inj, object? store); + public delegate object? Injector(InjectState inj, object? val, string? refStr, object? store); + + + public class InjectState + { + public int Mode { get; set; } // M_KEYPRE | M_KEYPOST | M_VAL + public bool Full { get; set; } + public int KeyI { get; set; } + public List Keys { get; set; } = []; + public string? Key { get; set; } + public object? Val { get; set; } + public object? Parent { get; set; } + public List Path { get; set; } = []; + public List Nodes { get; set; } = []; + public Injector? Handler { get; set; } + public List? Errs { get; set; } + public string? Base { get; set; } + public Modify? ModifyFn { get; set; } + public object? DParent { get; set; } + public List DPath { get; set; } = []; + public InjectState? Prior { get; set; } + public Dictionary Meta { get; set; } = []; + public object? Extra { get; set; } + public object? Root { get; set; } + + public object? Descend() + { + if (!Meta.TryGetValue("__d", out object? value)) + { + value = 0; + Meta["__d"] = value; + } -public class InjectState -{ - public int Mode { get; set; } // M_KEYPRE | M_KEYPOST | M_VAL - public bool Full { get; set; } - public int KeyI { get; set; } - public List Keys { get; set; } = []; - public string? Key { get; set; } - public object? Val { get; set; } - public object? Parent { get; set; } - public List Path { get; set; } = []; - public List Nodes { get; set; } = []; - public Injector? Handler { get; set; } - public List? Errs { get; set; } = null; - public string? Base { get; set; } - public Modify? ModifyFn { get; set; } - public object? DParent { get; set; } - public List DPath { get; set; } = []; - public InjectState? Prior { get; set; } - public Dictionary Meta { get; set; } = []; - public object? Extra { get; set; } - public object? Root { get; set; } - - public object? Descend() - { - if (!Meta.ContainsKey("__d")) Meta["__d"] = 0; - Meta["__d"] = Convert.ToInt64(Meta["__d"]) + 1L; + Meta["__d"] = Convert.ToInt64(value, CultureInfo.InvariantCulture) + 1L; - object? parentkey = StructUtils.GetElem(Path, -2); + object? parentkey = StructUtils.GetElem(Path, -2); - if (DParent == null) - { - if (StructUtils.Size(DPath) > 1) - DPath = [..DPath, parentkey]; + if (DParent == null) + { + if (StructUtils.Size(DPath) > 1) + { + DPath = [.. DPath, parentkey]; + } + } + else + { + if (parentkey != null) + { + DParent = StructUtils.GetProp(DParent, parentkey); + string pkStr = StructUtils.StrKey(parentkey); + object? lastpart = StructUtils.GetElem(DPath, -1); + string? lastStr = lastpart as string; + DPath = lastStr == "$:" + pkStr ? ([.. DPath.Take(DPath.Count - 1)]) : ([.. DPath, parentkey]); + } + } + + return DParent; + } + + public InjectState Child(int keyI, List keys) + { + string keyStr = StructUtils.StrKey(keys[keyI]); + // Parent / nodes must be the container (this.Val), not the child — same as TS + // cinj.parent = val; cinj.val = getprop(val, key). Object initializer Val is assigned first, + // so Parent = Val would wrongly use the child if written as a single block. + object? parentNode = Val; + var cinj = new InjectState + { + Mode = Mode, + Full = false, + KeyI = keyI, + Keys = keys, + Key = keyStr, + Val = StructUtils.GetProp(parentNode, keyStr), + Parent = parentNode, + Path = [.. Path, keyStr], + Nodes = [.. Nodes, parentNode], + Handler = Handler, + Errs = Errs, + Meta = Meta, + Base = Base, + ModifyFn = ModifyFn, + Prior = this, + DPath = [.. DPath], + DParent = DParent, + Extra = Extra, + Root = Root + }; + return cinj; } - else + + public object? SetVal(object? val, int ancestor = 0) { - if (parentkey != null) + // Match TS: setval(NONE) deletes the key; null also deletes. + bool del = val == null || ReferenceEquals(val, StructUtils.NONE); + if (ancestor < 2) { - DParent = StructUtils.GetProp(DParent, parentkey); - string pkStr = StructUtils.StrKey(parentkey); - object? lastpart = StructUtils.GetElem(DPath, -1); - string? lastStr = lastpart as string; - if (lastStr == "$:" + pkStr) - DPath = [..DPath.Take(DPath.Count - 1)]; + if (del) + { + StructUtils.DelProp(Parent, Key); + } else - DPath = [..DPath, parentkey]; + { + StructUtils.SetProp(Parent, Key, val); + } + + return Parent; + } + object? anode = StructUtils.GetElem(Nodes, -ancestor); + object? akey = StructUtils.GetElem(Path, -ancestor); + if (del) + { + StructUtils.DelProp(anode, akey); + } + else + { + StructUtils.SetProp(anode, akey, val); } - } - return DParent; + return anode; + } } - public InjectState Child(int keyI, List keys) - { - string keyStr = StructUtils.StrKey(keys[keyI]); - // Parent / nodes must be the container (this.Val), not the child — same as TS - // cinj.parent = val; cinj.val = getprop(val, key). Object initializer Val is assigned first, - // so Parent = Val would wrongly use the child if written as a single block. - object? parentNode = Val; - var cinj = new InjectState - { - Mode = Mode, - Full = false, - KeyI = keyI, - Keys = keys, - Key = keyStr, - Val = StructUtils.GetProp(parentNode, keyStr), - Parent = parentNode, - Path = [..Path, keyStr], - Nodes = [..Nodes, parentNode], - Handler = Handler, - Errs = Errs, - Meta = Meta, - Base = Base, - ModifyFn = ModifyFn, - Prior = this, - }; - cinj.DPath = [..DPath]; - cinj.DParent = DParent; - cinj.Extra = Extra; - cinj.Root = Root; - return cinj; - } - public object? SetVal(object? val, int ancestor = 0) - { - // Match TS: setval(NONE) deletes the key; null also deletes. - bool del = val == null || ReferenceEquals(val, StructUtils.NONE); - if (ancestor < 2) + public static partial class StructUtils + { + // Inject mode flags. + public const int M_KEYPRE = 1; + public const int M_KEYPOST = 2; + public const int M_VAL = 4; + + // Special strings. + public const string S_BKEY = "`$KEY`"; + public const string S_BANNO = "`$ANNO`"; + public const string S_BEXACT = "`$EXACT`"; + public const string S_BVAL = "`$VAL`"; + public const string S_DKEY = "$KEY"; + public const string S_DTOP = "$TOP"; + public const string S_DERRS = "$ERRS"; + public const string S_DSPEC = "$SPEC"; + + // General strings. + public const string S_list = "list"; + public const string S_base = "base"; + public const string S_boolean = "boolean"; + public const string S_function = "function"; + public const string S_number = "number"; + public const string S_object = "object"; + public const string S_string = "string"; + public const string S_decimal = "decimal"; + public const string S_integer = "integer"; + public const string S_map = "map"; + public const string S_scalar = "scalar"; + public const string S_node = "node"; + public const string S_instance = "instance"; + public const string S_any = "any"; + public const string S_nil = "nil"; + public const string S_null = "null"; + public const string S_key = "key"; + + // Character strings. + public const string S_BT = "`"; + public const string S_CN = ":"; + public const string S_CS = "]"; + public const string S_DS = "$"; + public const string S_DT = "."; + public const string S_FS = "/"; + public const string S_KEY = "KEY"; + public const string S_MT = ""; + public const string S_OS = "["; + public const string S_SP = " "; + public const string S_CM = ","; + public const string S_VIZ = ": "; + + // Cached JSON serializer options (avoid allocating per call - CA1869). + private static readonly JsonSerializerOptions JSON_OPTS_COMPACT = new() + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = false, + }; + private static readonly JsonSerializerOptions JSON_OPTS_INDENTED = new() { - if (del) StructUtils.DelProp(Parent, Key); - else StructUtils.SetProp(Parent, Key, val); - return Parent; - } - object? anode = StructUtils.GetElem(Nodes, -ancestor); - object? akey = StructUtils.GetElem(Path, -ancestor); - if (del) StructUtils.DelProp(anode, akey); - else StructUtils.SetProp(anode, akey, val); - return anode; - } -} - + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true, + }; -public static class StructUtils -{ - // Inject mode flags. - public const int M_KEYPRE = 1; - public const int M_KEYPOST = 2; - public const int M_VAL = 4; - - // Special strings. - public const string S_BKEY = "`$KEY`"; - public const string S_BANNO = "`$ANNO`"; - public const string S_BEXACT = "`$EXACT`"; - public const string S_BVAL = "`$VAL`"; - public const string S_DKEY = "$KEY"; - public const string S_DTOP = "$TOP"; - public const string S_DERRS = "$ERRS"; - public const string S_DSPEC = "$SPEC"; - - // General strings. - public const string S_list = "list"; - public const string S_base = "base"; - public const string S_boolean = "boolean"; - public const string S_function = "function"; - public const string S_number = "number"; - public const string S_object = "object"; - public const string S_string = "string"; - public const string S_decimal = "decimal"; - public const string S_integer = "integer"; - public const string S_map = "map"; - public const string S_scalar = "scalar"; - public const string S_node = "node"; - public const string S_instance = "instance"; - public const string S_any = "any"; - public const string S_nil = "nil"; - public const string S_null = "null"; - public const string S_key = "key"; - - // Character strings. - public const string S_BT = "`"; - public const string S_CN = ":"; - public const string S_CS = "]"; - public const string S_DS = "$"; - public const string S_DT = "."; - public const string S_FS = "/"; - public const string S_KEY = "KEY"; - public const string S_MT = ""; - public const string S_OS = "["; - public const string S_SP = " "; - public const string S_CM = ","; - public const string S_VIZ = ": "; - - // Private markers. - public static readonly Dictionary SKIP = new() { ["`$SKIP`"] = true }; - public static readonly Dictionary DELETE = new() { ["`$DELETE`"] = true }; - - // Sentinel for "value is not defined / absent" (analogous to TS undefined). - // Use this to distinguish "value is null (JSON null)" from "value is not present". - public static readonly object NONE = new(); - - // Typename lookup (indexed by bit position from MSB of type code). - private static readonly string[] TYPENAME = - [ - S_any, S_nil, S_boolean, S_decimal, S_integer, S_number, - S_string, S_function, S_nil, S_null, - "", "", "", "", "", "", "", - S_list, S_map, S_instance, - "", "", "", "", - S_scalar, S_node - ]; - - // Regular expressions. - private static readonly Regex R_INTEGER_KEY = new(@"^[-0-9]+$"); - private static readonly Regex R_ESCAPE_REGEXP = new(@"[.*+?^${}()|[\]\\]"); - private static readonly Regex R_CLONE_REF = new(@"^`\$REF:([0-9]+)`$"); - private static readonly Regex R_INJECTION_FULL = new(@"^`(\$[A-Z]+|[^`]*)[0-9]*`$"); - private static readonly Regex R_INJECTION_PARTIAL = new(@"`([^`]+)`"); - private static readonly Regex R_DOUBLE_DOLLAR = new(@"\$\$"); - private static readonly Regex R_META_PATH = new(@"^([^$]+)\$([=~])(.+)$"); - private static readonly Regex R_TRANSFORM_NAME = new(@"`\$([A-Z]+)`"); - private static readonly Regex R_DOT = new(@"\."); - - public const int MAXDEPTH = 32; - - - // ======================================================================== - // Minor utilities - // ======================================================================== - - // Value is a node - a map (Dictionary) or list (List). - public static bool IsNode(object? val) => - val is Dictionary || val is List; - - // Value is a map (Dictionary with string keys). - public static bool IsMap(object? val) => - val is Dictionary; - - // Value is a list (List). - public static bool IsList(object? val) => - val is List; - - // Value is a valid key: non-empty string or any number (matches TS where numbers are keys). - public static bool IsKey(object? key) - { - if (key is string s) return s.Length > 0; - if (key is int or long or double or float) return true; - return false; - } + // Private markers. + public static readonly Dictionary SKIP = new() { ["`$SKIP`"] = true }; + public static readonly Dictionary DELETE = new() { ["`$DELETE`"] = true }; - // Value is "empty": null, empty string, empty list, or empty map. - public static bool IsEmpty(object? val) - { - // Match TS isempty: undefined/null/empty-string/empty-array/empty-object → true. - if (val == null || ReferenceEquals(val, NONE)) return true; - if (val is string s) return s.Length == 0; - if (val is List l) return l.Count == 0; - if (val is Dictionary d) return d.Count == 0; - return false; - } + // Sentinel for "value is not defined / absent" (analogous to TS undefined). + // Use this to distinguish "value is null (JSON null)" from "value is not present". + public static readonly object NONE = new(); - // Value is a delegate/function. - public static bool IsFunc(object? val) => - val is Delegate; + // Typename lookup (indexed by bit position from MSB of type code). + private static readonly string[] TYPENAME = + [ + S_any, S_nil, S_boolean, S_decimal, S_integer, S_number, + S_string, S_function, S_nil, S_null, + "", "", "", "", "", "", "", + S_list, S_map, S_instance, + "", "", "", "", + S_scalar, S_node + ]; - // Get a defined value; return alt if val is null. - public static object? GetDef(object? val, object? alt) => - val ?? alt; + // Regular expressions (compile-time generated; see partial methods at end of class). + private static readonly Regex R_INTEGER_KEY = R_IntegerKeyGen(); + private static readonly Regex R_ESCAPE_REGEXP = R_EscapeRegexpGen(); + private static readonly Regex R_CLONE_REF = R_CloneRefGen(); + private static readonly Regex R_INJECTION_FULL = R_InjectionFullGen(); + private static readonly Regex R_INJECTION_PARTIAL = R_InjectionPartialGen(); + private static readonly Regex R_DOUBLE_DOLLAR = R_DoubleDollarGen(); + private static readonly Regex R_META_PATH = R_MetaPathGen(); + private static readonly Regex R_TRANSFORM_NAME = R_TransformNameGen(); + private static readonly Regex R_DOT = R_DotGen(); - // Return the typename for the narrowest type bit-flag. - public static string TypeName(int t) - { - int pos = System.Numerics.BitOperations.LeadingZeroCount((uint)t); - return pos < TYPENAME.Length ? TYPENAME[pos] : S_any; - } + public const int MAXDEPTH = 32; - // Determine the type of a value as a bit code. - public static int Typify(object? value) - { - // Match TS: undefined/NONE → noval; JSON null is its own type (T_null), not noval. - if (ReferenceEquals(value, NONE)) return T.NoVal; - if (value == null) return T.Scalar | T.Null; - - return value switch - { - bool => T.Scalar | T.Boolean, - int => T.Scalar | T.Number | T.Integer, - long => T.Scalar | T.Number | T.Integer, - double d => double.IsNaN(d) ? T.NoVal : double.IsInteger(d) - ? T.Scalar | T.Number | T.Integer - : T.Scalar | T.Number | T.Decimal, - float f => float.IsNaN(f) ? T.NoVal : (f % 1 == 0) - ? T.Scalar | T.Number | T.Integer - : T.Scalar | T.Number | T.Decimal, - string => T.Scalar | T.Str, - Delegate => T.Scalar | T.Func, - List => T.Node | T.List, - Dictionary => T.Node | T.Map, - _ => T.Any, - }; - } - // The integer size of a value. - public static int Size(object? val) - { - return val switch - { - List ls => ls.Count, - List l => l.Count, - Dictionary d => d.Count, - string s => s.Length, - int i => i, - long lg => (int)lg, - double d => (int)Math.Floor(d), - float f => (int)Math.Floor(f), - bool b => b ? 1 : 0, - _ => 0, - }; - } + // ======================================================================== + // Minor utilities + // ======================================================================== - // Extract a sub-range from a list or string (negative indices count from end). - // For numbers: clamp between start (inclusive) and end (exclusive). - public static object? Slice(object? val, int? start = null, int? end = null, bool mutate = false) - { - if (val is int or long or double or float) + // Value is a node - a map (Dictionary) or list (List). + public static bool IsNode(object? val) { - long lv = Convert.ToInt64(val); - long lo = start.HasValue ? start.Value : long.MinValue; - long hi = end.HasValue ? (long)end.Value - 1 : long.MaxValue; - return Math.Min(Math.Max(lv, lo), hi); + return val is Dictionary or List; } - int vlen = Size(val); - - if (end != null && start == null) start = 0; - - if (start == null) return val; - - int s = start.Value; - int e; - - if (s < 0) + // Value is a map (Dictionary with string keys). + public static bool IsMap(object? val) { - e = vlen + s; - if (e < 0) e = 0; - s = 0; + return val is Dictionary; } - else if (end != null) + + // Value is a list (List). + public static bool IsList(object? val) { - e = end.Value; - if (e < 0) - { - e = vlen + e; - if (e < 0) e = 0; - } - else if (vlen < e) - e = vlen; + return val is List; } - else - e = vlen; - if (s > vlen) s = vlen; - - if (s < 0 || s > e || e > vlen) + // Value is a valid key: non-empty string or any number (matches TS where numbers are keys). + public static bool IsKey(object? key) { - if (val is List) return new List(); - if (val is string) return S_MT; - return val; + return key is string s ? s.Length > 0 : key is int or long or double or float; } - if (val is List list) + // Value is "empty": null, empty string, empty list, or empty map. + public static bool IsEmpty(object? val) { - var sub = list.GetRange(s, e - s); - if (mutate) + // Match TS isempty: undefined/null/empty-string/empty-array/empty-object → true. + if (val == null || ReferenceEquals(val, NONE)) { - list.Clear(); - list.AddRange(sub); - return list; + return true; } - return sub; - } - if (val is string str) - return str.Substring(s, e - s); - - return val; - } - - // String padding. - public static string Pad(object? str, int padding = 44, string? padchar = null) - { - string s = str is string ss ? ss : Stringify(str); - string pc = padchar != null ? (padchar + S_SP)[0].ToString() : S_SP; - if (padding >= 0) - return s.PadRight(padding, pc[0]); - return s.PadLeft(-padding, pc[0]); - } - // Get a list element by integer index (negative counts from end). - public static object? GetElem(object? val, object? key, object? alt = null) - { - if (val == null || key == null) return alt; + if (val is string s) + { + return s.Length == 0; + } - if (val is List list) - { - if (!int.TryParse(key?.ToString(), out int nkey)) return alt; - if (!R_INTEGER_KEY.IsMatch(key!.ToString()!)) return alt; - if (nkey < 0) nkey = list.Count + nkey; - if (nkey < 0 || nkey >= list.Count) return alt; - return list[nkey]; + return val is List l ? l.Count == 0 : val is Dictionary d && d.Count == 0; } - return alt is Delegate d2 ? d2.DynamicInvoke() : alt; - } - - // Safely get a property of a node. - public static object? GetProp(object? val, object? key, object? alt = null) - { - if (val == null || key == null) return alt; - if (val is Dictionary map) + // Value is a delegate/function. + public static bool IsFunc(object? val) { - string k = StrKey(key) ?? S_MT; - // Key present with JSON null → return null (TS: missing vs null). - return map.TryGetValue(k, out object? v) ? v : alt; + return val is Delegate; } - if (val is List list) + + // Get a defined value; return alt if val is null. + public static object? GetDef(object? val, object? alt) { - string? ks = key?.ToString(); - if (ks != null && int.TryParse(ks, out int i)) - { - if (i < 0) i = list.Count + i; - if (i >= 0 && i < list.Count) - return list[i]; - } - return alt; + return val ?? alt; } - return alt; - } - - // Convert a key to its string representation. - public static string? StrKey(object? key = null) - { - if (key == null) return S_MT; - int t = Typify(key); - if (0 < (T.Str & t)) return (string)key; - if (0 < (T.Boolean & t)) return S_MT; - if (0 < (T.Number & t)) - { - double d = Convert.ToDouble(key); - return ((long)Math.Floor(d)).ToString(); + // Return the typename for the narrowest type bit-flag. + public static string TypeName(int t) + { + int pos = System.Numerics.BitOperations.LeadingZeroCount((uint)t); + return pos < TYPENAME.Length ? TYPENAME[pos] : S_any; } - return S_MT; - } - // Sorted keys of a map (strings) or list (index strings). - public static List KeysOf(object? val) - { - if (val is Dictionary map) + // Determine the type of a value as a bit code. + public static int Typify(object? value) { - var keys = map.Keys.ToList(); - keys.Sort(StringComparer.Ordinal); - return keys; + // Match TS: undefined/NONE → noval; JSON null is its own type (T_null), not noval. + return ReferenceEquals(value, NONE) + ? T.NoVal + : value == null + ? T.Scalar | T.Null + : value switch + { + bool => T.Scalar | T.Boolean, + int => T.Scalar | T.Number | T.Integer, + long => T.Scalar | T.Number | T.Integer, + double d => double.IsNaN(d) ? T.NoVal : double.IsInteger(d) + ? T.Scalar | T.Number | T.Integer + : T.Scalar | T.Number | T.Decimal, + float f => float.IsNaN(f) ? T.NoVal : (f % 1 == 0) + ? T.Scalar | T.Number | T.Integer + : T.Scalar | T.Number | T.Decimal, + string => T.Scalar | T.Str, + Delegate => T.Scalar | T.Func, + List => T.Node | T.List, + Dictionary => T.Node | T.Map, + _ => T.Any, + }; } - if (val is List list) - return Enumerable.Range(0, list.Count) - .Select(i => i.ToString()) - .ToList(); - return []; - } - // True if the key exists with a non-null value in val. - public static bool HasKey(object? val, object? key) - { - if (val == null || key == null) return false; - if (val is Dictionary map) + // The integer size of a value. + public static int Size(object? val) { - string k = StrKey(key) ?? S_MT; - return map.ContainsKey(k); + return val switch + { + List ls => ls.Count, + List l => l.Count, + Dictionary d => d.Count, + string s => s.Length, + int i => i, + long lg => (int)lg, + double d => (int)Math.Floor(d), + float f => (int)Math.Floor(f), + bool b => b ? 1 : 0, + _ => 0, + }; } - if (val is List list) + + // Extract a sub-range from a list or string (negative indices count from end). + // For numbers: clamp between start (inclusive) and end (exclusive). + public static object? Slice(object? val, int? start = null, int? end = null, bool mutate = false) { - string? ks = key?.ToString(); - if (ks != null && int.TryParse(ks, out int i)) + if (val is int or long or double or float) { - if (i < 0) i = list.Count + i; - return i >= 0 && i < list.Count; + long lv = Convert.ToInt64(val, CultureInfo.InvariantCulture); + long lo = start ?? long.MinValue; + long hi = end.HasValue ? (long)end.Value - 1 : long.MaxValue; + return Math.Min(Math.Max(lv, lo), hi); } - return false; - } - return false; - } - // Items of a map/list as [[key, value], ...] pairs. - public static List> Items(object? val) - { - var keys = KeysOf(val); - return keys.Select(k => (List)[k, GetProp(val, k)]).ToList(); - } + int vlen = Size(val); - // Items with a transform applied to each [key, value] pair. - public static List Items(object? val, Func, T2> apply) - { - return Items(val).Select(apply).ToList(); - } + if (end != null && start == null) + { + start = 0; + } - // Flatten nested lists up to the given depth (default 1). - public static List Flatten(List list, int depth = 1) - { - if (!IsList(list)) return list; - if (depth <= 0) return list; - var result = new List(); - foreach (var item in list) - { - if (item is List inner) - result.AddRange(depth > 1 ? Flatten(inner, depth - 1) : inner); - else - result.Add(item); - } - return result; - } + if (start == null) + { + return val; + } - // Filter items using a predicate on [key, value] pairs. - public static List Filter(object? val, Func, bool> check) - { - var all = Items(val); - var result = new List(); - foreach (var item in all) - if (check(item)) result.Add(item[1]); - return result; - } + int s = start.Value; + int e; - // Escape for use in a regular expression. - public static string EscRe(string? s) - { - if (s == null) return S_MT; - return R_ESCAPE_REGEXP.Replace(s, m => @"\" + m.Value); - } + if (s < 0) + { + e = vlen + s; + if (e < 0) + { + e = 0; + } - // URL-encode a string. - public static string EscUrl(string? s) - { - if (s == null) return S_MT; - return Uri.EscapeDataString(s); - } + s = 0; + } + else if (end != null) + { + e = end.Value; + if (e < 0) + { + e = vlen + e; + if (e < 0) + { + e = 0; + } + } + else if (vlen < e) + { + e = vlen; + } + } + else + { + e = vlen; + } - // Replace in a string (all occurrences). - private static string ReplaceStr(string? s, Regex from, string to) - { - if (s == null) return S_MT; - return from.Replace(s, to); - } + if (s > vlen) + { + s = vlen; + } - // Join an array of strings with a separator, stripping leading/trailing separators. - // Matches TypeScript: filter(items(filter(arr, string-non-empty), strip), non-empty).join(sep) - public static string Join(List arr, string? sep = null, bool url = false) - { - string sepdef = sep ?? S_CM; - string? sepre = sepdef.Length == 1 ? EscRe(sepdef) : null; - int sarr = Size(arr); + if (s < 0 || s > e || e > vlen) + { + return val is List ? new List() : val is string ? S_MT : val; + } - // Step 1: filter arr to non-empty strings. - var step1 = Filter(arr, n => (0 < (T.Str & Typify(n[1]))) && (string?)n[1] != S_MT); + if (val is List list) + { + var sub = list.GetRange(s, e - s); + if (mutate) + { + list.Clear(); + list.AddRange(sub); + return list; + } + return sub; + } + return val is string str ? str[s..e] : val; + } - // Step 2: items(step1, apply sep-stripping per element). - var step2 = Items(step1, n => + // String padding. + public static string Pad(object? str, int padding = 44, string? padchar = null) { - int i = int.Parse((string)n[0]!); - string s = (string)n[1]!; + string s = str is string ss ? ss : Stringify(str); + string pc = padchar != null ? (padchar + S_SP)[0].ToString() : S_SP; + return padding >= 0 ? s.PadRight(padding, pc[0]) : s.PadLeft(-padding, pc[0]); + } - if (sepre != null && sepre != S_MT) + // Get a list element by integer index (negative counts from end). + public static object? GetElem(object? val, object? key, object? alt = null) + { + if (val == null || key == null) { - if (url && i == 0) - return Regex.Replace(s, sepre + @"+$", S_MT); - - if (i > 0) - s = Regex.Replace(s, @"^" + sepre + @"+", S_MT); - - if (i < sarr - 1 || !url) - s = Regex.Replace(s, sepre + @"+$", S_MT); - - s = Regex.Replace(s, @"([^" + sepre + @"])" + sepre + @"+([^" + sepre + @"])", - "$1" + sepdef + "$2"); + return alt; } - return s; - }); + if (val is List list) + { + if (!int.TryParse(key?.ToString(), out int nkey)) + { + return alt; + } - // Step 3: filter out empty and join. - var nonEmpty = step2.Where(s => s != S_MT).ToList(); - return string.Join(sepdef, nonEmpty); - } + if (!R_INTEGER_KEY.IsMatch(key!.ToString()!)) + { + return alt; + } + + if (nkey < 0) + { + nkey = list.Count + nkey; + } - // Join URL parts. - public static string JoinUrl(params string[] parts) => - Join(parts.Cast().ToList(), S_FS, url: true); + return nkey < 0 || nkey >= list.Count ? alt : list[nkey]; + } + return alt is Delegate d2 ? d2.DynamicInvoke() : alt; + } - // JSON.stringify-style output (indent = spaces per level, like TS jsonify flags.indent). - public static string Jsonify(object? val, int indent = 2, int offset = 0) - { - if (val == null) return S_null; - try + // Safely get a property of a node. + public static object? GetProp(object? val, object? key, object? alt = null) { - var opts = new JsonSerializerOptions + if (val == null || key == null) { - WriteIndented = indent > 0, - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - string str = JsonSerializer.Serialize(SortKeys(val), opts); - // System.Text.Json on .NET 8 emits Environment.NewLine for indented - // output, which is "\r\n" on Windows. The TS-canonical corpus expects - // "\n", so normalise here. (.NET 9 adds JsonWriterOptions.NewLine.) - if (indent > 0) + return alt; + } + + if (val is Dictionary map) { - str = str.Replace("\r\n", "\n"); + string k = StrKey(key) ?? S_MT; + // Key present with JSON null → return null (TS: missing vs null). + return map.TryGetValue(k, out object? v) ? v : alt; } - // System.Text.Json always uses 2 spaces per level; rescale to match TS JSON.stringify(,,indent). - if (indent > 0 && indent != 2) + if (val is List list) { - const int netPerLevel = 2; - var lines = str.Split('\n'); - var rescaled = new List(lines.Length); - foreach (string line in lines) + string? ks = key?.ToString(); + if (ks != null && int.TryParse(ks, out int i)) { - int lead = 0; - while (lead < line.Length && line[lead] == ' ') lead++; - int levels = lead / netPerLevel; - string rest = line[lead..]; - rescaled.Add(levels == 0 ? rest : new string(' ', levels * indent) + rest); + if (i < 0) + { + i = list.Count + i; + } + + if (i >= 0 && i < list.Count) + { + return list[i]; + } } - str = string.Join("\n", rescaled); + return alt; } - if (offset > 0) - { - var lines = str.Split('\n').Skip(1).Select(l => - Pad(l, -(offset + Size(l)))).ToList(); - return "{\n" + Join(lines.Cast().ToList(), "\n"); - } - return str; + + return alt; } - catch + + // Convert a key to its string representation. + public static string? StrKey(object? key = null) { - return "__JSONIFY_FAILED__"; - } - } + if (key == null) + { + return S_MT; + } - // Human-friendly stringify (NOT JSON, removes quotes). Keys are sorted alphabetically. - public static string Stringify(object? val, int? maxlen = null) - { - string valstr; - // Match TS: NONE (undefined) -> "" but JSON null -> "null". - if (ReferenceEquals(val, NONE)) return S_MT; + int t = Typify(key); + if (0 < (T.Str & t)) + { + return (string)key; + } - if (val is string s) - { - valstr = s; - } - else - { - try + if (0 < (T.Boolean & t)) { - valstr = JsonSerializer.Serialize(SortKeys(val), - new JsonSerializerOptions - { - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }); - valstr = valstr.Replace("\"", S_MT); + return S_MT; } - catch + + if (0 < (T.Number & t)) { - return "__STRINGIFY_FAILED__"; + double d = Convert.ToDouble(key, CultureInfo.InvariantCulture); + return ((long)Math.Floor(d)).ToString(CultureInfo.InvariantCulture); } + return S_MT; } - if (maxlen != null && maxlen >= 0) + // Sorted keys of a map (strings) or list (index strings). + public static List KeysOf(object? val) { - if (valstr.Length > maxlen) - valstr = valstr.Substring(0, Math.Max(0, maxlen.Value - 3)) + "..."; + if (val is Dictionary map) + { + var keys = map.Keys.ToList(); + keys.Sort(StringComparer.Ordinal); + return keys; + } + return val is List list + ? Enumerable.Range(0, list.Count) + .Select(i => i.ToString(CultureInfo.InvariantCulture)) + .ToList() + : (List)([]); } - return valstr; - } - - // Recursively sort dictionary keys for deterministic output (matches TS behavior). - private static object? SortKeys(object? val) - { - if (val is Dictionary map) + // True if the key exists with a non-null value in val. + public static bool HasKey(object? val, object? key) { - var sorted = new Dictionary(); - foreach (var k in map.Keys.OrderBy(k => k, StringComparer.Ordinal)) - sorted[k] = SortKeys(map[k]); - return sorted; - } - if (val is List list) - return list.Select(SortKeys).ToList(); - return val; - } - - // Build a human-friendly path string. - // Pass StructUtils.NONE (not null) to indicate "path was not provided at all". - public static string Pathify(object? val, int startIn = 0, int endIn = 0) - { - // Distinguish "truly absent" (NONE) from "explicitly null" (null). - bool isNone = ReferenceEquals(val, NONE); - if (isNone) val = null; - - List? path = null; + if (val == null || key == null) + { + return false; + } - if (val is List lv) path = lv; - else if (val is string sv) path = [sv]; - else if (val is int or long or double or float) - path = [val]; + if (val is Dictionary map) + { + string k = StrKey(key) ?? S_MT; + return map.ContainsKey(k); + } + if (val is List list) + { + string? ks = key?.ToString(); + if (ks != null && int.TryParse(ks, out int i)) + { + if (i < 0) + { + i = list.Count + i; + } - int start = startIn >= 0 ? startIn : 0; - int end = endIn >= 0 ? endIn : 0; + return i >= 0 && i < list.Count; + } + return false; + } + return false; + } - if (path == null) + // Items of a map/list as [[key, value], ...] pairs. + public static List> Items(object? val) { - // val=null → , val=absent(NONE) → - string suffix = (val == null && !isNone) ? S_CN + "null" : - (val != null) ? S_CN + Stringify(val, 47) : S_MT; - return ""; + var keys = KeysOf(val); + return keys.Select(k => (List)[k, GetProp(val, k)]).ToList(); } - if (start >= 0) + // Items with a transform applied to each [key, value] pair. + public static List Items(object? val, Func, T2> apply) { - var sub = Slice(path, start, path.Count - end) as List ?? []; - if (sub.Count == 0) return ""; - - var parts = Filter(sub, n => IsKey(n[1])) - .Select(p => - { - if (p is int pi) return ((long)pi).ToString(); - if (p is long pl) return pl.ToString(); - if (p is double pd) return ((long)Math.Floor(pd)).ToString(); - if (p is float pf) return ((long)Math.Floor(pf)).ToString(); - return R_DOT.Replace((string)p!, S_MT); - }) - .Cast() - .ToList(); - - return Join(parts, S_DT); + return Items(val).Select(apply).ToList(); } - string sfx = (val == null && !isNone) ? S_CN + "null" : - (val != null) ? S_CN + Stringify(val, 47) : S_MT; - return ""; - } - - // Deep-clone a JSON-like structure (functions/instances are shallow-copied). - public static object? Clone(object? val) - { - if (val == null) return null; + // Flatten nested lists up to the given depth (default 1). + public static List Flatten(List list, int depth = 1) + { + if (!IsList(list)) + { + return list; + } - var refs = new List(); - int reftype = T.Func | T.Instance; + if (depth <= 0) + { + return list; + } - object? Replacer(object? v) - { - int tv = Typify(v); - if (0 < (reftype & tv)) + var result = new List(); + foreach (var item in list) { - refs.Add(v); - return "`$REF:" + (refs.Count - 1) + "`"; + if (item is List inner) + { + result.AddRange(depth > 1 ? Flatten(inner, depth - 1) : inner); + } + else + { + result.Add(item); + } } - return v; + return result; } - object? Reviver(object? v) + // Filter items using a predicate on [key, value] pairs. + public static List Filter(object? val, Func, bool> check) { - if (v is string sv) + var all = Items(val); + var result = new List(); + foreach (var item in all) { - var m = R_CLONE_REF.Match(sv); - if (m.Success && int.TryParse(m.Groups[1].Value, out int idx)) - return refs[idx]; + if (check(item)) + { + result.Add(item[1]); + } } - return v; - } - - return DeepClone(val, Replacer, Reviver); - } - - private static object? DeepClone(object? val, Func replacer, Func reviver) - { - if (val == null) return null; - var replaced = replacer(val); - if (replaced is string refStr && R_CLONE_REF.IsMatch(refStr)) - return reviver(refStr); - if (val is Dictionary map) - { - var result = new Dictionary(); - foreach (var kv in map) - result[kv.Key] = DeepClone(kv.Value, replacer, reviver); return result; } - if (val is List list) - return list.Select(item => DeepClone(item, replacer, reviver)).ToList(); - return val; - } - - // Safely set a property. Returns the (possibly modified) parent. - public static object? SetProp(object? parent, object? key, object? val) - { - if (!IsKey(key)) return parent; - - if (parent is Dictionary map) + // Escape for use in a regular expression. + public static string EscRe(string? s) { - string k = StrKey(key) ?? S_MT; - map[k] = val; + return s == null ? S_MT : R_ESCAPE_REGEXP.Replace(s, m => @"\" + m.Value); } - else if (parent is List list) + + // URL-encode a string. + public static string EscUrl(string? s) { - string? ks = key?.ToString(); - if (ks == null || !long.TryParse(ks, out long keyL)) return parent; - int keyI = (int)Math.Floor((double)keyL); - if (keyI >= 0) - { - int clamped = Convert.ToInt32(Slice(keyI, 0, list.Count + 1)); - while (list.Count <= clamped) list.Add(null); - list[clamped] = val; - } - else - list.Insert(0, val); + return s == null ? S_MT : Uri.EscapeDataString(s); } - return parent; - } - - // Safely delete a property. Returns the (possibly modified) parent. - public static object? DelProp(object? parent, object? key) - { - if (!IsKey(key)) return parent; - - if (parent is Dictionary map) + // Replace in a string (all occurrences). + private static string ReplaceStr(string? s, Regex from, string to) { - string k = StrKey(key) ?? S_MT; - map.Remove(k); + return s == null ? S_MT : from.Replace(s, to); } - else if (parent is List list) + + // Join an array of strings with a separator, stripping leading/trailing separators. + // Matches TypeScript: filter(items(filter(arr, string-non-empty), strip), non-empty).join(sep) + public static string Join(List arr, string? sep = null, bool url = false) { - if (!int.TryParse(key?.ToString(), out int keyI)) return parent; - keyI = (int)Math.Floor((double)keyI); - if (keyI >= 0 && keyI < list.Count) - list.RemoveAt(keyI); - } + string sepdef = sep ?? S_CM; + string? sepre = sepdef.Length == 1 ? EscRe(sepdef) : null; + int sarr = Size(arr); - return parent; - } + // Step 1: filter arr to non-empty strings. + var step1 = Filter(arr, n => (0 < (T.Str & Typify(n[1]))) && (string?)n[1] != S_MT); + // Step 2: items(step1, apply sep-stripping per element). + var step2 = Items(step1, n => + { + int i = int.Parse((string)n[0]!, CultureInfo.InvariantCulture); + string s = (string)n[1]!; - // ======================================================================== - // SetPath - // ======================================================================== + if (sepre is not null and not S_MT) + { + if (url && i == 0) + { + return Regex.Replace(s, sepre + @"+$", S_MT); + } - // Set a value at path inside store. Returns the parent node where the - // last key was set (matches TypeScript behavior). - public static object? SetPath(object? store, object? path, object? val) - { - if (store == null) return null; - - int pathType = Typify(path); - List parts; - - if (0 < (T.List & pathType) && path is List lp) - parts = lp; - else if (0 < (T.Str & pathType) && path is string ps) - parts = ps.Split('.').Cast().ToList(); - else if (0 < (T.Number & pathType)) - parts = [path]; - else - return null; + if (i > 0) + { + s = Regex.Replace(s, @"^" + sepre + @"+", S_MT); + } - if (parts.Count == 0) return null; + if (i < sarr - 1 || !url) + { + s = Regex.Replace(s, sepre + @"+$", S_MT); + } - object? parent = store; + s = Regex.Replace(s, @"([^" + sepre + @"])" + sepre + @"+([^" + sepre + @"])", + "$1" + sepdef + "$2"); + } - for (int pI = 0; pI < parts.Count - 1; pI++) + return s; + }); + + // Step 3: filter out empty and join. + var nonEmpty = step2.Where(s => s != S_MT).ToList(); + return string.Join(sepdef, nonEmpty); + } + + // Join URL parts. + public static string JoinUrl(params string[] parts) { - object? partKey = parts[pI]; - object? nextParent = GetProp(parent, partKey); - if (!IsNode(nextParent)) - { - // Create list if next key is numeric, otherwise map. - object? nextPartKey = GetElem(parts, pI + 1); - nextParent = (0 < (T.Number & Typify(nextPartKey))) - ? (object?)new List() - : new Dictionary(); - SetProp(parent, partKey, nextParent); - } - parent = nextParent; + return Join(parts.Cast().ToList(), S_FS, url: true); } - object? lastKey = GetElem(parts, -1); - if (val is Dictionary deleteMarker && - deleteMarker.ContainsKey("`$DELETE`")) - DelProp(parent, lastKey); - else - SetProp(parent, lastKey, val); + // JSON.stringify-style output (indent = spaces per level, like TS jsonify flags.indent). + public static string Jsonify(object? val, int indent = 2, int offset = 0) + { + if (val == null) + { + return S_null; + } - return parent; - } + try + { + var opts = indent > 0 ? JSON_OPTS_INDENTED : JSON_OPTS_COMPACT; + string str = JsonSerializer.Serialize(SortKeys(val), opts); + // System.Text.Json on .NET 8 emits Environment.NewLine for indented + // output, which is "\r\n" on Windows. The TS-canonical corpus expects + // "\n", so normalise here. (.NET 9 adds JsonWriterOptions.NewLine.) + if (indent > 0) + { + str = str.Replace("\r\n", "\n"); + } + // System.Text.Json always uses 2 spaces per level; rescale to match TS JSON.stringify(,,indent). + if (indent is > 0 and not 2) + { + const int netPerLevel = 2; + var lines = str.Split('\n'); + var rescaled = new List(lines.Length); + foreach (string line in lines) + { + int lead = 0; + while (lead < line.Length && line[lead] == ' ') + { + lead++; + } + int levels = lead / netPerLevel; + string rest = line[lead..]; + rescaled.Add(levels == 0 ? rest : new string(' ', levels * indent) + rest); + } + str = string.Join("\n", rescaled); + } + if (offset > 0) + { + var lines = str.Split('\n').Skip(1).Select(l => + Pad(l, -(offset + Size(l)))).ToList(); + return "{\n" + Join(lines.Cast().ToList(), "\n"); + } + return str; + } + catch + { + return "__JSONIFY_FAILED__"; + } + } - // ======================================================================== - // GetPath - // ======================================================================== + // Human-friendly stringify (NOT JSON, removes quotes). Keys are sorted alphabetically. + public static string Stringify(object? val, int? maxlen = null) + { + string valstr; + // Match TS: NONE (undefined) -> "" but JSON null -> "null". + if (ReferenceEquals(val, NONE)) + { + return S_MT; + } - // Get the value at a dot-separated key path deep inside a store. - // path: string ("a.b.c"), List, or number. - // state: optional InjectState providing base, dparent, dpath, meta, - // key, and a post-processing handler. - public static object? GetPath(object? store, object? path, - object? current = null, InjectState? state = null) - { - // Resolve path to a mutable list of string parts. - List parts; - if (path is List lp) - parts = lp.Select(p => StrKey(p) ?? S_MT).ToList(); - else if (path is List ls) - parts = new List(ls); - else if (path is string sp) - parts = sp == S_MT ? [S_MT] : sp.Split('.').ToList(); - else if (0 < (T.Number & Typify(path))) - parts = [StrKey(path) ?? S_MT]; - else - return null; + if (val is string s) + { + valstr = s; + } + else + { + try + { + valstr = JsonSerializer.Serialize(SortKeys(val), JSON_OPTS_COMPACT); + valstr = valstr.Replace("\"", S_MT); + } + catch + { + return "__STRINGIFY_FAILED__"; + } + } - string? baseKey = state?.Base; - object? src = GetProp(store, baseKey, store) ?? store; - object? dparent = state?.DParent; - int numparts = parts.Count; + if (maxlen is not null and >= 0) + { + if (valstr.Length > maxlen) + { + valstr = valstr[..Math.Max(0, maxlen.Value - 3)] + "..."; + } + } - object? val = store; + return valstr; + } - // Empty path (or empty string path) → return src. - if (path == null || store == null || (numparts == 1 && S_MT == parts[0])) + // Recursively sort dictionary keys for deterministic output (matches TS behavior). + private static object? SortKeys(object? val) { - val = src; + if (val is Dictionary map) + { + var sorted = new Dictionary(); + foreach (var k in map.Keys.OrderBy(k => k, StringComparer.Ordinal)) + { + sorted[k] = SortKeys(map[k]); + } + + return sorted; + } + return val is List list ? list.Select(SortKeys).ToList() : val; } - else if (numparts > 0) + + // Build a human-friendly path string. + // Pass StructUtils.NONE (not null) to indicate "path was not provided at all". + public static string Pathify(object? val, int startIn = 0, int endIn = 0) { - // Single-part path: check for $ACTION functions stored in the root. - if (numparts == 1) - val = GetProp(store, parts[0]); + // Distinguish "truly absent" (NONE) from "explicitly null" (null). + bool isNone = ReferenceEquals(val, NONE); + if (isNone) + { + val = null; + } - if (!IsFunc(val)) + List? path = null; + + if (val is List lv) { - val = src; + path = lv; + } + else if (val is string sv) + { + path = [sv]; + } + else if (val is int or long or double or float) + { + path = [val]; + } - // Meta path syntax: "q0$~x" or "q0$=x" — navigate into meta. - var m = R_META_PATH.Match(parts[0]); - if (m.Success && state?.Meta != null) + int start = startIn >= 0 ? startIn : 0; + int end = endIn >= 0 ? endIn : 0; + + if (path == null) + { + // val=null → , val=absent(NONE) → + string suffix = (val == null && !isNone) ? S_CN + "null" : + (val != null) ? S_CN + Stringify(val, 47) : S_MT; + return ""; + } + + if (start >= 0) + { + var sub = Slice(path, start, path.Count - end) as List ?? []; + if (sub.Count == 0) { - val = GetProp(state.Meta, m.Groups[1].Value); - parts[0] = m.Groups[3].Value; + return ""; } - List dpath = state?.DPath ?? []; + var parts = Filter(sub, n => IsKey(n[1])) + .Select(p => + { + if (p is int pi) + { + return ((long)pi).ToString(CultureInfo.InvariantCulture); + } + + if (p is long pl) + { + return pl.ToString(CultureInfo.InvariantCulture); + } + + return p is double pd + ? ((long)Math.Floor(pd)).ToString(CultureInfo.InvariantCulture) + : p is float pf + ? ((long)Math.Floor(pf)).ToString(CultureInfo.InvariantCulture) + : R_DOT.Replace((string)p!, S_MT); + }) + .Cast() + .ToList(); + + return Join(parts, S_DT); + } + + string sfx = (val == null && !isNone) ? S_CN + "null" : + (val != null) ? S_CN + Stringify(val, 47) : S_MT; + return ""; + } + + // Deep-clone a JSON-like structure (functions/instances are shallow-copied). + public static object? Clone(object? val) + { + if (val == null) + { + return null; + } + + var refs = new List(); + int reftype = T.Func | T.Instance; - for (int pI = 0; val != null && pI < numparts; pI++) + object? Replacer(object? v) + { + int tv = Typify(v); + if (0 < (reftype & tv)) { - string part = parts[pI]; + refs.Add(v); + return "`$REF:" + (refs.Count - 1) + "`"; + } + return v; + } - // Special path keywords and dynamic substitution. - if (state != null && part == S_DKEY) - part = state.Key ?? S_MT; - else if (state != null && part.StartsWith("$GET:")) + object? Reviver(object? v) + { + if (v is string sv) + { + var m = R_CLONE_REF.Match(sv); + if (m.Success && int.TryParse(m.Groups[1].Value, out int idx)) { - string sub = part[5..^1]; - part = Stringify(GetPath(src, sub)); + return refs[idx]; } - else if (state != null && part.StartsWith("$REF:")) + } + return v; + } + + return DeepClone(val, Replacer, Reviver); + } + + private static object? DeepClone(object? val, Func replacer, Func reviver) + { + if (val == null) + { + return null; + } + + var replaced = replacer(val); + if (replaced is string refStr && R_CLONE_REF.IsMatch(refStr)) + { + return reviver(refStr); + } + + if (val is Dictionary map) + { + var result = new Dictionary(); + foreach (var kv in map) + { + result[kv.Key] = DeepClone(kv.Value, replacer, reviver); + } + + return result; + } + return val is List list ? list.Select(item => DeepClone(item, replacer, reviver)).ToList() : val; + } + + // Define a JSON object (dictionary) from alternating key/value arguments. + // Jm("a", 1, "b", 2) => { "a": 1, "b": 2 }. A missing trailing value is null. + public static Dictionary Jm(params object?[] kv) + { + var o = new Dictionary(); + for (int i = 0; i < kv.Length; i += 2) + { + var k = kv[i]; + var ks = k is string s ? s : Stringify(k); + o[ks] = i + 1 < kv.Length ? kv[i + 1] : null; + } + + return o; + } + + // Define a JSON array (list) from positional arguments. + // Jt(1, "x", true) => [1, "x", true]. + public static List Jt(params object?[] v) + { + var a = new List(v.Length); + a.AddRange(v); + return a; + } + + // Safely set a property. Returns the (possibly modified) parent. + public static object? SetProp(object? parent, object? key, object? val) + { + if (!IsKey(key)) + { + return parent; + } + + if (parent is Dictionary map) + { + string k = StrKey(key) ?? S_MT; + map[k] = val; + } + else if (parent is List list) + { + string? ks = key?.ToString(); + if (ks == null || !long.TryParse(ks, out long keyL)) + { + return parent; + } + + int keyI = (int)Math.Floor((double)keyL); + if (keyI >= 0) + { + int clamped = Convert.ToInt32(Slice(keyI, 0, list.Count + 1), CultureInfo.InvariantCulture); + while (list.Count <= clamped) { - string sub = part[5..^1]; - object? specVal = GetProp(store, S_DSPEC); - part = specVal != null ? Stringify(GetPath(specVal, sub)) : S_MT; + list.Add(null); } - else if (state != null && part.StartsWith("$META:")) + + list[clamped] = val; + } + else + { + list.Insert(0, val); + } + } + + return parent; + } + + // Safely delete a property. Returns the (possibly modified) parent. + public static object? DelProp(object? parent, object? key) + { + if (!IsKey(key)) + { + return parent; + } + + if (parent is Dictionary map) + { + string k = StrKey(key) ?? S_MT; + map.Remove(k); + } + else if (parent is List list) + { + if (!int.TryParse(key?.ToString(), out int keyI)) + { + return parent; + } + + keyI = (int)Math.Floor((double)keyI); + if (keyI >= 0 && keyI < list.Count) + { + list.RemoveAt(keyI); + } + } + + return parent; + } + + + // ======================================================================== + // SetPath + // ======================================================================== + + // Set a value at path inside store. Returns the parent node where the + // last key was set (matches TypeScript behavior). + public static object? SetPath(object? store, object? path, object? val) + { + if (store == null) + { + return null; + } + + int pathType = Typify(path); + List parts; + + if (0 < (T.List & pathType) && path is List lp) + { + parts = lp; + } + else if (0 < (T.Str & pathType) && path is string ps) + { + parts = ps.Split('.').Cast().ToList(); + } + else if (0 < (T.Number & pathType)) + { + parts = [path]; + } + else + { + return null; + } + + if (parts.Count == 0) + { + return null; + } + + object? parent = store; + + for (int pI = 0; pI < parts.Count - 1; pI++) + { + object? partKey = parts[pI]; + object? nextParent = GetProp(parent, partKey); + if (!IsNode(nextParent)) + { + // Create list if next key is numeric, otherwise map. + object? nextPartKey = GetElem(parts, pI + 1); + nextParent = (0 < (T.Number & Typify(nextPartKey))) + ? (object?)new List() + : new Dictionary(); + SetProp(parent, partKey, nextParent); + } + parent = nextParent; + } + + object? lastKey = GetElem(parts, -1); + if (val is Dictionary deleteMarker && + deleteMarker.ContainsKey("`$DELETE`")) + { + DelProp(parent, lastKey); + } + else + { + SetProp(parent, lastKey, val); + } + + return parent; + } + + + // ======================================================================== + // GetPath + // ======================================================================== + + // Get the value at a dot-separated key path deep inside a store. + // path: string ("a.b.c"), List, or number. + // state: optional InjectState providing base, dparent, dpath, meta, + // key, and a post-processing handler. + public static object? GetPath(object? store, object? path, + object? current = null, InjectState? state = null) + { + // Resolve path to a mutable list of string parts. + List parts; + if (path is List lp) + { + parts = lp.Select(p => StrKey(p) ?? S_MT).ToList(); + } + else if (path is List ls) + { + parts = new List(ls); + } + else if (path is string sp) + { + parts = sp == S_MT ? [S_MT] : [.. sp.Split('.')]; + } + else if (0 < (T.Number & Typify(path))) + { + parts = [StrKey(path) ?? S_MT]; + } + else + { + return null; + } + + string? baseKey = state?.Base; + object? src = GetProp(store, baseKey, store) ?? store; + object? dparent = state?.DParent; + int numparts = parts.Count; + + object? val = store; + + // Empty path (or empty string path) → return src. + if (path == null || store == null || (numparts == 1 && S_MT == parts[0])) + { + val = src; + } + else if (numparts > 0) + { + // Single-part path: check for $ACTION functions stored in the root. + if (numparts == 1) + { + val = GetProp(store, parts[0]); + } + + if (!IsFunc(val)) + { + val = src; + + // Meta path syntax: "q0$~x" or "q0$=x" — navigate into meta. + var m = R_META_PATH.Match(parts[0]); + if (m.Success && state?.Meta != null) { - string sub = part[6..^1]; - part = Stringify(GetPath(state.Meta, sub)); + val = GetProp(state.Meta, m.Groups[1].Value); + parts[0] = m.Groups[3].Value; } - // $$ is an escape for a literal $. - part = part.Replace("$$", "$"); + List dpath = state?.DPath ?? []; - if (S_MT == part) + for (int pI = 0; val != null && pI < numparts; pI++) { - // Count consecutive empty parts (each "." adds one). - int ascends = 0; - while (1 + pI < numparts && S_MT == parts[1 + pI]) + string part = parts[pI]; + + // Special path keywords and dynamic substitution. + if (state != null && part == S_DKEY) { - ascends++; - pI++; + part = state.Key ?? S_MT; } + else if (state != null && part.StartsWith("$GET:", StringComparison.Ordinal)) + { + string sub = part[5..^1]; + part = Stringify(GetPath(src, sub)); + } + else if (state != null && part.StartsWith("$REF:", StringComparison.Ordinal)) + { + string sub = part[5..^1]; + object? specVal = GetProp(store, S_DSPEC); + part = specVal != null ? Stringify(GetPath(specVal, sub)) : S_MT; + } + else if (state != null && part.StartsWith("$META:", StringComparison.Ordinal)) + { + string sub = part[6..^1]; + part = Stringify(GetPath(state.Meta, sub)); + } + + // $$ is an escape for a literal $. + part = part.Replace("$$", "$"); - if (state != null && ascends > 0) + if (S_MT == part) { - // Last empty part cancels one ascend (the "." that - // ended the path string is a trailing separator). - if (pI == numparts - 1) ascends--; + // Count consecutive empty parts (each "." adds one). + int ascends = 0; + while (1 + pI < numparts && S_MT == parts[1 + pI]) + { + ascends++; + pI++; + } - if (ascends == 0) + if (state != null && ascends > 0) { - val = dparent; + // Last empty part cancels one ascend (the "." that + // ended the path string is a trailing separator). + if (pI == numparts - 1) + { + ascends--; + } + + if (ascends == 0) + { + val = dparent; + } + else + { + int dpathLen = dpath.Count; + int cutLen = Math.Max(0, dpathLen - ascends); + var fullpath = new List(); + for (int i = 0; i < cutLen; i++) + { + fullpath.Add(StrKey(dpath[i]) ?? S_MT); + } + + if (pI + 1 < numparts) + { + fullpath.AddRange(parts.Skip(pI + 1)); + } + + val = ascends <= dpathLen + ? GetPath(store, fullpath.Cast().ToList()) + : null; + break; + } } else { - int dpathLen = dpath.Count; - int cutLen = Math.Max(0, dpathLen - ascends); - var fullpath = new List(); - for (int i = 0; i < cutLen; i++) - fullpath.Add(StrKey(dpath[i]) ?? S_MT); - if (pI + 1 < numparts) - fullpath.AddRange(parts.Skip(pI + 1)); - - val = ascends <= dpathLen - ? GetPath(store, fullpath.Cast().ToList()) - : null; - break; + val = dparent; } } else { - val = dparent; + val = GetProp(val, part); } } - else + } + } + + // Optional post-processing via a handler function in the state. + if (state?.Handler != null) + { + string refStr = Pathify(path); + val = state.Handler(state, val, refStr, store); + } + + return val; + } + + + // ======================================================================== + // Walk + // ======================================================================== + + // Walk a data structure depth-first, applying before/after callbacks. + // key=null and parent=null at the root. path contains string keys. + // maxdepth: null or negative → MAXDEPTH. 0 → no descent at all. + // Stops descending when path.Count >= md (children beyond depth are not visited). + // + // The `path` argument passed to the before/after callbacks is a single + // mutable list per depth, shared across all callback invocations for the + // lifetime of this top-level Walk call. Callbacks that need to store the + // path MUST clone it (e.g. new List(path)); the contents will + // otherwise be overwritten by subsequent visits. + public static object? Walk( + object? val, + WalkApply? before = null, + WalkApply? after = null, + int? maxdepth = null, + object? key = null, + object? parent = null, + List? path = null, + List>? pool = null) + { + pool ??= [[]]; + path ??= pool[0]; + + int depth = path.Count; + + object? out_ = before == null ? val : before(key, val, parent, path); + + int md = (maxdepth.HasValue && maxdepth.Value >= 0) ? maxdepth.Value : MAXDEPTH; + if (md == 0 || depth >= md) + { + return out_; + } + + if (IsNode(out_)) + { + int childDepth = depth + 1; + + // Grow pool on demand so pool[childDepth] exists. + while (pool.Count <= childDepth) + { + pool.Add(new List(pool.Count)); + } + + List childPath = pool[childDepth]; + + // Ensure childPath has exactly childDepth slots (mutated in place below). + if (childPath.Count < childDepth) + { + while (childPath.Count < childDepth) + { + childPath.Add(null); + } + } + else if (childPath.Count > childDepth) + { + childPath.RemoveRange(childDepth, childPath.Count - childDepth); + } + + // Sync prefix [0..depth-1] from the current path. Only needed once per + // parent: siblings share the same prefix and will each overwrite slot + // [depth] below. + for (int i = 0; i < depth; i++) + { + childPath[i] = path[i]; + } + + foreach (var item in Items(out_)) + { + string ckey = StrKey(item[0]) ?? S_MT; + object? child = item[1]; + childPath[depth] = ckey; + SetProp(out_, ckey, Walk(child, before, after, maxdepth, ckey, out_, childPath, pool)); + } + } + + out_ = after == null ? out_ : after(key, out_, parent, path); + + return out_; + } + + + // ======================================================================== + // Merge + // ======================================================================== + + // Merge a list of values into each other. Later values take precedence. + // Nodes override scalars; mismatched node kinds override rather than merge. + // maxdepth: null/-1 → MAXDEPTH (full deep merge). 0 → empty-shell output. + public static object? Merge(object? val, int? maxdepth = null) + { + if (!IsList(val)) + { + return val; + } + + int md = maxdepth.HasValue ? (maxdepth.Value < 0 ? 0 : maxdepth.Value) : MAXDEPTH; + + var list = (List)val!; + int lenlist = list.Count; + + if (lenlist == 0) + { + return null; + } + + if (lenlist == 1) + { + return list[0]; + } + + // Start with first element (or empty map if null). + object? out_ = GetProp(list, 0) ?? new Dictionary(); + + for (int oI = 1; oI < lenlist; oI++) + { + object? obj = list[oI]; + + if (!IsNode(obj)) + { + out_ = obj; // Scalar wins. + } + else + { + // cur[pI] = working value at depth pI in the output. + // dst[pI] = existing value at depth pI in the destination (out_). + var cur = new object?[MAXDEPTH + 2]; + var dst = new object?[MAXDEPTH + 2]; + cur[0] = out_; + dst[0] = out_; + + object? mergeBefore(object? key, object? mval, object? _parent, List path) + { + int pI = path.Count; + + if (md <= pI) + { + // Beyond max depth: copy directly. + if (key != null) + { + SetProp(cur[pI - 1], key, mval); + } + } + else if (!IsNode(mval)) + { + cur[pI] = mval; + } + else + { + // Navigate destination parallel to current override path. + if (pI > 0 && key != null) + { + dst[pI] = GetProp(dst[pI - 1], key); + } + + object? tval = dst[pI]; + + if (tval == null && 0 == (T.Instance & Typify(mval))) + { + // Destination absent → create empty node. + cur[pI] = IsList(mval) + ? (object?)new List() + : new Dictionary(); + } + else if (Typify(mval) == Typify(tval)) + { + // Same type → merge into existing destination node. + cur[pI] = tval; + } + else + { + // Type mismatch → override wins, skip descending. + cur[pI] = mval; + mval = null; + } + } + + return mval; + } + + object? mergeAfter(object? key, object? _, object? _parent, List path) { - val = GetProp(val, part); + int cI = path.Count; + if (key == null || cI <= 0) + { + return cur[0]; + } + + object? value = cur[cI]; + cur[cI - 1] = SetProp(cur[cI - 1], key, value) ?? cur[cI - 1]; + return value; } + + Walk(obj, mergeBefore, mergeAfter, md); + out_ = cur[0]; } } - } - - // Optional post-processing via a handler function in the state. - if (state?.Handler != null) - { - string refStr = Pathify(path); - val = state.Handler(state, val, refStr, store); - } - - return val; - } + // md=0: return empty shell of last element's type. + if (md == 0) + { + out_ = GetElem(list, -1); + if (IsList(out_)) + { + out_ = new List(); + } + else if (IsMap(out_)) + { + out_ = new Dictionary(); + } + } - // ======================================================================== - // Walk - // ======================================================================== - - // Walk a data structure depth-first, applying before/after callbacks. - // key=null and parent=null at the root. path contains string keys. - // maxdepth: null or negative → MAXDEPTH. 0 → no descent at all. - // Stops descending when path.Count >= md (children beyond depth are not visited). - // - // The `path` argument passed to the before/after callbacks is a single - // mutable list per depth, shared across all callback invocations for the - // lifetime of this top-level Walk call. Callbacks that need to store the - // path MUST clone it (e.g. new List(path)); the contents will - // otherwise be overwritten by subsequent visits. - public static object? Walk( - object? val, - WalkApply? before = null, - WalkApply? after = null, - int? maxdepth = null, - object? key = null, - object? parent = null, - List? path = null, - List>? pool = null) - { - if (pool == null) - { - pool = new List> { new List() }; - } - if (path == null) - { - path = pool[0]; + return out_; } - int depth = path.Count; - - object? out_ = before == null ? val : before(key, val, parent, path); - int md = (maxdepth.HasValue && maxdepth.Value >= 0) ? maxdepth.Value : MAXDEPTH; - if (md == 0 || depth >= md) - return out_; + // ======================================================================== + // Inject + // ======================================================================== - if (IsNode(out_)) + // Default handler: invokes transform functions (keys starting with $) + // or updates the parent node when a full-match injection is resolved. + private static readonly Injector _InjectHandler = (inj, val, refStr, store) => { - int childDepth = depth + 1; - - // Grow pool on demand so pool[childDepth] exists. - while (pool.Count <= childDepth) - { - pool.Add(new List(pool.Count)); - } - - List childPath = pool[childDepth]; - - // Ensure childPath has exactly childDepth slots (mutated in place below). - if (childPath.Count < childDepth) + object? out_ = val; + bool iscmd = IsFunc(val) && (refStr == null || refStr.StartsWith(S_DS, StringComparison.Ordinal)); + if (iscmd && val is Injector injFn) { - while (childPath.Count < childDepth) - childPath.Add(null); + out_ = injFn(inj, val, refStr, store); } - else if (childPath.Count > childDepth) + else if (inj.Mode == M_VAL && inj.Full) { - childPath.RemoveRange(childDepth, childPath.Count - childDepth); + inj.SetVal(val); } - // Sync prefix [0..depth-1] from the current path. Only needed once per - // parent: siblings share the same prefix and will each overwrite slot - // [depth] below. - for (int i = 0; i < depth; i++) - { - childPath[i] = path[i]; - } + return out_; + }; + - foreach (var item in Items(out_)) + // Inject store values into a string. Not a public utility – used by Inject. + // Backtick-delimited references (e.g. "`a.b`") are resolved via GetPath. + // A string that is entirely one reference returns the raw resolved value. + // A string with embedded references has each one stringified in-place. + private static object? _InjectStr(string val, object? store, object? current, InjectState? state) + { + if (val == S_MT) { - string ckey = StrKey(item[0]) ?? S_MT; - object? child = item[1]; - childPath[depth] = ckey; - SetProp(out_, ckey, Walk(child, before, after, maxdepth, ckey, out_, childPath, pool)); + return S_MT; } - } - out_ = after == null ? out_ : after(key, out_, parent, path); + var m = R_INJECTION_FULL.Match(val); - return out_; - } + if (m.Success) + { + if (state != null) + { + state.Full = true; + } + string pathref = m.Groups[1].Value; + // Unescape $BT → ` and $DS → $ + if (pathref.Length > 3) + { + pathref = pathref.Replace("$BT", S_BT).Replace("$DS", S_DS); + } - // ======================================================================== - // Merge - // ======================================================================== + return GetPath(store, pathref, current, state); + } - // Merge a list of values into each other. Later values take precedence. - // Nodes override scalars; mismatched node kinds override rather than merge. - // maxdepth: null/-1 → MAXDEPTH (full deep merge). 0 → empty-shell output. - public static object? Merge(object? val, int? maxdepth = null) - { - if (!IsList(val)) return val; + // Replace every embedded `ref` with its stringified resolved value. + object? outStr = R_INJECTION_PARTIAL.Replace(val, match => + { + string ref_ = match.Groups[1].Value; + if (ref_.Length > 3) + { + ref_ = ref_.Replace("$BT", S_BT).Replace("$DS", S_DS); + } - int md = maxdepth.HasValue ? (maxdepth.Value < 0 ? 0 : maxdepth.Value) : MAXDEPTH; + if (state != null) + { + state.Full = false; + } - var list = (List)val!; - int lenlist = list.Count; + object? found = GetPath(store, ref_, current, state); + if (found == null) + { + // Distinguish an explicit null value (key exists) from a + // missing key. Only top-level single-segment paths are checked. + bool keyExists = !ref_.Contains('.') + && store is Dictionary sm + && sm.ContainsKey(ref_); + return keyExists ? S_null : S_MT; + } + if (found is string fs) + { + return fs; + } + // Non-string, non-null → serialize as compact JSON. + try + { + return JsonSerializer.Serialize(found, JSON_OPTS_COMPACT); + } + catch { return Stringify(found); } + }); - if (lenlist == 0) return null; - if (lenlist == 1) return list[0]; + // Custom handler on the whole string (e.g. for post-processing by transforms). + if (state?.Handler != null && IsFunc(state.Handler)) + { + state.Full = true; + outStr = state.Handler(state, outStr, val, store); + } - // Start with first element (or empty map if null). - object? out_ = GetProp(list, 0) ?? new Dictionary(); + return outStr; + } - for (int oI = 1; oI < lenlist; oI++) - { - object? obj = list[oI]; - if (!IsNode(obj)) - { - out_ = obj; // Scalar wins. - } - else + // Inject values from a data store into a node recursively. + // Backtick-delimited path references in string values are replaced with + // the resolved store values. The optional state carries context for + // recursive calls; callers at the root should pass null. + public static object? Inject(object? val, object? store, InjectState? state = null) + { + // ── Root initialisation ────────────────────────────────────────────── + if (state == null || state.Mode == 0) { - // cur[pI] = working value at depth pI in the output. - // dst[pI] = existing value at depth pI in the destination (out_). - var cur = new object?[MAXDEPTH + 2]; - var dst = new object?[MAXDEPTH + 2]; - cur[0] = out_; - dst[0] = out_; + // Wrap val inside a virtual {$TOP: val} parent so every code path + // can use the same "set value in parent" logic. + var parent = new Dictionary { [S_DTOP] = val }; + var newState = new InjectState + { + Mode = M_VAL, + Full = false, + KeyI = 0, + Keys = [(object?)S_DTOP], + Key = S_DTOP, + Val = val, + Parent = parent, + Path = [(object?)S_DTOP], + Nodes = [(object?)parent], + Handler = _InjectHandler, + Base = S_DTOP, + Errs = (GetProp(store, S_DERRS) as List) ?? [], + Meta = new Dictionary { ["__d"] = 0L }, + DParent = store, + DPath = [(object?)S_DTOP], + }; - WalkApply mergeBefore = (key, mval, _parent, path) => + // Allow a partial injdef to override defaults. + if (state != null) { - int pI = path.Count; + if (state.ModifyFn != null) + { + newState.ModifyFn = state.ModifyFn; + } - if (md <= pI) + if (state.Extra != null) { - // Beyond max depth: copy directly. - if (key != null) SetProp(cur[pI - 1], key, mval); + newState.Extra = state.Extra; } - else if (!IsNode(mval)) + + if (state.Meta != null) { - cur[pI] = mval; + newState.Meta = state.Meta; } - else + + if (state.Handler != null) { - // Navigate destination parallel to current override path. - if (pI > 0 && key != null) - dst[pI] = GetProp(dst[pI - 1], key); + newState.Handler = state.Handler; + } + } - object? tval = dst[pI]; + state = newState; + } - if (tval == null && 0 == (T.Instance & Typify(mval))) - { - // Destination absent → create empty node. - cur[pI] = IsList(mval) - ? (object?)new List() - : new Dictionary(); - } - else if (Typify(mval) == Typify(tval)) - { - // Same type → merge into existing destination node. - cur[pI] = tval; - } - else - { - // Type mismatch → override wins, skip descending. - cur[pI] = mval; - mval = null; - } - } + state.Descend(); - return mval; - }; + // ── Node: recurse into children ────────────────────────────────────── + if (IsNode(val)) + { + // Non-$ keys first (deterministic order), then $ transform keys. + var allKeys = KeysOf(val); + List keys; + if (IsMap(val)) + { + var normal = allKeys.Where(k => !k.Contains(S_DS)).ToList(); + var transform = allKeys.Where(k => k.Contains(S_DS)).ToList(); + keys = [.. normal.Cast(), .. transform.Cast()]; + } + else + { + keys = allKeys.Cast().ToList(); + } - WalkApply mergeAfter = (key, _, _parent, path) => + int nkI = 0; + while (nkI < keys.Count) { - int cI = path.Count; - if (key == null || cI <= 0) return cur[0]; + string nodekey = StrKey(keys[nkI]) ?? S_MT; + var childinj = state.Child(nkI, keys); + childinj.Mode = M_KEYPRE; - object? value = cur[cI]; - cur[cI - 1] = SetProp(cur[cI - 1], key, value) ?? cur[cI - 1]; - return value; - }; + object? preKey = _InjectStr(nodekey, store, state.DParent, childinj); - Walk(obj, mergeBefore, mergeAfter, md); - out_ = cur[0]; - } - } + // Read back any key-list / index modifications from the child. + nkI = childinj.KeyI; + keys = childinj.Keys; + val = childinj.Parent; - // md=0: return empty shell of last element's type. - if (md == 0) - { - out_ = GetElem(list, -1); - if (IsList(out_)) out_ = new List(); - else if (IsMap(out_)) out_ = new Dictionary(); - } + // TS: if (NONE !== prekey) — skip NONE/undefined (e.g. $CHILD KEYPRE). Also skip null prekey. + if (preKey != null && !ReferenceEquals(preKey, NONE)) + { + object? childval = GetProp(val, preKey); + childinj.Val = childval; + childinj.Mode = M_VAL; - return out_; - } + Inject(childval, store, childinj); + nkI = childinj.KeyI; + keys = childinj.Keys; + val = childinj.Parent; - // ======================================================================== - // Inject - // ======================================================================== + childinj.Mode = M_KEYPOST; + _InjectStr(nodekey, store, state.DParent, childinj); - // Default handler: invokes transform functions (keys starting with $) - // or updates the parent node when a full-match injection is resolved. - private static readonly Injector _InjectHandler = (inj, val, refStr, store) => - { - object? out_ = val; - bool iscmd = IsFunc(val) && (refStr == null || refStr.StartsWith(S_DS)); - if (iscmd && val is Injector injFn) - out_ = injFn(inj, val, refStr, store); - else if (inj.Mode == M_VAL && inj.Full) - inj.SetVal(val); - return out_; - }; - - - // Inject store values into a string. Not a public utility – used by Inject. - // Backtick-delimited references (e.g. "`a.b`") are resolved via GetPath. - // A string that is entirely one reference returns the raw resolved value. - // A string with embedded references has each one stringified in-place. - private static object? _InjectStr(string val, object? store, object? current, InjectState? state) - { - if (val == S_MT) return S_MT; + nkI = childinj.KeyI; + keys = childinj.Keys; + val = childinj.Parent; + } - var m = R_INJECTION_FULL.Match(val); + nkI++; + } + } - if (m.Success) - { - if (state != null) state.Full = true; - string pathref = m.Groups[1].Value; - // Unescape $BT → ` and $DS → $ - if (pathref.Length > 3) - pathref = pathref.Replace("$BT", S_BT).Replace("$DS", S_DS); - return GetPath(store, pathref, current, state); - } + // ── String scalar: resolve backtick references ────────────────────── + else if (0 < (T.Str & Typify(val)) && val is string strVal) + { + state.Mode = M_VAL; + object? injected = _InjectStr(strVal, store, state.DParent, state); + bool isSkip = injected is Dictionary sd && sd.ContainsKey("`$SKIP`"); + if (!isSkip) + { + state.SetVal(injected); + } - // Replace every embedded `ref` with its stringified resolved value. - object? outStr = R_INJECTION_PARTIAL.Replace(val, match => - { - string ref_ = match.Groups[1].Value; - if (ref_.Length > 3) - ref_ = ref_.Replace("$BT", S_BT).Replace("$DS", S_DS); - if (state != null) state.Full = false; - object? found = GetPath(store, ref_, current, state); - if (found == null) - { - // Distinguish an explicit null value (key exists) from a - // missing key. Only top-level single-segment paths are checked. - bool keyExists = !ref_.Contains('.') - && store is Dictionary sm - && sm.ContainsKey(ref_); - return keyExists ? S_null : S_MT; - } - if (found is string fs) return fs; - // Non-string, non-null → serialize as compact JSON. - try + val = injected; + } + + // ── Custom modify callback ─────────────────────────────────────────── + if (state.ModifyFn != null) { - return System.Text.Json.JsonSerializer.Serialize(found, - new System.Text.Json.JsonSerializerOptions - { - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - WriteIndented = false, - }); + bool isSkip = val is Dictionary sd2 && sd2.ContainsKey("`$SKIP`"); + if (!isSkip) + { + object? mparent = state.Parent; + object? mval = GetProp(mparent, state.Key); + state.ModifyFn(mval, state.Key, mparent, state, store); + } } - catch { return Stringify(found); } - }); - // Custom handler on the whole string (e.g. for post-processing by transforms). - if (state?.Handler != null && IsFunc(state.Handler)) - { - state.Full = true; - outStr = state.Handler(state, outStr, val, store); - } + state.Val = val; - return outStr; - } + // The top-level return is the injected value extracted from its wrapper. + return GetProp(state.Parent, S_DTOP); + } - // Inject values from a data store into a node recursively. - // Backtick-delimited path references in string values are replaced with - // the resolved store values. The optional state carries context for - // recursive calls; callers at the root should pass null. - public static object? Inject(object? val, object? store, InjectState? state = null) - { - // ── Root initialisation ────────────────────────────────────────────── - if (state == null || state.Mode == 0) - { - // Wrap val inside a virtual {$TOP: val} parent so every code path - // can use the same "set value in parent" logic. - var parent = new Dictionary { [S_DTOP] = val }; - var newState = new InjectState - { - Mode = M_VAL, - Full = false, - KeyI = 0, - Keys = [(object?)S_DTOP], - Key = S_DTOP, - Val = val, - Parent = parent, - Path = [(object?)S_DTOP], - Nodes = [(object?)parent], - Handler = _InjectHandler, - Base = S_DTOP, - Errs = (GetProp(store, S_DERRS) as List) ?? [], - Meta = new Dictionary { ["__d"] = 0L }, - DParent = store, - DPath = [(object?)S_DTOP], - }; + // ======================================================================== + // Transform helpers + // ======================================================================== - // Allow a partial injdef to override defaults. - if (state != null) + // Verify the injection mode is valid for a given transform. + public static bool CheckPlacement(int modes, string name, int parentType, InjectState state) + { + static string PlacementName(int mode) { - if (state.ModifyFn != null) newState.ModifyFn = state.ModifyFn; - if (state.Extra != null) newState.Extra = state.Extra; - if (state.Meta != null) newState.Meta = state.Meta; - if (state.Handler != null) newState.Handler = state.Handler; + return mode == M_VAL ? "value" : S_key; } - state = newState; - } + if (0 == (modes & state.Mode)) + { + var expected = new List(); + foreach (int m in new[] { M_KEYPRE, M_KEYPOST, M_VAL }) + { + if (0 != (modes & m)) + { + expected.Add(PlacementName(m)); + } + } - state.Descend(); + string exp = Join(expected.Cast().ToList(), ", "); + state.Errs.Add($"${name}: invalid placement as {PlacementName(state.Mode)}, expected: {exp}."); + return false; + } + if (parentType != 0 && parentType != T.Any && 0 == (parentType & Typify(state.Parent))) + { + state.Errs.Add($"${name}: invalid placement in parent {TypeName(Typify(state.Parent))}, " + + $"expected: {TypeName(parentType)}."); + return false; + } + return true; + } - // ── Node: recurse into children ────────────────────────────────────── - if (IsNode(val)) + // Validate injector arguments: returns [null|errMsg, arg0, arg1, ...]. + public static List InjectorArgs(List argTypes, object? args) { - // Non-$ keys first (deterministic order), then $ transform keys. - var allKeys = KeysOf(val); - List keys; - if (IsMap(val)) + var argslist = args as List ?? []; + int numargs = argTypes.Count; + var found = new List { null }; // found[0] = null means OK + + for (int ai = 0; ai < numargs; ai++) { - var normal = allKeys.Where(k => !k.Contains(S_DS)).ToList(); - var transform = allKeys.Where(k => k.Contains(S_DS)).ToList(); - keys = [..normal.Cast(), ..transform.Cast()]; + object? arg = GetElem(argslist, ai); + int argType = Typify(arg); + if (0 == (argTypes[ai] & argType)) + { + found[0] = $"invalid argument: {Stringify(arg, 22)} " + + $"({TypeName(argType)} at position {1 + ai}) " + + $"is not of type: {TypeName(argTypes[ai])}."; + while (found.Count <= numargs) + { + found.Add(null); + } + + return found; + } + found.Add(arg); } - else + while (found.Count <= numargs) { - keys = allKeys.Cast().ToList(); + found.Add(null); } - int nkI = 0; - while (nkI < keys.Count) + return found; + } + + // Create an inject child context to re-inject a sub-value in the right scope. + private static InjectState _InjectChild(object? child, object? store, InjectState inj) + { + InjectState cinj = inj; + if (inj.Prior != null) { - string nodekey = StrKey(keys[nkI]) ?? S_MT; - var childinj = state.Child(nkI, keys); - childinj.Mode = M_KEYPRE; + if (inj.Prior.Prior != null) + { + cinj = inj.Prior.Prior.Child(inj.Prior.KeyI, inj.Prior.Keys); + cinj.Val = child; + SetProp(cinj.Parent, inj.Prior.Key, child); + } + else + { + cinj = inj.Prior.Child(inj.KeyI, inj.Keys); + cinj.Val = child; + SetProp(cinj.Parent, inj.Key, child); + } + } + Inject(child, store, cinj); + return cinj; + } - object? preKey = _InjectStr(nodekey, store, state.DParent, childinj); + // ======================================================================== + // Transform sub-transforms + // ======================================================================== - // Read back any key-list / index modifications from the child. - nkI = childinj.KeyI; - keys = childinj.Keys; - val = childinj.Parent; + // Delete a key from its parent. + private static readonly Injector _TransformDelete = (inj, val, refStr, store) => + { + inj.SetVal(null); + return null; + }; - // TS: if (NONE !== prekey) — skip NONE/undefined (e.g. $CHILD KEYPRE). Also skip null prekey. - if (preKey != null && !ReferenceEquals(preKey, NONE)) - { - object? childval = GetProp(val, preKey); - childinj.Val = childval; - childinj.Mode = M_VAL; + // Copy value from source data (DParent) at the same key. + private static readonly Injector _TransformCopy = (inj, val, refStr, store) => + { + if (!CheckPlacement(M_VAL, "COPY", T.Any, inj)) + { + return null; + } - Inject(childval, store, childinj); + object? out_ = GetProp(inj.DParent, inj.Key); + inj.SetVal(out_); + return out_; + }; - nkI = childinj.KeyI; - keys = childinj.Keys; - val = childinj.Parent; + // Return the key of the current node from $KEY meta or $ANNO or path. + private static readonly Injector _TransformKey = (inj, val, refStr, store) => + { + if (inj.Mode != M_VAL) + { + return null; + } - childinj.Mode = M_KEYPOST; - _InjectStr(nodekey, store, state.DParent, childinj); + object? keyspec = GetProp(inj.Parent, S_BKEY); + if (keyspec != null) + { + DelProp(inj.Parent, S_BKEY); + return GetProp(inj.DParent, keyspec); + } - nkI = childinj.KeyI; - keys = childinj.Keys; - val = childinj.Parent; - } + object? anno = GetProp(inj.Parent, S_BANNO); + object? pkey = GetProp(anno, S_KEY); + return pkey ?? GetElem(inj.Path, -2); + }; - nkI++; - } - } + // Remove the $ANNO annotation from the parent node. + private static readonly Injector _TransformAnno = (inj, val, refStr, store) => + { + DelProp(inj.Parent, S_BANNO); + return null; + }; - // ── String scalar: resolve backtick references ────────────────────── - else if (0 < (T.Str & Typify(val)) && val is string strVal) + // Remove the $META annotation from the parent node. + private static readonly Injector _TransformMeta = (inj, val, refStr, store) => { - state.Mode = M_VAL; - object? injected = _InjectStr(strVal, store, state.DParent, state); - bool isSkip = injected is Dictionary sd && sd.ContainsKey("`$SKIP`"); - if (!isSkip) - state.SetVal(injected); - val = injected; - } + DelProp(inj.Parent, "`$META`"); + return null; + }; - // ── Custom modify callback ─────────────────────────────────────────── - if (state.ModifyFn != null) + // Merge a list of objects into the current node. + private static readonly Injector _TransformMerge = (inj, val, refStr, store) => { - bool isSkip = val is Dictionary sd2 && sd2.ContainsKey("`$SKIP`"); - if (!isSkip) + if (inj.Mode == M_KEYPRE) { - object? mparent = state.Parent; - object? mval = GetProp(mparent, state.Key); - state.ModifyFn(mval, state.Key, mparent, state, store); + return inj.Key; } - } - - state.Val = val; - // The top-level return is the injected value extracted from its wrapper. - return GetProp(state.Parent, S_DTOP); - } + if (inj.Mode == M_KEYPOST) + { + object? args = GetProp(inj.Parent, inj.Key); + if (args is string sa && sa == S_MT) + { + args = new List { GetProp(store, S_DTOP) }; + } + else if (!IsList(args)) + { + args = new List { args }; + } + inj.SetVal(null); // remove the $MERGE key from parent - // ======================================================================== - // Transform helpers - // ======================================================================== + if (args is not List list) + { + return inj.Key; + } - // Verify the injection mode is valid for a given transform. - public static bool CheckPlacement(int modes, string name, int parentType, InjectState state) - { - static string PlacementName(int mode) => - mode == M_VAL ? "value" : S_key; + var mergeList = new List { inj.Parent }; + mergeList.AddRange(list); + mergeList.Add(Clone(inj.Parent)); + Merge(mergeList); - if (0 == (modes & state.Mode)) - { - var expected = new List(); - foreach (int m in new[] { M_KEYPRE, M_KEYPOST, M_VAL }) - if (0 != (modes & m)) - expected.Add(PlacementName(m)); - string exp = Join(expected.Cast().ToList(), ", "); - state.Errs.Add($"${name}: invalid placement as {PlacementName(state.Mode)}, expected: {exp}."); - return false; - } - if (parentType != 0 && parentType != T.Any && 0 == (parentType & Typify(state.Parent))) - { - state.Errs.Add($"${name}: invalid placement in parent {TypeName(Typify(state.Parent))}, " + - $"expected: {TypeName(parentType)}."); - return false; - } - return true; - } + return inj.Key; + } - // Validate injector arguments: returns [null|errMsg, arg0, arg1, ...]. - public static List InjectorArgs(List argTypes, object? args) - { - var argslist = args as List ?? []; - int numargs = argTypes.Count; - var found = new List { null }; // found[0] = null means OK + return null; // VAL mode: skip + }; - for (int ai = 0; ai < numargs; ai++) + // Convert a node to a list by iterating over a source path. + private static readonly Injector _TransformEach = (inj, val, refStr, store) => { - object? arg = GetElem(argslist, ai); - int argType = Typify(arg); - if (0 == (argTypes[ai] & argType)) + if (!CheckPlacement(M_VAL, "EACH", T.List, inj)) { - found[0] = $"invalid argument: {Stringify(arg, 22)} " + - $"({TypeName(argType)} at position {1 + ai}) " + - $"is not of type: {TypeName(argTypes[ai])}."; - while (found.Count <= numargs) found.Add(null); - return found; + return null; } - found.Add(arg); - } - while (found.Count <= numargs) found.Add(null); - return found; - } - // Create an inject child context to re-inject a sub-value in the right scope. - private static InjectState _InjectChild(object? child, object? store, InjectState inj) - { - InjectState cinj = inj; - if (inj.Prior != null) - { - if (inj.Prior.Prior != null) + // Truncate keys to just the first element. + if (inj.Keys.Count > 1) { - cinj = inj.Prior.Prior.Child(inj.Prior.KeyI, inj.Prior.Keys); - cinj.Val = child; - SetProp(cinj.Parent, inj.Prior.Key, child); + inj.Keys.RemoveRange(1, inj.Keys.Count - 1); } - else + + // Get args: ['`$EACH`', 'source-path', child-template] + var parentList = inj.Parent as List ?? []; + var sliced = parentList.Count > 1 ? parentList.GetRange(1, parentList.Count - 1) : []; + var args = InjectorArgs([T.Str, T.Any], sliced); + if (args[0] != null) { - cinj = inj.Prior.Child(inj.KeyI, inj.Keys); - cinj.Val = child; - SetProp(cinj.Parent, inj.Key, child); + inj.Errs.Add("$EACH: " + args[0]); + return null; } - } - Inject(child, store, cinj); - return cinj; - } - - // ======================================================================== - // Transform sub-transforms - // ======================================================================== - - // Delete a key from its parent. - private static readonly Injector _TransformDelete = (inj, val, refStr, store) => - { - inj.SetVal(null); - return null; - }; - // Copy value from source data (DParent) at the same key. - private static readonly Injector _TransformCopy = (inj, val, refStr, store) => - { - if (!CheckPlacement(M_VAL, "COPY", T.Any, inj)) return null; - object? out_ = GetProp(inj.DParent, inj.Key); - inj.SetVal(out_); - return out_; - }; - - // Return the key of the current node from $KEY meta or $ANNO or path. - private static readonly Injector _TransformKey = (inj, val, refStr, store) => - { - if (inj.Mode != M_VAL) return null; - - object? keyspec = GetProp(inj.Parent, S_BKEY); - if (keyspec != null) - { - DelProp(inj.Parent, S_BKEY); - return GetProp(inj.DParent, keyspec); - } + string srcpath = (string)args[1]!; + object? child = args[2]; - object? anno = GetProp(inj.Parent, S_BANNO); - object? pkey = GetProp(anno, S_KEY); - if (pkey != null) return pkey; + // Source data. + object? srcstore = GetProp(store, inj.Base, store); + object? src = GetPath(srcstore, srcpath, null, inj); + int srctype = Typify(src); - return GetElem(inj.Path, -2); - }; + object? tval = null; - // Remove the $ANNO annotation from the parent node. - private static readonly Injector _TransformAnno = (inj, val, refStr, store) => - { - DelProp(inj.Parent, S_BANNO); - return null; - }; + if (0 < (T.List & srctype)) + { + var sl = (List)src!; + tval = sl.Select(_ => Clone(child)).ToList(); + } + else if (0 < (T.Map & srctype)) + { + tval = Items(src).Select(item => Merge( + new List { + Clone(child), + new Dictionary { [S_BANNO] = new Dictionary { [S_KEY] = item[0] } } + }, 1)).ToList(); + } - // Remove the $META annotation from the parent node. - private static readonly Injector _TransformMeta = (inj, val, refStr, store) => - { - DelProp(inj.Parent, "`$META`"); - return null; - }; + var rval = new List(); - // Merge a list of objects into the current node. - private static readonly Injector _TransformMerge = (inj, val, refStr, store) => - { - if (inj.Mode == M_KEYPRE) return inj.Key; + if (tval != null && Size(tval) > 0) + { + var tvalList = (List)tval; - if (inj.Mode == M_KEYPOST) - { - object? args = GetProp(inj.Parent, inj.Key); - if (args is string sa && sa == S_MT) - args = new List { GetProp(store, S_DTOP) }; - else if (!IsList(args)) - args = new List { args }; + // Source values for DParent navigation. + List srcVals = IsMap(src) ? Items(src).Select(item => item[1]).ToList() : src as List ?? []; + string ckey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; + string tkey = ckey; + object? target = GetElem(inj.Nodes, -2) ?? GetElem(inj.Nodes, -1); - inj.SetVal(null); // remove the $MERGE key from parent + // tpath = path[:-1] (all but last element, same as Go) + var tpath = inj.Path.Count > 0 + ? inj.Path.GetRange(0, inj.Path.Count - 1).Cast().ToList() + : []; - if (args is not List list) return inj.Key; + // dpath: [$TOP, ...srcpath.split('.'), $:ckey, ...] + var dpath = new List { (object?)S_DTOP }; + foreach (var p in srcpath.Split('.')) + { + dpath.Add(p); + } - var mergeList = new List { inj.Parent }; - mergeList.AddRange(list); - mergeList.Add(Clone(inj.Parent)); - Merge(mergeList); + dpath.Add("$:" + ckey); - return inj.Key; - } + // tcur: {ckey: srcVals} + object? tcur = (object?)new Dictionary { [ckey] = (object?)srcVals }; - return null; // VAL mode: skip - }; + if (tpath.Count > 1) + { + string pkey = StrKey(GetElem(inj.Path, -3)) ?? S_DTOP; + tcur = new Dictionary { [pkey] = tcur }; + dpath.Add("$:" + pkey); + } - // Convert a node to a list by iterating over a source path. - private static readonly Injector _TransformEach = (inj, val, refStr, store) => - { - if (!CheckPlacement(M_VAL, "EACH", T.List, inj)) return null; + // Build tinj child state. + var tinj = inj.Child(0, [(object?)ckey]); + tinj.Path = tpath; + tinj.Nodes = inj.Nodes.Count > 0 + ? [inj.Nodes[^1]] + : []; + tinj.Parent = tinj.Nodes.Count > 0 ? tinj.Nodes[^1] : null; - // Truncate keys to just the first element. - if (inj.Keys.Count > 1) - inj.Keys.RemoveRange(1, inj.Keys.Count - 1); + SetProp(tinj.Parent, ckey, tvalList); + tinj.Val = tvalList; + tinj.DPath = dpath; + tinj.DParent = tcur; - // Get args: ['`$EACH`', 'source-path', child-template] - var parentList = inj.Parent as List ?? []; - var sliced = parentList.Count > 1 ? parentList.GetRange(1, parentList.Count - 1) : []; - var args = InjectorArgs([T.Str, T.Any], sliced); - if (args[0] != null) - { - inj.Errs.Add("$EACH: " + args[0]); - return null; - } + Inject(tvalList, store, tinj); - string srcpath = (string)args[1]!; - object? child = args[2]; + rval = tinj.Val as List ?? rval; + } - // Source data. - object? srcstore = GetProp(store, inj.Base, store); - object? src = GetPath(srcstore, srcpath, null, inj); - int srctype = Typify(src); + // Set result on the parent target. + string ftkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; + object? ftarget = GetElem(inj.Nodes, -2) ?? GetElem(inj.Nodes, -1); + SetProp(ftarget, ftkey, rval); - object? tval = null; + return rval.Count > 0 ? rval[0] : null; + }; - if (0 < (T.List & srctype)) - { - var sl = (List)src!; - tval = sl.Select(_ => Clone(child)).ToList(); - } - else if (0 < (T.Map & srctype)) + // Convert a node to a map by packing source items. + private static readonly Injector _TransformPack = (inj, val, refStr, store) => { - tval = Items(src).Select(item => (object?)Merge( - new List { - Clone(child), - new Dictionary { [S_BANNO] = new Dictionary { [S_KEY] = item[0] } } - }, 1)).ToList(); - } - - var rval = new List(); + if (!CheckPlacement(M_KEYPRE, "PACK", T.Map, inj)) + { + return null; + } - if (tval != null && Size(tval) > 0) - { - var tvalList = (List)tval; + // Get args: [srcpath, childspec] + object? argsRaw = GetProp(inj.Parent, inj.Key); + var argsList = argsRaw as List ?? []; + var args = InjectorArgs([T.Str, T.Any], argsList); + if (args[0] != null) + { + inj.Errs.Add("$PACK: " + args[0]); + return null; + } - // Source values for DParent navigation. - List srcVals; - if (IsMap(src)) - srcVals = Items(src).Select(item => item[1]).ToList(); - else - srcVals = src as List ?? []; + string srcpath = (string)args[1]!; + object? origchildspec = args[2]; - string ckey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; - string tkey = ckey; - object? target = GetElem(inj.Nodes, -2) ?? GetElem(inj.Nodes, -1); + // Target key and node. + string tkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; + int psz = inj.Path.Count; + object? target = psz >= 2 ? inj.Nodes[psz - 2] : (psz >= 1 ? inj.Nodes[psz - 1] : null); - // tpath = path[:-1] (all but last element, same as Go) - var tpath = inj.Path.Count > 0 - ? inj.Path.GetRange(0, inj.Path.Count - 1).Cast().ToList() - : new List(); + // Source data. + object? srcstore = GetProp(store, inj.Base, store); + object? src = GetPath(srcstore, srcpath, null, inj); - // dpath: [$TOP, ...srcpath.split('.'), $:ckey, ...] - var dpath = new List { (object?)S_DTOP }; - foreach (var p in srcpath.Split('.')) - dpath.Add(p); - dpath.Add("$:" + ckey); + // Normalise src to a flat list. + if (!IsList(src)) + { + if (IsMap(src)) + { + src = Items(src).Select(item => + { + var node = item[1]; + if (IsMap(node)) + { + SetProp(node, S_BANNO, new Dictionary { [S_KEY] = item[0] }); + } - // tcur: {ckey: srcVals} - object? tcur = (object?)new Dictionary { [ckey] = (object?)srcVals }; + return node; + }).ToList(); + } + else + { + return null; + } + } - if (tpath.Count > 1) + if (src == null) { - string pkey = StrKey(GetElem(inj.Path, -3)) ?? S_DTOP; - tcur = new Dictionary { [pkey] = tcur }; - dpath.Add("$:" + pkey); + return null; } - // Build tinj child state. - var tinj = inj.Child(0, new List { (object?)ckey }); - tinj.Path = tpath; - tinj.Nodes = inj.Nodes.Count > 0 - ? new List { inj.Nodes[inj.Nodes.Count - 1] } - : new List(); - tinj.Parent = tinj.Nodes.Count > 0 ? tinj.Nodes[tinj.Nodes.Count - 1] : null; + // Extract keypath and child template. + object? keypath = GetProp(origchildspec, S_BKEY); + object? childspec = DelProp(Clone(origchildspec), S_BKEY) ?? origchildspec; + object? child = GetProp(childspec, S_BVAL, childspec); - SetProp(tinj.Parent, ckey, tvalList); - tinj.Val = tvalList; - tinj.DPath = dpath; - tinj.DParent = tcur; + var srclist = (List)src; - Inject(tvalList, store, tinj); + // Resolve output key for a source item. + string ResolveKey(object? srcItem, int idx) + { + if (keypath == null) + { + return idx.ToString(CultureInfo.InvariantCulture); + } - rval = tinj.Val as List ?? rval; - } + if (keypath is not string kpStr) + { + return ""; + } - // Set result on the parent target. - string ftkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; - object? ftarget = GetElem(inj.Nodes, -2) ?? GetElem(inj.Nodes, -1); - SetProp(ftarget, ftkey, rval); + if (kpStr.StartsWith('`')) + { + // inject keypath with srcItem as $TOP + var ks = new Dictionary(store is Dictionary sd ? sd : []) { [S_DTOP] = srcItem }; + var kr = Inject(kpStr, ks); + return kr?.ToString() ?? ""; + } + else + { + var kval = GetPath(srcItem, kpStr, null, inj); + return kval?.ToString() ?? ""; + } + } - return rval.Count > 0 ? rval[0] : null; - }; + // Build tval (output map with injected children). + var tval = new Dictionary(); - // Convert a node to a map by packing source items. - private static readonly Injector _TransformPack = (inj, val, refStr, store) => - { - if (!CheckPlacement(M_KEYPRE, "PACK", T.Map, inj)) return null; + for (int i = 0; i < srclist.Count; i++) + { + object? srcItem = srclist[i]; + string outKey = ResolveKey(srcItem, i); + if (outKey == "") + { + continue; + } - // Get args: [srcpath, childspec] - object? argsRaw = GetProp(inj.Parent, inj.Key); - var argsList = argsRaw as List ?? []; - var args = InjectorArgs([T.Str, T.Any], argsList); - if (args[0] != null) - { - inj.Errs.Add("$PACK: " + args[0]); - return null; - } + object? tchild = Clone(child); - string srcpath = (string)args[1]!; - object? origchildspec = args[2]; + // Forward $ANNO from src (for map sources annotated above). + object? anno = GetProp(srcItem, S_BANNO); + if (anno != null && IsMap(tchild)) + { + SetProp(tchild, S_BANNO, anno); + } + else if (anno == null && IsMap(tchild)) + { + DelProp(tchild, S_BANNO); + } - // Target key and node. - string tkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; - int psz = inj.Path.Count; - object? target = psz >= 2 ? inj.Nodes[psz - 2] : (psz >= 1 ? inj.Nodes[psz - 1] : null); + tval[outKey] = tchild; + } - // Source data. - object? srcstore = GetProp(store, inj.Base, store); - object? src = GetPath(srcstore, srcpath, null, inj); + var rval = new Dictionary(); - // Normalise src to a flat list. - if (!IsList(src)) - { - if (IsMap(src)) + if (tval.Count > 0) { - src = Items(src).Select(item => + // Build parallel source map for DParent navigation. + var tsrc = new Dictionary(); + for (int i = 0; i < srclist.Count; i++) { - var node = item[1]; - if (IsMap(node)) - SetProp(node, S_BANNO, new Dictionary { [S_KEY] = item[0] }); - return node; - }).ToList(); - } - else return null; - } - - if (src == null) return null; + string ok = ResolveKey(srclist[i], i); + if (ok != "") + { + tsrc[ok] = srclist[i]; + } + } - // Extract keypath and child template. - object? keypath = GetProp(origchildspec, S_BKEY); - object? childspec = DelProp(Clone(origchildspec), S_BKEY) ?? origchildspec; - object? child = GetProp(childspec, S_BVAL, childspec); + string ckey = tkey; + var tpath = inj.Path.Count > 0 + ? inj.Path.GetRange(0, inj.Path.Count - 1).Cast().ToList() + : []; - var srclist = (List)src; + var dpath = new List { (object?)S_DTOP }; + foreach (var p in srcpath.Split('.')) + { + dpath.Add(p); + } - // Resolve output key for a source item. - string ResolveKey(object? srcItem, int idx) - { - if (keypath == null) return idx.ToString(); - if (keypath is not string kpStr) return ""; - if (kpStr.StartsWith("`")) - { - // inject keypath with srcItem as $TOP - var ks = new Dictionary(store is Dictionary sd ? sd : []) { [S_DTOP] = srcItem }; - var kr = Inject(kpStr, ks); - return kr?.ToString() ?? ""; - } - else - { - var kval = GetPath(srcItem, kpStr, null, inj); - return kval?.ToString() ?? ""; - } - } + dpath.Add("$:" + ckey); - // Build tval (output map with injected children). - var tval = new Dictionary(); + object? tcur = (object?)new Dictionary { [ckey] = (object?)tsrc }; - for (int i = 0; i < srclist.Count; i++) - { - object? srcItem = srclist[i]; - string outKey = ResolveKey(srcItem, i); - if (outKey == "") continue; + if (tpath.Count > 1) + { + string pkey = StrKey(GetElem(inj.Path, -3)) ?? S_DTOP; + tcur = new Dictionary { [pkey] = tcur }; + dpath.Add("$:" + pkey); + } - object? tchild = Clone(child); + var tinj = inj.Child(0, [(object?)ckey]); + tinj.Path = tpath; + tinj.Nodes = [inj.Nodes.Count > 0 ? inj.Nodes[^1] : null]; + tinj.Parent = tinj.Nodes[0]; + tinj.Val = tval; + tinj.DPath = dpath; + tinj.DParent = tcur; - // Forward $ANNO from src (for map sources annotated above). - object? anno = GetProp(srcItem, S_BANNO); - if (anno != null && IsMap(tchild)) - SetProp(tchild, S_BANNO, anno); - else if (anno == null && IsMap(tchild)) - DelProp(tchild, S_BANNO); + Inject(tval, store, tinj); - tval[outKey] = tchild; - } + if (tinj.Val is Dictionary rv) + { + rval = rv; + } + } - var rval = new Dictionary(); + SetProp(target, tkey, rval); + return null; + }; - if (tval.Count > 0) + // Reference a value from the original spec. + private static readonly Injector _TransformRef = (inj, val, refStr, store) => { - // Build parallel source map for DParent navigation. - var tsrc = new Dictionary(); - for (int i = 0; i < srclist.Count; i++) + if (inj.Mode != M_VAL) { - string ok = ResolveKey(srclist[i], i); - if (ok != "") tsrc[ok] = srclist[i]; + return null; } - string ckey = tkey; - var tpath = inj.Path.Count > 0 - ? inj.Path.GetRange(0, inj.Path.Count - 1).Cast().ToList() - : new List(); + // Get refpath from parent list element 1. + object? refpath = GetProp(inj.Parent, 1); + inj.KeyI = inj.Keys.Count; // skip remaining keys - var dpath = new List { (object?)S_DTOP }; - foreach (var p in srcpath.Split('.')) - dpath.Add(p); - dpath.Add("$:" + ckey); - - object? tcur = (object?)new Dictionary { [ckey] = (object?)tsrc }; + // Get the original spec via $SPEC function. + object? specFn = GetProp(store, S_DSPEC); + if (specFn == null) + { + return null; + } - if (tpath.Count > 1) + object? spec = specFn is Injector si ? si(inj, null, null, store) : null; + if (spec == null) { - string pkey = StrKey(GetElem(inj.Path, -3)) ?? S_DTOP; - tcur = new Dictionary { [pkey] = tcur }; - dpath.Add("$:" + pkey); + return null; } - var tinj = inj.Child(0, new List { (object?)ckey }); - tinj.Path = tpath; - tinj.Nodes = new List { inj.Nodes.Count > 0 ? inj.Nodes[inj.Nodes.Count - 1] : null }; - tinj.Parent = tinj.Nodes[0]; - tinj.Val = tval; - tinj.DPath = dpath; - tinj.DParent = tcur; + // Build dpath from current path (skip first element "$TOP"). + var dpath = inj.Path.Count > 1 + ? inj.Path.GetRange(1, inj.Path.Count - 1).Select(p => (object?)(StrKey(p) ?? S_MT)).ToList() + : []; + var dpathInj = new InjectState + { + DPath = dpath, + DParent = GetPath(spec, dpath.Cast().ToList()) + }; + + object? refResult = GetPath(spec, refpath, null, dpathInj); - Inject(tval, store, tinj); + // Check if refResult contains sub-references. + bool hasSubRef = false; + if (IsNode(refResult)) + { + Walk(refResult, (k, v, parent2, path2) => + { + if (v is string sv && sv == "`$REF`") + { + hasSubRef = true; + } - if (tinj.Val is Dictionary rv) rval = rv; - } + return v; + }); + } - SetProp(target, tkey, rval); - return null; - }; + object? tref = Clone(refResult); + int pathLen = inj.Path.Count; - // Reference a value from the original spec. - private static readonly Injector _TransformRef = (inj, val, refStr, store) => - { - if (inj.Mode != M_VAL) return null; - - // Get refpath from parent list element 1. - object? refpath = GetProp(inj.Parent, 1); - inj.KeyI = inj.Keys.Count; // skip remaining keys - - // Get the original spec via $SPEC function. - object? specFn = GetProp(store, S_DSPEC); - if (specFn == null) return null; - object? spec = specFn is Injector si ? si(inj, null, null, store) : null; - if (spec == null) return null; - - // Build dpath from current path (skip first element "$TOP"). - var dpath = inj.Path.Count > 1 - ? inj.Path.GetRange(1, inj.Path.Count - 1).Select(p => (object?)(StrKey(p) ?? S_MT)).ToList() - : new List(); - var dpathInj = new InjectState - { - DPath = dpath, - DParent = GetPath(spec, dpath.Cast().ToList()) - }; + var cpath = pathLen > 3 + ? inj.Path.GetRange(0, pathLen - 3).Cast().ToList() + : []; + var tpath = pathLen >= 1 + ? inj.Path.GetRange(0, pathLen - 1).Cast().ToList() + : []; - object? refResult = GetPath(spec, refpath, null, dpathInj); + object? tcur = GetPath(store, cpath, null, null); + object? tvalRef = GetPath(store, tpath, null, null); - // Check if refResult contains sub-references. - bool hasSubRef = false; - if (IsNode(refResult)) - Walk(refResult, (k, v, parent2, path2) => + if (!hasSubRef || tvalRef != null) { - if (v is string sv && sv == "`$REF`") hasSubRef = true; - return v; - }); + string lastPath = tpath.Count > 0 + ? (StrKey(tpath[^1]) ?? S_DTOP) + : S_DTOP; - object? tref = Clone(refResult); - int pathLen = inj.Path.Count; + var tinj = inj.Child(0, [(object?)lastPath]); + tinj.Path = tpath; - var cpath = pathLen > 3 - ? inj.Path.GetRange(0, pathLen - 3).Cast().ToList() - : new List(); - var tpath = pathLen >= 1 - ? inj.Path.GetRange(0, pathLen - 1).Cast().ToList() - : new List(); + int nodesLen = inj.Nodes.Count; + tinj.Nodes = nodesLen > 1 + ? inj.Nodes.GetRange(0, nodesLen - 1).Cast().ToList() + : []; + tinj.Parent = nodesLen >= 2 ? inj.Nodes[nodesLen - 2] : null; + tinj.Val = tref; + tinj.DPath = cpath; + tinj.DParent = tcur; - object? tcur = GetPath(store, cpath, null, null); - object? tvalRef = GetPath(store, tpath, null, null); + Inject(tref, store, tinj); + object? rval = tinj.Val; - if (!hasSubRef || tvalRef != null) - { - string lastPath = tpath.Count > 0 - ? (StrKey(tpath[tpath.Count - 1]) ?? S_DTOP) - : S_DTOP; + inj.SetVal(rval, 2); - var tinj = inj.Child(0, new List { (object?)lastPath }); - tinj.Path = tpath; + if (IsList(GetElem(inj.Nodes, -2)) && inj.Prior != null) + { + inj.Prior.KeyI--; + } + } + else + { + // Circular self-reference with no data: delete the key from the grandparent. + inj.SetVal(null, 2); + if (IsList(GetElem(inj.Nodes, -2)) && inj.Prior != null) + { + inj.Prior.KeyI--; + } + } - int nodesLen = inj.Nodes.Count; - tinj.Nodes = nodesLen > 1 - ? inj.Nodes.GetRange(0, nodesLen - 1).Cast().ToList() - : new List(); - tinj.Parent = nodesLen >= 2 ? inj.Nodes[nodesLen - 2] : null; - tinj.Val = tref; - tinj.DPath = cpath; - tinj.DParent = tcur; + return val; + }; - Inject(tref, store, tinj); - object? rval = tinj.Val; + // Apply a named format function to a child value. + private static readonly Injector _TransformFormat = (inj, val, refStr, store) => + { + // Truncate remaining keys. + if (inj.Keys.Count > 1) + { + inj.Keys.RemoveRange(1, inj.Keys.Count - 1); + } - inj.SetVal(rval, 2); + if (inj.Mode != M_VAL) + { + return null; + } - if (IsList(GetElem(inj.Nodes, -2)) && inj.Prior != null) - inj.Prior.KeyI--; - } - else - { - // Circular self-reference with no data: delete the key from the grandparent. - inj.SetVal(null, 2); - if (IsList(GetElem(inj.Nodes, -2)) && inj.Prior != null) - inj.Prior.KeyI--; - } + object? name = GetProp(inj.Parent, 1); + object? child = GetProp(inj.Parent, 2); - return val; - }; + // Resolve child through injection using the proper DParent context. + var rcInj = _InjectChild(child, store, inj); + object? resolved = rcInj.Val; - // Apply a named format function to a child value. - private static readonly Injector _TransformFormat = (inj, val, refStr, store) => - { - // Truncate remaining keys. - if (inj.Keys.Count > 1) - inj.Keys.RemoveRange(1, inj.Keys.Count - 1); + // Target key and node. + string tkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; + int psz = inj.Path.Count; + object? target = psz >= 2 ? GetElem(inj.Nodes, -2) : GetElem(inj.Nodes, -1); - if (inj.Mode != M_VAL) return null; + // Helper: scalar to string. + static string FmtStr(object? v) + { + return v switch + { + null => "null", + bool b => b ? "true" : "false", + _ => v.ToString() ?? "null", + }; + } - object? name = GetProp(inj.Parent, 1); - object? child = GetProp(inj.Parent, 2); + // Determine formatter. + WalkApply? formatter = null; - // Resolve child through injection using the proper DParent context. - var rcInj = _InjectChild(child, store, inj); - object? resolved = rcInj.Val; + if (name is string nameStr) + { + formatter = nameStr switch + { + "upper" => (k, v, p, path) => IsNode(v) ? v : (object?)FmtStr(v).ToUpperInvariant(), + "lower" => (k, v, p, path) => IsNode(v) ? v : (object?)FmtStr(v).ToLowerInvariant(), + "string" => (k, v, p, path) => IsNode(v) ? v : (object?)FmtStr(v), + "number" => (k, v, p, path) => + { + if (IsNode(v)) + { + return v; + } - // Target key and node. - string tkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; - int psz = inj.Path.Count; - object? target = psz >= 2 ? GetElem(inj.Nodes, -2) : GetElem(inj.Nodes, -1); + if (v is long li) + { + return (object?)li; + } - // Helper: scalar to string. - static string FmtStr(object? v) => v switch - { - null => "null", - bool b => b ? "true" : "false", - _ => v.ToString() ?? "null", - }; + return v is double di ? (object?)di : v is string sv ? double.TryParse(sv, out double d) ? (object?)d : 0 : (object?)0; + } + , + "integer" => (k, v, p, path) => + { + if (IsNode(v)) + { + return v; + } - // Determine formatter. - WalkApply? formatter = null; + if (v is long li) + { + return (object?)li; + } - if (name is string nameStr) - { - formatter = nameStr switch - { - "upper" => (k, v, p, path) => IsNode(v) ? v : (object?)FmtStr(v).ToUpper(), - "lower" => (k, v, p, path) => IsNode(v) ? v : (object?)FmtStr(v).ToLower(), - "string" => (k, v, p, path) => IsNode(v) ? v : (object?)FmtStr(v), - "number" => (k, v, p, path) => - { - if (IsNode(v)) return v; - if (v is long li) return (object?)li; - if (v is double di) return (object?)di; - if (v is string sv) return double.TryParse(sv, out double d) ? (object?)d : 0; - return (object?)0; - }, - "integer" => (k, v, p, path) => - { - if (IsNode(v)) return v; - if (v is long li) return (object?)li; - if (v is double di) return (object?)(long)di; - if (v is string sv) return double.TryParse(sv, out double d) ? (object?)(long)d : (object?)0L; - return (object?)0L; - }, - "concat" => (k, v, p, path) => - { - if (k == null && IsList(v)) + return v is double di + ? (object?)(long)di + : v is string sv ? double.TryParse(sv, out double d) ? (object?)(long)d : (object?)0L : (object?)0L; + } + , + "concat" => (k, v, p, path) => { - var parts2 = (v as List)!.Select(x => IsNode(x) ? "" : FmtStr(x)); - return (object?)string.Join("", parts2); + if (k == null && IsList(v)) + { + var parts2 = (v as List)!.Select(x => IsNode(x) ? "" : FmtStr(x)); + return (object?)string.Join("", parts2); + } + return v; } - return v; - }, - "identity" => (k, v, p, path) => v, - _ => null, - }; + , + "identity" => (k, v, p, path) => v, + _ => null, + }; - if (formatter == null) + if (formatter == null) + { + inj.Errs.Add($"$FORMAT: unknown format: {nameStr}."); + SetProp(target, tkey, null); + return null; + } + } + else { - inj.Errs.Add($"$FORMAT: unknown format: {nameStr}."); + inj.Errs.Add($"$FORMAT: unknown format: {Stringify(name)}."); SetProp(target, tkey, null); return null; } - } - else - { - inj.Errs.Add($"$FORMAT: unknown format: {Stringify(name)}."); - SetProp(target, tkey, null); - return null; - } - - // Apply formatter. - object? out_; - if (!IsNode(resolved)) - out_ = formatter(null, resolved, null, []); - else if (name is string ns && ns == "concat") - out_ = formatter(null, resolved, null, []); - else - out_ = Walk(resolved, before: formatter); - - SetProp(target, tkey, out_); - return out_; - }; - - // Apply a function to a child value. - private static readonly Injector _TransformApply = (inj, val, refStr, store) => - { - if (inj.Keys.Count > 1) - inj.Keys.RemoveRange(1, inj.Keys.Count - 1); - - if (!CheckPlacement(M_VAL, "APPLY", T.List, inj)) return null; - - var parentList = inj.Parent as List ?? []; - var sliced = parentList.Count > 1 ? parentList.GetRange(1, parentList.Count - 1) : []; - var args = InjectorArgs([T.Func, T.Any], sliced); - string tkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; - object? target = GetElem(inj.Nodes, -2) ?? GetElem(inj.Nodes, -1); + // Apply formatter. + object? out_ = !IsNode(resolved) + ? formatter(null, resolved, null, []) + : name is string ns && ns == "concat" ? formatter(null, resolved, null, []) : Walk(resolved, before: formatter); + SetProp(target, tkey, out_); + return out_; + }; - if (args[0] != null) + // Apply a function to a child value. + private static readonly Injector _TransformApply = (inj, val, refStr, store) => { - inj.Errs.Add("$APPLY: " + args[0]); - SetProp(target, tkey, null); - return null; - } - - object? applyFn = args[1]; - object? child2 = args[2]; - - // Resolve child. - object? resolved = child2; - if (child2 is string cs) - resolved = _InjectStr(cs, store, inj.DParent, inj); + if (inj.Keys.Count > 1) + { + inj.Keys.RemoveRange(1, inj.Keys.Count - 1); + } - // Invoke. - object? out_ = null; - if (applyFn is Func fn1) - out_ = fn1(resolved); - else if (applyFn is Func fn3) - out_ = fn3(resolved, store, inj); - else if (applyFn is Injector aInj) - out_ = aInj(inj, resolved, null, store); + if (!CheckPlacement(M_VAL, "APPLY", T.List, inj)) + { + return null; + } - SetProp(target, tkey, out_); - return out_; - }; + var parentList = inj.Parent as List ?? []; + var sliced = parentList.Count > 1 ? parentList.GetRange(1, parentList.Count - 1) : []; + var args = InjectorArgs([T.Func, T.Any], sliced); + string tkey = StrKey(GetElem(inj.Path, -2)) ?? S_DTOP; + object? target = GetElem(inj.Nodes, -2) ?? GetElem(inj.Nodes, -1); - // ======================================================================== - // Transform - // ======================================================================== + if (args[0] != null) + { + inj.Errs.Add("$APPLY: " + args[0]); + SetProp(target, tkey, null); + return null; + } - public static object? Transform(object? data, object? spec, InjectState? injdef = null) - { - object? origspec = spec; - spec = Clone(spec); + object? applyFn = args[1]; + object? child2 = args[2]; - // Separate extra-data from extra-transforms (keys starting with $). - var extraTransforms = new Dictionary(); - var extraData = new Dictionary(); + // Resolve child. + object? resolved = child2; + if (child2 is string cs) + { + resolved = _InjectStr(cs, store, inj.DParent, inj); + } - object? extra = injdef?.Extra; - if (extra != null) - { - foreach (var kv in Items(extra)) + // Invoke. + object? out_ = null; + if (applyFn is Func fn1) { - string k = kv[0]?.ToString() ?? ""; - if (k.StartsWith(S_DS)) - extraTransforms[k] = kv[1]; - else - extraData[k] = kv[1]; + out_ = fn1(resolved); + } + else if (applyFn is Func fn3) + { + out_ = fn3(resolved, store, inj); + } + else if (applyFn is Injector aInj) + { + out_ = aInj(inj, resolved, null, store); } - } - // Merge extra data + source data (match TS: clone(data), no default map when data is null). - object? dataClone = Merge(new List - { - IsEmpty(extraData) ? null : Clone(extraData), - Clone(data), - }); - - // Build the transform store. - var store = new Dictionary - { - [S_DTOP] = dataClone, - [S_DSPEC] = (Injector)((inj2, v, r, s) => Clone(origspec)), - ["$BT"] = (Injector)((inj2, v, r, s) => (object?)S_BT), - ["$DS"] = (Injector)((inj2, v, r, s) => (object?)S_DS), - ["$WHEN"] = (Injector)((inj2, v, r, s) => (object?)DateTime.UtcNow.ToString("o")), - ["$DELETE"] = (Injector)_TransformDelete, - ["$COPY"] = (Injector)_TransformCopy, - ["$KEY"] = (Injector)_TransformKey, - ["$ANNO"] = (Injector)_TransformAnno, - ["$META"] = (Injector)_TransformMeta, - ["$MERGE"] = (Injector)_TransformMerge, - ["$MERGE0"] = (Injector)_TransformMerge, - ["$MERGE1"] = (Injector)_TransformMerge, - ["$EACH"] = (Injector)_TransformEach, - ["$PACK"] = (Injector)_TransformPack, - ["$REF"] = (Injector)_TransformRef, - ["$FORMAT"] = (Injector)_TransformFormat, - ["$APPLY"] = (Injector)_TransformApply, + SetProp(target, tkey, out_); + return out_; }; - // Merge extra transforms into the store. - foreach (var kv in extraTransforms) - store[kv.Key] = kv.Value; - // Error collection: collect=true when caller explicitly provided an errs list. - bool collect = injdef?.Errs != null; - List errs = injdef?.Errs ?? []; - store[S_DERRS] = errs; + // ======================================================================== + // Transform + // ======================================================================== - // Build an inject state with any overrides from injdef. - var injState = new InjectState + public static object? Transform(object? data, object? spec, InjectState? injdef = null) { - ModifyFn = injdef?.ModifyFn, - Extra = injdef?.Extra, - Meta = injdef?.Meta ?? new Dictionary(), - Handler = injdef?.Handler, - }; + object? origspec = spec; + spec = Clone(spec); - object? out_ = Inject(spec, store, injState); + // Separate extra-data from extra-transforms (keys starting with $). + var extraTransforms = new Dictionary(); + var extraData = new Dictionary(); - if (errs.Count > 0 && !collect) - throw new InvalidOperationException(string.Join(" | ", errs.Select(e => e?.ToString() ?? ""))); + object? extra = injdef?.Extra; + if (extra != null) + { + foreach (var kv in Items(extra)) + { + string k = kv[0]?.ToString() ?? ""; + if (k.StartsWith(S_DS, StringComparison.Ordinal)) + { + extraTransforms[k] = kv[1]; + } + else + { + extraData[k] = kv[1]; + } + } + } - return out_; - } + // Merge extra data + source data (match TS: clone(data), no default map when data is null). + object? dataClone = Merge(new List + { + IsEmpty(extraData) ? null : Clone(extraData), + Clone(data), + }); + // Build the transform store. + var store = new Dictionary + { + [S_DTOP] = dataClone, + [S_DSPEC] = (Injector)((inj2, v, r, s) => Clone(origspec)), + ["$BT"] = (Injector)((inj2, v, r, s) => (object?)S_BT), + ["$DS"] = (Injector)((inj2, v, r, s) => (object?)S_DS), + ["$WHEN"] = (Injector)((inj2, v, r, s) => (object?)DateTime.UtcNow.ToString("o")), + ["$DELETE"] = _TransformDelete, + ["$COPY"] = _TransformCopy, + ["$KEY"] = _TransformKey, + ["$ANNO"] = _TransformAnno, + ["$META"] = _TransformMeta, + ["$MERGE"] = _TransformMerge, + ["$MERGE0"] = _TransformMerge, + ["$MERGE1"] = _TransformMerge, + ["$EACH"] = _TransformEach, + ["$PACK"] = _TransformPack, + ["$REF"] = _TransformRef, + ["$FORMAT"] = _TransformFormat, + ["$APPLY"] = _TransformApply, + }; - // ======================================================================== - // VALIDATE - // ======================================================================== + // Merge extra transforms into the store. + foreach (var kv in extraTransforms) + { + store[kv.Key] = kv.Value; + } - // Build a "Expected X, but found Y: v." error message. - private static string _InvalidTypeMsg(object? path, string needtype, int vt, object? v) - { - // TS: null == v → "no value"; NONE is undefined in TS and uses the same wording. - bool absent = v == null || ReferenceEquals(v, NONE); - string vs = absent ? "no value" : Stringify(v); - string loc = Size(path) > 1 ? "field " + Pathify(path, 1) + " to be " : ""; - string found = !absent ? TypeName(vt) + S_VIZ : ""; - return "Expected " + loc + needtype + ", but found " + found + vs + S_DT; - } + // Error collection: collect=true when caller explicitly provided an errs list. + bool collect = injdef?.Errs != null; + List errs = injdef?.Errs ?? []; + store[S_DERRS] = errs; - // Require a non-empty string. - private static readonly Injector _ValidateString = (inj, val, refStr, store) => - { - bool keyExists = TryGetDataValue(inj.DParent, inj.Key, out object? out_); - int t = keyExists ? Typify(out_) : T.NoVal; + // Build an inject state with any overrides from injdef. + var injState = new InjectState + { + ModifyFn = injdef?.ModifyFn, + Extra = injdef?.Extra, + Meta = injdef?.Meta ?? [], + Handler = injdef?.Handler, + }; - if (0 == (T.Str & t)) - { - inj.Errs.Add(_InvalidTypeMsg(inj.Path, S_string, t, keyExists ? out_ : NONE)); - return NONE; - } + object? out_ = Inject(spec, store, injState); - if (S_MT == out_ as string) - { - inj.Errs.Add("Empty string at " + Pathify(inj.Path, 1)); - return NONE; + return errs.Count > 0 && !collect + ? throw new InvalidOperationException(string.Join(" | ", errs.Select(e => e?.ToString() ?? ""))) + : out_; } - return out_; - }; - - // Require a value of the named type ($NUMBER, $INTEGER, etc.). - private static readonly Injector _ValidateType = (inj, val, refStr, store) => - { - string tname = refStr != null && refStr.Length > 1 ? refStr.Substring(1).ToLower() : ""; - int idx = Array.IndexOf(TYPENAME, tname); - int typev = idx >= 0 ? (1 << (31 - idx)) : 0; - bool keyExists = TryGetDataValue(inj.DParent, inj.Key, out object? out_); - int t = keyExists ? Typify(out_) : T.NoVal; + // ======================================================================== + // VALIDATE + // ======================================================================== - if (0 == (t & typev)) + // Build a "Expected X, but found Y: v." error message. + private static string _InvalidTypeMsg(object? path, string needtype, int vt, object? v) { - inj.Errs.Add(_InvalidTypeMsg(inj.Path, tname, t, keyExists ? out_ : NONE)); - return NONE; + // TS: null == v → "no value"; NONE is undefined in TS and uses the same wording. + bool absent = v == null || ReferenceEquals(v, NONE); + string vs = absent ? "no value" : Stringify(v); + string loc = Size(path) > 1 ? "field " + Pathify(path, 1) + " to be " : ""; + string found = !absent ? TypeName(vt) + S_VIZ : ""; + return "Expected " + loc + needtype + ", but found " + found + vs + S_DT; } - return out_; - }; - - // Allow any value without type check. - private static readonly Injector _ValidateAny = (inj, val, refStr, store) => - GetProp(inj.DParent, inj.Key); - - // Validate every child of a map or list against a template. - private static readonly Injector _ValidateChild = (inj, val, refStr, store) => - { - int mode = inj.Mode; - - // Map syntax: {'`$CHILD`': childTemplate} — runs at M_KEYPRE. - if (M_KEYPRE == mode) + // Require a non-empty string. + private static readonly Injector _ValidateString = (inj, val, refStr, store) => { - object? childtm = GetProp(inj.Parent, inj.Key); + bool keyExists = TryGetDataValue(inj.DParent, inj.Key, out object? out_); + int t = keyExists ? Typify(out_) : T.NoVal; - object? pkey = GetElem(inj.Path, -2); - object? tval = GetProp(inj.DParent, pkey); - - if (tval == NONE || tval == null) - tval = new Dictionary(); - else if (!IsMap(tval)) + if (0 == (T.Str & t)) { - inj.Errs.Add(_InvalidTypeMsg(Slice(inj.Path, -1), S_object, Typify(tval), tval)); - inj.SetVal(NONE); + inj.Errs.Add(_InvalidTypeMsg(inj.Path, S_string, t, keyExists ? out_ : NONE)); return NONE; } - foreach (string ckey in KeysOf(tval)) + if (S_MT == (out_ as string)) { - SetProp(inj.Parent, ckey, Clone(childtm)); - inj.Keys.Add(ckey); + inj.Errs.Add("Empty string at " + Pathify(inj.Path, 1)); + return NONE; } - inj.SetVal(NONE); - return NONE; - } + return out_; + }; - // List syntax: ['`$CHILD`', childTemplate] — runs at M_VAL. - if (M_VAL == mode) + // Require a value of the named type ($NUMBER, $INTEGER, etc.). + private static readonly Injector _ValidateType = (inj, val, refStr, store) => { - if (!IsList(inj.Parent)) - { - inj.Errs.Add("Invalid $CHILD as value"); - return NONE; - } + string tname = refStr != null && refStr.Length > 1 ? refStr[1..].ToLowerInvariant() : ""; + int idx = Array.IndexOf(TYPENAME, tname); + int typev = idx >= 0 ? (1 << (31 - idx)) : 0; - object? childtm = GetProp(inj.Parent, 1); + bool keyExists = TryGetDataValue(inj.DParent, inj.Key, out object? out_); + int t = keyExists ? Typify(out_) : T.NoVal; - if (inj.DParent == NONE || inj.DParent == null) + if (0 == (t & typev)) { - Slice(inj.Parent, 0, 0, true); + inj.Errs.Add(_InvalidTypeMsg(inj.Path, tname, t, keyExists ? out_ : NONE)); return NONE; } - if (!IsList(inj.DParent)) - { - string msg = _InvalidTypeMsg( - Slice(inj.Path, -1), S_list, Typify(inj.DParent), inj.DParent); - inj.Errs.Add(msg); - inj.KeyI = Size(inj.Parent); - return inj.DParent; - } + return out_; + }; - var dpList = (List)inj.DParent; - foreach (var item in Items(inj.DParent)) - SetProp(inj.Parent, item[0], Clone(childtm)); - Slice(inj.Parent, 0, dpList.Count, true); - inj.KeyI = 0; + // Allow any value without type check. + private static readonly Injector _ValidateAny = (inj, val, refStr, store) => + GetProp(inj.DParent, inj.Key); - return GetProp(inj.DParent, 0); - } + // Validate every child of a map or list against a template. + private static readonly Injector _ValidateChild = (inj, val, refStr, store) => + { + int mode = inj.Mode; - return NONE; - }; + // Map syntax: {'`$CHILD`': childTemplate} — runs at M_KEYPRE. + if (M_KEYPRE == mode) + { + object? childtm = GetProp(inj.Parent, inj.Key); - // Match at least one of the provided alternatives. - private static readonly Injector _ValidateOne = (inj, val, refStr, store) => - { - if (M_VAL != inj.Mode) return null; + object? pkey = GetElem(inj.Path, -2); + object? tval = GetProp(inj.DParent, pkey); - if (!IsList(inj.Parent) || 0 != inj.KeyI) - { - inj.Errs.Add("The $ONE validator at field " + - Pathify(inj.Path, 1, 1) + - " must be the first element of an array."); - return null; - } + if (tval == NONE || tval == null) + { + tval = new Dictionary(); + } + else if (!IsMap(tval)) + { + inj.Errs.Add(_InvalidTypeMsg(Slice(inj.Path, -1), S_object, Typify(tval), tval)); + inj.SetVal(NONE); + return NONE; + } - inj.KeyI = Size(inj.Keys); + foreach (string ckey in KeysOf(tval)) + { + SetProp(inj.Parent, ckey, Clone(childtm)); + inj.Keys.Add(ckey); + } - inj.SetVal(inj.DParent, 2); - inj.Path = (List)Slice(inj.Path, -1)!; - inj.Key = StrKey(GetElem(inj.Path, -1)) ?? S_MT; + inj.SetVal(NONE); + return NONE; + } - var tvals = (List)Slice(inj.Parent, 1)!; - if (0 == Size(tvals)) - { - inj.Errs.Add("The $ONE validator at field " + - Pathify(inj.Path, 1, 1) + - " must have at least one argument."); - return null; - } + // List syntax: ['`$CHILD`', childTemplate] — runs at M_VAL. + if (M_VAL == mode) + { + if (!IsList(inj.Parent)) + { + inj.Errs.Add("Invalid $CHILD as value"); + return NONE; + } - foreach (object? tval in tvals) - { - var terrs = new List(); - var vstore = (Dictionary)Merge( - new List { new Dictionary(), store }, 1)!; - vstore[S_DTOP] = inj.DParent; + object? childtm = GetProp(inj.Parent, 1); - object? vcurrent = Validate(inj.DParent, tval, new InjectState - { - Extra = vstore, - Errs = terrs, - Meta = inj.Meta, - }); + if (inj.DParent == NONE || inj.DParent == null) + { + Slice(inj.Parent, 0, 0, true); + return NONE; + } - inj.SetVal(vcurrent, -2); + if (!IsList(inj.DParent)) + { + string msg = _InvalidTypeMsg( + Slice(inj.Path, -1), S_list, Typify(inj.DParent), inj.DParent); + inj.Errs.Add(msg); + inj.KeyI = Size(inj.Parent); + return inj.DParent; + } - if (0 == Size(terrs)) return null; - } + var dpList = (List)inj.DParent; + foreach (var item in Items(inj.DParent)) + { + SetProp(inj.Parent, item[0], Clone(childtm)); + } - // No match found. - string valdesc = R_TRANSFORM_NAME.Replace( - Join(Items(tvals, n => (object?)Stringify(n[1])), ", "), - m => m.Groups[1].Value.ToLower()); + Slice(inj.Parent, 0, dpList.Count, true); + inj.KeyI = 0; - inj.Errs.Add(_InvalidTypeMsg( - inj.Path, - (1 < Size(tvals) ? "one of " : "") + valdesc, - Typify(inj.DParent), inj.DParent)); + return GetProp(inj.DParent, 0); + } - return null; - }; + return NONE; + }; - // Match one of the provided exact values. - private static readonly Injector _ValidateExact = (inj, val, refStr, store) => - { - if (M_VAL == inj.Mode) + // Match at least one of the provided alternatives. + private static readonly Injector _ValidateOne = (inj, val, refStr, store) => { + if (M_VAL != inj.Mode) + { + return null; + } + if (!IsList(inj.Parent) || 0 != inj.KeyI) { - inj.Errs.Add("The $EXACT validator at field " + + inj.Errs.Add("The $ONE validator at field " + Pathify(inj.Path, 1, 1) + " must be the first element of an array."); return null; @@ -2419,490 +2805,655 @@ private static string _InvalidTypeMsg(object? path, string needtype, int vt, obj inj.KeyI = Size(inj.Keys); inj.SetVal(inj.DParent, 2); - inj.Path = (List)Slice(inj.Path, 0, -1)!; - inj.Key = StrKey(GetElem(inj.Path, -1)) ?? S_MT; + inj.Path = (List)Slice(inj.Path, -1)!; + inj.Key = StrKey(GetElem(inj.Path, -1)) ?? S_MT; var tvals = (List)Slice(inj.Parent, 1)!; if (0 == Size(tvals)) { - inj.Errs.Add("The $EXACT validator at field " + + inj.Errs.Add("The $ONE validator at field " + Pathify(inj.Path, 1, 1) + " must have at least one argument."); return null; } - string? currentStr = null; foreach (object? tval in tvals) { - bool exactMatch = Equals(tval, inj.DParent); - if (!exactMatch && IsNode(tval)) + var terrs = new List(); + var vstore = (Dictionary)Merge( + new List { new Dictionary(), store }, 1)!; + vstore[S_DTOP] = inj.DParent; + + object? vcurrent = Validate(inj.DParent, tval, new InjectState + { + Extra = vstore, + Errs = terrs, + Meta = inj.Meta, + }); + + inj.SetVal(vcurrent, -2); + + if (0 == Size(terrs)) { - currentStr ??= Stringify(inj.DParent); - exactMatch = Stringify(tval) == currentStr; + return null; } - if (exactMatch) return null; } + // No match found. string valdesc = R_TRANSFORM_NAME.Replace( Join(Items(tvals, n => (object?)Stringify(n[1])), ", "), - m => m.Groups[1].Value.ToLower()); + m => m.Groups[1].Value.ToLowerInvariant()); inj.Errs.Add(_InvalidTypeMsg( inj.Path, - (1 < Size(inj.Path) ? "" : "value ") + - "exactly equal to " + (1 == Size(tvals) ? "" : "one of ") + valdesc, + (1 < Size(tvals) ? "one of " : "") + valdesc, Typify(inj.DParent), inj.DParent)); - } - else - { - DelProp(inj.Parent, inj.Key); - } - return null; - }; + return null; + }; - // True if key exists on node (JSON null counts as present); sets value when present. - private static bool TryGetDataValue(object? node, object? key, out object? value) - { - value = null; - if (node == null || key == null) return false; - if (node is Dictionary map) + // Match one of the provided exact values. + private static readonly Injector _ValidateExact = (inj, val, refStr, store) => { - string k = StrKey(key) ?? S_MT; - return map.TryGetValue(k, out value); - } - if (node is List list) - { - string? ks = key?.ToString(); - if (ks != null && int.TryParse(ks, out int i)) + if (M_VAL == inj.Mode) { - if (i < 0) i = list.Count + i; - if (i >= 0 && i < list.Count) + if (!IsList(inj.Parent) || 0 != inj.KeyI) { - value = list[i]; - return true; + inj.Errs.Add("The $EXACT validator at field " + + Pathify(inj.Path, 1, 1) + + " must be the first element of an array."); + return null; } - } - return false; - } - return false; - } - // Modify callback: runs after each inject step to perform type/structure validation. - private static object? _Validation( - object? pval, object? key, object? parent, object? injObj, object? store) - { - var inj = injObj as InjectState; - if (inj == null) return null; + inj.KeyI = Size(inj.Keys); - bool isSkipVal = pval is Dictionary sd && sd.ContainsKey("`$SKIP`"); - if (isSkipVal) return null; + inj.SetVal(inj.DParent, 2); + inj.Path = (List)Slice(inj.Path, 0, -1)!; + inj.Key = StrKey(GetElem(inj.Path, -1)) ?? S_MT; - bool exact = GetProp(inj.Meta, S_BEXACT) is bool b && b; + var tvals = (List)Slice(inj.Parent, 1)!; + if (0 == Size(tvals)) + { + inj.Errs.Add("The $EXACT validator at field " + + Pathify(inj.Path, 1, 1) + + " must have at least one argument."); + return null; + } - bool cKeyExists = TryGetDataValue(inj.DParent, key, out object? cval); + string? currentStr = null; + foreach (object? tval in tvals) + { + bool exactMatch = Equals(tval, inj.DParent); + if (!exactMatch && IsNode(tval)) + { + currentStr ??= Stringify(inj.DParent); + exactMatch = Stringify(tval) == currentStr; + } + if (exactMatch) + { + return null; + } + } - // TS: if (!exact && NONE === cval) return — only skip when key is absent, not when value is JSON null. - if (!exact && !cKeyExists) return null; + string valdesc = R_TRANSFORM_NAME.Replace( + Join(Items(tvals, n => (object?)Stringify(n[1])), ", "), + m => m.Groups[1].Value.ToLowerInvariant()); - int ptype = Typify(pval); + inj.Errs.Add(_InvalidTypeMsg( + inj.Path, + (1 < Size(inj.Path) ? "" : "value ") + + "exactly equal to " + (1 == Size(tvals) ? "" : "one of ") + valdesc, + Typify(inj.DParent), inj.DParent)); + } + else + { + DelProp(inj.Parent, inj.Key); + } - // Skip if spec value still contains a $ command name. - if (0 < (T.Str & ptype) && pval is string ps && ps.Contains(S_DS)) return null; + return null; + }; - int ctype = Typify(cval); + // True if key exists on node (JSON null counts as present); sets value when present. + private static bool TryGetDataValue(object? node, object? key, out object? value) + { + value = null; + if (node == null || key == null) + { + return false; + } - // TS: ptype !== ctype && NONE !== pval — deleted spec keys read as undefined; C# GetProp gives null. - // Distinguish absent key (skip) vs JSON null present (Typify null) via TryGetDataValue on parent. - bool specKeyPresent = TryGetDataValue(parent, key, out _); + if (node is Dictionary map) + { + string k = StrKey(key) ?? S_MT; + return map.TryGetValue(k, out value); + } + if (node is List list) + { + string? ks = key?.ToString(); + if (ks != null && int.TryParse(ks, out int i)) + { + if (i < 0) + { + i = list.Count + i; + } - // Type mismatch. - if (ptype != ctype && !ReferenceEquals(pval, NONE) && specKeyPresent) - { - inj.Errs.Add(_InvalidTypeMsg(inj.Path, TypeName(ptype), ctype, cval)); - return null; + if (i >= 0 && i < list.Count) + { + value = list[i]; + return true; + } + } + return false; + } + return false; } - if (IsMap(cval)) + // Modify callback: runs after each inject step to perform type/structure validation. + private static object? _Validation( + object? pval, object? key, object? parent, object? injObj, object? store) { - if (!IsMap(pval)) + if (injObj is not InjectState inj) + { + return null; + } + + bool isSkipVal = pval is Dictionary sd && sd.ContainsKey("`$SKIP`"); + if (isSkipVal) + { + return null; + } + + bool exact = GetProp(inj.Meta, S_BEXACT) is bool b && b; + + bool cKeyExists = TryGetDataValue(inj.DParent, key, out object? cval); + + // TS: if (!exact && NONE === cval) return — only skip when key is absent, not when value is JSON null. + if (!exact && !cKeyExists) + { + return null; + } + + int ptype = Typify(pval); + + // Skip if spec value still contains a $ command name. + if (0 < (T.Str & ptype) && pval is string ps && ps.Contains(S_DS)) { - inj.Errs.Add(_InvalidTypeMsg(inj.Path, TypeName(ptype), ctype, cval)); return null; } - var ckeys = KeysOf(cval); - var pkeys = KeysOf(pval); + int ctype = Typify(cval); - object? openFlag = GetProp(pval, "`$OPEN`"); - bool isOpen = true == openFlag as bool?; + // TS: ptype !== ctype && NONE !== pval — deleted spec keys read as undefined; C# GetProp gives null. + // Distinguish absent key (skip) vs JSON null present (Typify null) via TryGetDataValue on parent. + bool specKeyPresent = TryGetDataValue(parent, key, out _); - // Empty spec map means open (accepts any keys). - if (0 < Size(pkeys) && !isOpen) + // Type mismatch. + if (ptype != ctype && !ReferenceEquals(pval, NONE) && specKeyPresent) { - var badkeys = new List(); - foreach (string ck in ckeys) - if (!HasKey(pval, ck)) badkeys.Add(ck); + inj.Errs.Add(_InvalidTypeMsg(inj.Path, TypeName(ptype), ctype, cval)); + return null; + } - if (0 < badkeys.Count) + if (IsMap(cval)) + { + if (!IsMap(pval)) { - string msg = "Unexpected keys at field " + Pathify(inj.Path, 1) + - S_VIZ + Join(badkeys.Cast().ToList()); - inj.Errs.Add(msg); + inj.Errs.Add(_InvalidTypeMsg(inj.Path, TypeName(ptype), ctype, cval)); return null; } + + var ckeys = KeysOf(cval); + var pkeys = KeysOf(pval); + + object? openFlag = GetProp(pval, "`$OPEN`"); + bool isOpen = true == (openFlag as bool?); + + // Empty spec map means open (accepts any keys). + if (0 < Size(pkeys) && !isOpen) + { + var badkeys = new List(); + foreach (string ck in ckeys) + { + if (!HasKey(pval, ck)) + { + badkeys.Add(ck); + } + } + + if (0 < badkeys.Count) + { + string msg = "Unexpected keys at field " + Pathify(inj.Path, 1) + + S_VIZ + Join(badkeys.Cast().ToList()); + inj.Errs.Add(msg); + return null; + } + } + else + { + Merge(new List { pval, cval }); + if (IsNode(pval)) + { + DelProp(pval, "`$OPEN`"); + } + } + } + else if (IsList(cval)) + { + if (!IsList(pval)) + { + inj.Errs.Add(_InvalidTypeMsg(inj.Path, TypeName(ptype), ctype, cval)); + } + } + else if (exact) + { + // Missing key (TS undefined) must not match JSON null in the spec. + object? cExact = cKeyExists ? cval : NONE; + if (!Equals(cExact, pval)) + { + string pathmsg = 1 < Size(inj.Path) + ? "at field " + Pathify(inj.Path, 1) + S_VIZ + : S_MT; + inj.Errs.Add("Value " + pathmsg + Stringify(cExact) + + " should equal " + Stringify(pval) + S_DT); + } } else { - Merge(new List { pval, cval }); - if (IsNode(pval)) DelProp(pval, "`$OPEN`"); + // Use data value as output. + SetProp(parent, key, cval); } + + return null; } - else if (IsList(cval)) - { - if (!IsList(pval)) - inj.Errs.Add(_InvalidTypeMsg(inj.Path, TypeName(ptype), ctype, cval)); - } - else if (exact) + + // Inject handler for validation: intercepts meta-path syntax. + private static readonly Injector _ValidateHandler = (inj, val, refStr, store) => { - // Missing key (TS undefined) must not match JSON null in the spec. - object? cExact = cKeyExists ? cval : NONE; - if (!Equals(cExact, pval)) + if (refStr == null) { - string pathmsg = 1 < Size(inj.Path) - ? "at field " + Pathify(inj.Path, 1) + S_VIZ - : S_MT; - inj.Errs.Add("Value " + pathmsg + Stringify(cExact) + - " should equal " + Stringify(pval) + S_DT); + return _InjectHandler(inj, val, refStr, store); } - } - else - { - // Use data value as output. - SetProp(parent, key, cval); - } - return null; - } + var m = R_META_PATH.Match(refStr); + if (m.Success) + { + if (m.Groups[2].Value == "=") + { + inj.SetVal(new List { (object?)S_BEXACT, val }); + } + else + { + inj.SetVal(val); + } - // Inject handler for validation: intercepts meta-path syntax. - private static readonly Injector _ValidateHandler = (inj, val, refStr, store) => - { - if (refStr == null) return _InjectHandler(inj, val, refStr, store); + inj.KeyI = -1; + return SKIP; + } + + return _InjectHandler(inj, val, refStr, store); + }; - var m = R_META_PATH.Match(refStr); - if (m.Success) + // Validate a data structure against a shape specification. + public static object? Validate( + object? data, + object? spec, + InjectState? injdef = null) { - if (m.Groups[2].Value == "=") - inj.SetVal(new List { (object?)S_BEXACT, val }); - else - inj.SetVal(val); + bool collect = injdef?.Errs != null; + var errs = injdef?.Errs ?? []; - inj.KeyI = -1; - return SKIP; - } + // Extra validation commands override / supplement default store. + var extraStore = new Dictionary(); + if (injdef?.Extra != null) + { + foreach (var kv in Items(injdef.Extra)) + { + extraStore[kv[0]?.ToString() ?? ""] = kv[1]; + } + } - return _InjectHandler(inj, val, refStr, store); - }; + var store = (Dictionary)Merge(new List + { + new Dictionary + { + // Null out transform-only commands so they don't fire. + ["$DELETE"] = null, + ["$COPY"] = null, + ["$KEY"] = null, + ["$META"] = null, + ["$MERGE"] = null, + ["$EACH"] = null, + ["$PACK"] = null, + + // Validation commands. + ["$STRING"] = _ValidateString, + ["$NUMBER"] = _ValidateType, + ["$INTEGER"] = _ValidateType, + ["$DECIMAL"] = _ValidateType, + ["$BOOLEAN"] = _ValidateType, + ["$NULL"] = _ValidateType, + ["$NIL"] = _ValidateType, + ["$MAP"] = _ValidateType, + ["$LIST"] = _ValidateType, + ["$FUNCTION"] = _ValidateType, + ["$INSTANCE"] = _ValidateType, + ["$ANY"] = _ValidateAny, + ["$CHILD"] = _ValidateChild, + ["$ONE"] = _ValidateOne, + ["$EXACT"] = _ValidateExact, + + [S_DERRS] = errs, + }, + // Match TS merge([..., getdef(extra, {}), ...]): empty extra is {}, not null — + // null would be treated as a scalar and wipe the validation command map. + IsEmpty(extraStore) ? [] : extraStore, + new Dictionary { [S_DERRS] = errs }, + }, 1)!; + + var meta = injdef?.Meta ?? []; + SetProp(meta, S_BEXACT, GetProp(meta, S_BEXACT) ?? false); + + // Pass errs explicitly so Transform collects (doesn't throw) internally. + object? out_ = Transform(data, spec, new InjectState + { + Extra = store, + ModifyFn = _Validation, + Handler = _ValidateHandler, + Meta = meta, + Errs = errs, + }); - // Validate a data structure against a shape specification. - public static object? Validate( - object? data, - object? spec, - InjectState? injdef = null) - { - bool collect = injdef?.Errs != null; - var errs = injdef?.Errs ?? new List(); - - // Extra validation commands override / supplement default store. - var extraStore = new Dictionary(); - if (injdef?.Extra != null) - foreach (var kv in Items(injdef.Extra)) - extraStore[kv[0]?.ToString() ?? ""] = kv[1]; - - var store = (Dictionary)Merge(new List - { - new Dictionary - { - // Null out transform-only commands so they don't fire. - ["$DELETE"] = null, - ["$COPY"] = null, - ["$KEY"] = null, - ["$META"] = null, - ["$MERGE"] = null, - ["$EACH"] = null, - ["$PACK"] = null, - - // Validation commands. - ["$STRING"] = (Injector)_ValidateString, - ["$NUMBER"] = (Injector)_ValidateType, - ["$INTEGER"] = (Injector)_ValidateType, - ["$DECIMAL"] = (Injector)_ValidateType, - ["$BOOLEAN"] = (Injector)_ValidateType, - ["$NULL"] = (Injector)_ValidateType, - ["$NIL"] = (Injector)_ValidateType, - ["$MAP"] = (Injector)_ValidateType, - ["$LIST"] = (Injector)_ValidateType, - ["$FUNCTION"] = (Injector)_ValidateType, - ["$INSTANCE"] = (Injector)_ValidateType, - ["$ANY"] = (Injector)_ValidateAny, - ["$CHILD"] = (Injector)_ValidateChild, - ["$ONE"] = (Injector)_ValidateOne, - ["$EXACT"] = (Injector)_ValidateExact, - - [S_DERRS] = errs, - }, - // Match TS merge([..., getdef(extra, {}), ...]): empty extra is {}, not null — - // null would be treated as a scalar and wipe the validation command map. - IsEmpty(extraStore) ? new Dictionary() : extraStore, - new Dictionary { [S_DERRS] = errs }, - }, 1)!; - - var meta = injdef?.Meta ?? new Dictionary(); - SetProp(meta, S_BEXACT, GetProp(meta, S_BEXACT) ?? false); - - // Pass errs explicitly so Transform collects (doesn't throw) internally. - object? out_ = Transform(data, spec, new InjectState - { - Extra = store, - ModifyFn = _Validation, - Handler = _ValidateHandler, - Meta = meta, - Errs = errs, - }); - - if (errs.Count > 0 && !collect) - throw new InvalidOperationException( - string.Join(" | ", errs.Select(e => e?.ToString() ?? ""))); - - return out_; - } + return errs.Count > 0 && !collect + ? throw new InvalidOperationException( + string.Join(" | ", errs.Select(e => e?.ToString() ?? ""))) + : out_; + } - // ======================================================================== - // SELECT - // ======================================================================== + // ======================================================================== + // SELECT + // ======================================================================== - private static double _SelectToDouble(object? v) => - v switch + private static double _SelectToDouble(object? v) { - int i => i, - long l => l, - double d => d, - float f => f, - _ => double.TryParse(v?.ToString(), System.Globalization.NumberStyles.Any, - System.Globalization.CultureInfo.InvariantCulture, out double x) - ? x : double.NaN, - }; - - // $AND: all terms must match. - private static readonly Injector _SelectAnd = (inj, val, refStr, store) => - { - if (M_KEYPRE != inj.Mode) return null; - - var terms = GetProp(inj.Parent, inj.Key) as List ?? []; - var ppath = (List)Slice(inj.Path, -1)!; - object? point = GetPath(store, ppath); - - var vstore = (Dictionary)Merge( - new List { new Dictionary(), store }, 1)!; - vstore[S_DTOP] = point; + return v switch + { + int i => i, + long l => l, + double d => d, + float f => f, + _ => double.TryParse(v?.ToString(), NumberStyles.Any, + CultureInfo.InvariantCulture, out double x) + ? x : double.NaN, + }; + } - foreach (object? term in terms) + // $AND: all terms must match. + private static readonly Injector _SelectAnd = (inj, val, refStr, store) => { - var terrs = new List(); - Validate(point, term, new InjectState { Extra = vstore, Errs = terrs, Meta = inj.Meta }); - if (0 != Size(terrs)) - inj.Errs.Add("AND:" + Pathify(ppath) + S_VIZ + Stringify(point) + - " fail:" + Stringify(terms)); - } + if (M_KEYPRE != inj.Mode) + { + return null; + } - object? gkey = GetElem(inj.Path, -2); - object? gp = GetElem(inj.Nodes, -2); - SetProp(gp, gkey, point); - return null; - }; + var terms = GetProp(inj.Parent, inj.Key) as List ?? []; + var ppath = (List)Slice(inj.Path, -1)!; + object? point = GetPath(store, ppath); - // $OR: at least one term must match. - private static readonly Injector _SelectOr = (inj, val, refStr, store) => - { - if (M_KEYPRE != inj.Mode) return null; + var vstore = (Dictionary)Merge( + new List { new Dictionary(), store }, 1)!; + vstore[S_DTOP] = point; - var terms = GetProp(inj.Parent, inj.Key) as List ?? []; - var ppath = (List)Slice(inj.Path, -1)!; - object? point = GetPath(store, ppath); + foreach (object? term in terms) + { + var terrs = new List(); + Validate(point, term, new InjectState { Extra = vstore, Errs = terrs, Meta = inj.Meta }); + if (0 != Size(terrs)) + { + inj.Errs.Add("AND:" + Pathify(ppath) + S_VIZ + Stringify(point) + + " fail:" + Stringify(terms)); + } + } - var vstore = (Dictionary)Merge( - new List { new Dictionary(), store }, 1)!; - vstore[S_DTOP] = point; + object? gkey = GetElem(inj.Path, -2); + object? gp = GetElem(inj.Nodes, -2); + SetProp(gp, gkey, point); + return null; + }; - foreach (object? term in terms) + // $OR: at least one term must match. + private static readonly Injector _SelectOr = (inj, val, refStr, store) => { - var terrs = new List(); - Validate(point, term, new InjectState { Extra = vstore, Errs = terrs, Meta = inj.Meta }); - if (0 == Size(terrs)) + if (M_KEYPRE != inj.Mode) { - object? gkey2 = GetElem(inj.Path, -2); - object? gp2 = GetElem(inj.Nodes, -2); - SetProp(gp2, gkey2, point); return null; } - } - - inj.Errs.Add("OR:" + Pathify(ppath) + S_VIZ + Stringify(point) + - " fail:" + Stringify(terms)); - return null; - }; - - // $NOT: term must NOT match. - private static readonly Injector _SelectNot = (inj, val, refStr, store) => - { - if (M_KEYPRE != inj.Mode) return null; - - object? term = GetProp(inj.Parent, inj.Key); - var ppath = (List)Slice(inj.Path, -1)!; - object? point = GetPath(store, ppath); - var vstore = (Dictionary)Merge( - new List { new Dictionary(), store }, 1)!; - vstore[S_DTOP] = point; + var terms = GetProp(inj.Parent, inj.Key) as List ?? []; + var ppath = (List)Slice(inj.Path, -1)!; + object? point = GetPath(store, ppath); - var terrs = new List(); - Validate(point, term, new InjectState { Extra = vstore, Errs = terrs, Meta = inj.Meta }); + var vstore = (Dictionary)Merge( + new List { new Dictionary(), store }, 1)!; + vstore[S_DTOP] = point; - if (0 == Size(terrs)) - inj.Errs.Add("NOT:" + Pathify(ppath) + S_VIZ + Stringify(point) + - " fail:" + Stringify(term)); + foreach (object? term in terms) + { + var terrs = new List(); + Validate(point, term, new InjectState { Extra = vstore, Errs = terrs, Meta = inj.Meta }); + if (0 == Size(terrs)) + { + object? gkey2 = GetElem(inj.Path, -2); + object? gp2 = GetElem(inj.Nodes, -2); + SetProp(gp2, gkey2, point); + return null; + } + } - object? gkey = GetElem(inj.Path, -2); - object? gp = GetElem(inj.Nodes, -2); - SetProp(gp, gkey, point); - return null; - }; + inj.Errs.Add("OR:" + Pathify(ppath) + S_VIZ + Stringify(point) + + " fail:" + Stringify(terms)); + return null; + }; - // $GT, $LT, $GTE, $LTE, $LIKE: comparison operators. - private static readonly Injector _SelectCmp = (inj, val, refStr, store) => - { - if (M_KEYPRE != inj.Mode) return NONE; + // $NOT: term must NOT match. + private static readonly Injector _SelectNot = (inj, val, refStr, store) => + { + if (M_KEYPRE != inj.Mode) + { + return null; + } - object? term = GetProp(inj.Parent, inj.Key); - object? gkey = GetElem(inj.Path, -2); - var ppath = (List)Slice(inj.Path, -1)!; - object? point = GetPath(store, ppath); + object? term = GetProp(inj.Parent, inj.Key); + var ppath = (List)Slice(inj.Path, -1)!; + object? point = GetPath(store, ppath); - bool pass = false; + var vstore = (Dictionary)Merge( + new List { new Dictionary(), store }, 1)!; + vstore[S_DTOP] = point; - double pd = _SelectToDouble(point); - double td = _SelectToDouble(term); - if (!double.IsNaN(pd) && !double.IsNaN(td)) - { - if (refStr == "$GT" && pd > td) pass = true; - else if (refStr == "$LT" && pd < td) pass = true; - else if (refStr == "$GTE" && pd >= td) pass = true; - else if (refStr == "$LTE" && pd <= td) pass = true; - } + var terrs = new List(); + Validate(point, term, new InjectState { Extra = vstore, Errs = terrs, Meta = inj.Meta }); - if (!pass && refStr == "$LIKE") - { - string pattern = term?.ToString() ?? ""; - string subject2 = Stringify(point); - try + if (0 == Size(terrs)) { - if (Regex.IsMatch(subject2, pattern)) pass = true; + inj.Errs.Add("NOT:" + Pathify(ppath) + S_VIZ + Stringify(point) + + " fail:" + Stringify(term)); } - catch { /* invalid pattern */ } - } - if (pass) - { + object? gkey = GetElem(inj.Path, -2); object? gp = GetElem(inj.Nodes, -2); SetProp(gp, gkey, point); - } - else + return null; + }; + + // $GT, $LT, $GTE, $LTE, $LIKE: comparison operators. + private static readonly Injector _SelectCmp = (inj, val, refStr, store) => { - inj.Errs.Add("CMP: " + Pathify(ppath) + S_VIZ + Stringify(point) + - " fail:" + refStr + " " + Stringify(term)); - } + if (M_KEYPRE != inj.Mode) + { + return NONE; + } - return NONE; - }; + object? term = GetProp(inj.Parent, inj.Key); + object? gkey = GetElem(inj.Path, -2); + var ppath = (List)Slice(inj.Path, -1)!; + object? point = GetPath(store, ppath); - // Select children from a collection that match a query. - public static List Select(object? children, object? query) - { - if (!IsNode(children)) return []; + bool pass = false; - List childList; - if (IsMap(children)) - { - childList = Items(children, n => + double pd = _SelectToDouble(point); + double td = _SelectToDouble(term); + if (!double.IsNaN(pd) && !double.IsNaN(td)) { - SetProp(n[1], S_DKEY, n[0]); - return (object?)n[1]; - }); - } - else - { - childList = Items(children, n => + if (refStr == "$GT" && pd > td) + { + pass = true; + } + else if (refStr == "$LT" && pd < td) + { + pass = true; + } + else if (refStr == "$GTE" && pd >= td) + { + pass = true; + } + else if (refStr == "$LTE" && pd <= td) + { + pass = true; + } + } + + if (!pass && refStr == "$LIKE") { - object? idx = n[0]; - if (idx is string s && long.TryParse(s, out long li)) - SetProp(n[1], S_DKEY, (object?)li); - else - SetProp(n[1], S_DKEY, idx); - return (object?)n[1]; - }); - } + string pattern = term?.ToString() ?? ""; + string subject2 = Stringify(point); + try + { + if (Regex.IsMatch(subject2, pattern)) + { + pass = true; + } + } + catch { /* invalid pattern */ } + } - var results = new List(); - - var selectExtra = new Dictionary - { - ["`$AND`"] = (Injector)_SelectAnd, - ["`$OR`"] = (Injector)_SelectOr, - ["`$NOT`"] = (Injector)_SelectNot, - ["$AND"] = (Injector)_SelectAnd, - ["$OR"] = (Injector)_SelectOr, - ["$NOT"] = (Injector)_SelectNot, - // JSON/test fixtures use backtick-wrapped keys (`$GT`, etc.); GetPath resolves bare $NAME. - ["`$GT`"] = (Injector)_SelectCmp, - ["`$LT`"] = (Injector)_SelectCmp, - ["`$GTE`"] = (Injector)_SelectCmp, - ["`$LTE`"] = (Injector)_SelectCmp, - ["`$LIKE`"] = (Injector)_SelectCmp, - ["$GT"] = (Injector)_SelectCmp, - ["$LT"] = (Injector)_SelectCmp, - ["$GTE"] = (Injector)_SelectCmp, - ["$LTE"] = (Injector)_SelectCmp, - ["$LIKE"] = (Injector)_SelectCmp, - }; + if (pass) + { + object? gp = GetElem(inj.Nodes, -2); + SetProp(gp, gkey, point); + } + else + { + inj.Errs.Add("CMP: " + Pathify(ppath) + S_VIZ + Stringify(point) + + " fail:" + refStr + " " + Stringify(term)); + } - var meta = new Dictionary { [S_BEXACT] = true }; - var q = Clone(query); + return NONE; + }; - Walk(q, (k, v, p, path) => + // Select children from a collection that match a query. + public static List Select(object? children, object? query) { - if (IsMap(v)) + if (!IsNode(children)) { - object? existing = GetProp(v, "`$OPEN`"); - if (existing == null) SetProp(v, "`$OPEN`", true); + return []; } - return v; - }); - foreach (object? child in childList) - { - var errs = new List(); - Validate(child, Clone(q), new InjectState + List childList = IsMap(children) + ? Items(children, n => + { + SetProp(n[1], S_DKEY, n[0]); + return n[1]; + }) + : Items(children, n => + { + object? idx = n[0]; + if (idx is string s && long.TryParse(s, out long li)) + { + SetProp(n[1], S_DKEY, (object?)li); + } + else + { + SetProp(n[1], S_DKEY, idx); + } + + return n[1]; + }); + var results = new List(); + + var selectExtra = new Dictionary + { + ["`$AND`"] = _SelectAnd, + ["`$OR`"] = _SelectOr, + ["`$NOT`"] = _SelectNot, + ["$AND"] = _SelectAnd, + ["$OR"] = _SelectOr, + ["$NOT"] = _SelectNot, + // JSON/test fixtures use backtick-wrapped keys (`$GT`, etc.); GetPath resolves bare $NAME. + ["`$GT`"] = _SelectCmp, + ["`$LT`"] = _SelectCmp, + ["`$GTE`"] = _SelectCmp, + ["`$LTE`"] = _SelectCmp, + ["`$LIKE`"] = _SelectCmp, + ["$GT"] = _SelectCmp, + ["$LT"] = _SelectCmp, + ["$GTE"] = _SelectCmp, + ["$LTE"] = _SelectCmp, + ["$LIKE"] = _SelectCmp, + }; + + var meta = new Dictionary { [S_BEXACT] = true }; + var q = Clone(query); + + Walk(q, (k, v, p, path) => { - Extra = selectExtra, - Errs = errs, - Meta = meta, + if (IsMap(v)) + { + object? existing = GetProp(v, "`$OPEN`"); + if (existing == null) + { + SetProp(v, "`$OPEN`", true); + } + } + return v; }); - if (0 == Size(errs)) results.Add(child); - } + foreach (object? child in childList) + { + var errs = new List(); + Validate(child, Clone(q), new InjectState + { + Extra = selectExtra, + Errs = errs, + Meta = meta, + }); + + if (0 == Size(errs)) + { + results.Add(child); + } + } - return results; + return results; + } + + [GeneratedRegex(@"^[-0-9]+$")] + private static partial Regex R_IntegerKeyGen(); + [GeneratedRegex(@"[.*+?^${}()|[\]\\]")] + private static partial Regex R_EscapeRegexpGen(); + [GeneratedRegex(@"^`\$REF:([0-9]+)`$")] + private static partial Regex R_CloneRefGen(); + [GeneratedRegex(@"^`(\$[A-Z]+|[^`]*)[0-9]*`$")] + private static partial Regex R_InjectionFullGen(); + [GeneratedRegex(@"`([^`]+)`")] + private static partial Regex R_InjectionPartialGen(); + [GeneratedRegex(@"\$\$")] + private static partial Regex R_DoubleDollarGen(); + [GeneratedRegex(@"^([^$]+)\$([=~])(.+)$")] + private static partial Regex R_MetaPathGen(); + [GeneratedRegex(@"`\$([A-Z]+)`")] + private static partial Regex R_TransformNameGen(); + [GeneratedRegex(@"\.")] + private static partial Regex R_DotGen(); } } diff --git a/cs/VoxgigStruct.csproj b/cs/VoxgigStruct.csproj index 8377b982..59aba3f8 100644 --- a/cs/VoxgigStruct.csproj +++ b/cs/VoxgigStruct.csproj @@ -2,12 +2,23 @@ net8.0 enable - - $(NoWarn);CS8600;CS8602;CS8604 + + true + + $(NoWarn);CS8600;CS8602;CS8604;CS1591 enable VoxgigStruct Voxgig.Struct 12 + + + true + 8.0 + Recommended + true diff --git a/cs/global.json b/cs/global.json index 1e056533..4c4c3ae5 100644 --- a/cs/global.json +++ b/cs/global.json @@ -1,7 +1,7 @@ { "sdk": { "version": "8.0.100", - "rollForward": "latestMajor", + "rollForward": "latestFeature", "allowPrerelease": false } } diff --git a/cspell.json b/cspell.json new file mode 100644 index 00000000..1c100b09 --- /dev/null +++ b/cspell.json @@ -0,0 +1,206 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en,en-GB", + "allowCompoundWords": true, + "ignorePaths": [ + "**/node_modules/**", + "**/vendor/**", + "**/dist/**", + "**/dist-test/**", + "**/target/**", + "**/build/**", + "**/.gradle/**", + "**/bin/**", + "**/obj/**", + "**/.zig-cache/**", + "**/zig-out/**", + "**/coverage/**", + "**/.git/**", + "**/*.lock", + "**/Cargo.lock", + "**/Gemfile.lock", + "**/composer.lock", + "**/gradlew*", + "build/test/**", + "**/NOTES.md", + "**/REVIEW.md", + "**/PLAN.md", + "cpp/REFACTOR_PLAN.md", + "cpp/overview/**" + ], + "dictionaries": [ + "en_US", + "en_GB", + "softwareTerms", + "filetypes", + "fonts", + "node", + "npm", + "go", + "python", + "rust", + "php", + "lua", + "csharp", + "html", + "bash", + "cpp", + "java" + ], + "words": [ + "voxgig", + "Voxgig", + "jsonic", + "perigolo", + "Rodger", + "Lovelace", + "getpath", + "setpath", + "getprop", + "setprop", + "delprop", + "getelem", + "getdef", + "isnode", + "ismap", + "islist", + "iskey", + "isempty", + "isfunc", + "keysof", + "haskey", + "typify", + "typename", + "strkey", + "pathify", + "stringify", + "jsonify", + "escre", + "escurl", + "injdef", + "nodekeys", + "nodekey", + "childinj", + "childval", + "prekey", + "valtype", + "srcpath", + "Injector", + "noval", + "MODENAME", + "KEYPRE", + "KEYPOST", + "maxdepth", + "joinurl", + "urlmode", + "extradata", + "extratransforms", + "collecterrs", + "bitfield", + "bitfields", + "nlohmann", + "Niels", + "indexmap", + "mvzr", + "monostate", + "constexpr", + "intptr", + "simdjson", + "vtables", + "vtable", + "cout", + "structlib", + "nodenext", + "rbenv", + "luarocks", + "luacheck", + "stylua", + "busted", + "luajit", + "luacov", + "rockspec", + "gofumpt", + "golangci", + "staticcheck", + "ineffassign", + "errcheck", + "govet", + "gofmt", + "gosec", + "govulncheck", + "deadcode", + "clippy", + "rustfmt", + "rustc", + "Cargo", + "RustSec", + "phpcs", + "phpcbf", + "phpstan", + "phpunit", + "psalm", + "Composer", + "Packagist", + "PHPT", + "rubocop", + "minitest", + "Gemfile", + "bundler", + "rspec", + "detekt", + "ktlint", + "checkstyle", + "spotbugs", + "Gradle", + "gradlew", + "jvm", + "Kotlin", + "mypy", + "pyproject", + "pyflakes", + "pyupgrade", + "pycodestyle", + "isort", + "bugbear", + "venv", + "pyright", + "eslint", + "Prettier", + "tsbuildinfo", + "tsconfig", + "tseslint", + "tsc", + "actionlint", + "gitleaks", + "Semgrep", + "markdownlint", + "cspell", + "shellcheck", + "osv", + "serialiser", + "deserialise", + "deserialize", + "noqa", + "nolint", + "nosec", + "Nosec", + "Roslyn", + "scoreboard", + "dkjson", + "endin", + "ijname", + "voxgigstruct", + "sarr", + "cinj", + "startin", + "injdefs", + "luassert" + ], + "ignoreRegExpList": [ + "/`\\$[A-Z]+`/", + "/\\bT_[a-z]+\\b/", + "/\\bM_[A-Z]+\\b/", + "/\\bS_[A-Za-z]+\\b/" + ] +} diff --git a/go/.golangci.yml b/go/.golangci.yml new file mode 100644 index 00000000..7a0a084d --- /dev/null +++ b/go/.golangci.yml @@ -0,0 +1,25 @@ +# golangci-lint configuration for the Go port. +# https://golangci-lint.run/ +version: "2" + +run: + timeout: 5m + +linters: + # The "standard" set: errcheck, govet, ineffassign, staticcheck, unused. + default: standard + exclusions: + rules: + # Error strings are intentionally phrased to match the canonical + # TypeScript port's wording (cross-language message parity), even where + # that conflicts with Go's lowercase-error-string convention. + - linters: [staticcheck] + text: "ST1005" + # The port deliberately uses "Yoda" comparisons (`"" != x`, `0 == idx`) + # throughout, mirroring the canonical source layout across all languages. + - linters: [staticcheck] + text: "ST1017" + +formatters: + enable: + - gofmt diff --git a/go/Makefile b/go/Makefile index e707e4ba..1a509a7d 100644 --- a/go/Makefile +++ b/go/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test clean vet fmt +.PHONY: build test lint vet fmt fmt-check clean all audit build: go build ./... @@ -6,13 +6,31 @@ build: test: go test -v ./... +# Code quality: golangci-lint (errcheck/govet/ineffassign/staticcheck/unused), +# plus a gofmt formatting check. +lint: fmt-check vet + golangci-lint run ./... + vet: go vet ./... fmt: go fmt ./... +fmt-check: + @unformatted=$$(gofmt -l .); \ + if [ -n "$$unformatted" ]; then \ + echo "gofmt: the following files are not formatted:"; \ + echo "$$unformatted"; \ + exit 1; \ + fi + clean: go clean ./... -all: fmt vet build test +all: fmt vet lint build test + +# Supply-chain + security: Go vuln DB (with reachability) + gosec SAST. +audit: + govulncheck ./... + gosec -quiet ./... diff --git a/go/README.md b/go/README.md index 9a7b118f..bf6cea22 100644 --- a/go/README.md +++ b/go/README.md @@ -278,8 +278,6 @@ voxgigstruct.Select( ```go func Jm(args ...any) map[string]any // JSON Object func Jt(args ...any) []any // JSON Tuple/Array -func Jo(args ...any) map[string]any // alias for Jm -func Ja(args ...any) []any // alias for Jt ``` ```go diff --git a/go/client_test.go b/go/client_test.go index a8a14dd6..206e9a42 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -1,8 +1,6 @@ - // RUN: go test // RUN-SOME: go test -v -run=TestStruct/getpath - package voxgigstruct_test import ( @@ -17,25 +15,24 @@ import ( const TEST_JSON_FILE = "../build/test/test.json" - func TestClient(t *testing.T) { store := make(map[string]any) - sdk, err := runner.TestSDK(nil) - if err != nil { - t.Fatalf("Failed to create SDK: %v", err) - } - runnerFunc := runner.MakeRunner(TEST_JSON_FILE, sdk) + sdk, err := runner.TestSDK(nil) + if err != nil { + t.Fatalf("Failed to create SDK: %v", err) + } + runnerFunc := runner.MakeRunner(TEST_JSON_FILE, sdk) runnerMap, err := runnerFunc("check", store) if err != nil { t.Fatalf("Failed to create runner check: %v", err) } - var spec map[string]any = runnerMap.Spec - var runset runner.RunSet = runnerMap.RunSet - var subject runner.Subject = runnerMap.Subject + spec := runnerMap.Spec + runset := runnerMap.RunSet + subject := runnerMap.Subject t.Run("client-check-basic", func(t *testing.T) { - runset(t, spec["basic"], subject) + runset(t, spec["basic"], subject) }) } diff --git a/go/testutil/client_test.go b/go/testutil/client_test.go index c4c7046d..95de4cfb 100644 --- a/go/testutil/client_test.go +++ b/go/testutil/client_test.go @@ -12,21 +12,21 @@ func TestClient(t *testing.T) { if err != nil { t.Fatalf("Failed to create SDK: %v", err) } - + // Create the runner with the SDK client runnerFunc := MakeRunner("../../build/test/test.json", sdk) runnerMap, err := runnerFunc("check", nil) if err != nil { t.Fatalf("Failed to create runner check: %v", err) } - + // Extract the spec, runset, and subject spec := runnerMap.Spec runset := runnerMap.RunSet subject := runnerMap.Subject - + // Run the client-check-basic test t.Run("client-check-basic", func(t *testing.T) { runset(t, spec["basic"], subject) }) -} \ No newline at end of file +} diff --git a/go/testutil/direct.go b/go/testutil/direct.go index 1d4b8548..e6161ab5 100644 --- a/go/testutil/direct.go +++ b/go/testutil/direct.go @@ -2,7 +2,7 @@ package runner import ( "fmt" - + voxgigstruct "github.com/voxgig/struct/go" ) @@ -10,84 +10,83 @@ import ( // Similar to the direct.ts TypeScript file, it provides a way to test validation directly func DirectTest() { var out any - var errs *voxgigstruct.ListRef[any] - + // Direct testing code ported from direct.ts - + // errs = [] // out = validate(1, '`$STRING`', undefined, errs) // console.log('OUT-A0', out, errs) /* - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(1, "`$STRING`", &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT-A0", out, errs.List) - - // errs = [] - // out = validate({ a: 1 }, { a: '`$STRING`' }, undefined, errs) - // console.log('OUT-A1', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(map[string]any{"a": 1}, map[string]any{"a": "`$STRING`"}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT-A1", out, errs.List) - - // errs = [] - // out = validate(true, ['`$ONE`', '`$STRING`', '`$NUMBER`'], undefined, errs) - // console.log('OUT-B0', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(true, []any{"`$ONE`", "`$STRING`", "`$NUMBER`"}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT-B0", out, errs.List) - - // errs = [] - // out = validate(true, ['`$ONE`', '`$STRING`'], undefined, errs) - // console.log('OUT-B1', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(true, []any{"`$ONE`", "`$STRING`"}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT-B1", out, errs.List) - - // errs = [] - // out = validate(3, ['`$EXACT`', 4], undefined, errs) - // console.log('OUT', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(3, []any{"`$EXACT`", 4}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT", out, errs.List) - - // errs = [] - // out = validate({ a: 3 }, { a: ['`$EXACT`', 4] }, undefined, errs) - // console.log('OUT', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(map[string]any{"a": 3}, map[string]any{"a": []any{"`$EXACT`", 4}}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT", out, errs.List) - - // errs = [] - // out = validate({}, { '`$EXACT`': 1 }, undefined, errs) - // console.log('OUT', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"`$EXACT`": 1}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT", out, errs.List) - - // errs = [] - // out = validate({}, { a: '`$EXACT`' }, undefined, errs) - // console.log('OUT', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"a": "`$EXACT`"}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT", out, errs.List) - - // errs = [] - // out = validate({}, { a: [1, '`$EXACT`'] }, undefined, errs) - // console.log('OUT', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"a": []any{1, "`$EXACT`"}}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT", out, errs.List) - - // errs = [] - // out = validate({}, { a: ['`$ONE`', '`$STRING`', '`$NUMBER`'] }, undefined, errs) - // console.log('OUT', out, errs) - errs = voxgigstruct.ListRefCreate[any]() - out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"a": []any{"`$ONE`", "`$STRING`", "`$NUMBER`"}}, &voxgigstruct.Injection{Errs: errs}) - fmt.Println("OUT", out, errs.List) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(1, "`$STRING`", &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT-A0", out, errs.List) + + // errs = [] + // out = validate({ a: 1 }, { a: '`$STRING`' }, undefined, errs) + // console.log('OUT-A1', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(map[string]any{"a": 1}, map[string]any{"a": "`$STRING`"}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT-A1", out, errs.List) + + // errs = [] + // out = validate(true, ['`$ONE`', '`$STRING`', '`$NUMBER`'], undefined, errs) + // console.log('OUT-B0', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(true, []any{"`$ONE`", "`$STRING`", "`$NUMBER`"}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT-B0", out, errs.List) + + // errs = [] + // out = validate(true, ['`$ONE`', '`$STRING`'], undefined, errs) + // console.log('OUT-B1', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(true, []any{"`$ONE`", "`$STRING`"}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT-B1", out, errs.List) + + // errs = [] + // out = validate(3, ['`$EXACT`', 4], undefined, errs) + // console.log('OUT', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(3, []any{"`$EXACT`", 4}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT", out, errs.List) + + // errs = [] + // out = validate({ a: 3 }, { a: ['`$EXACT`', 4] }, undefined, errs) + // console.log('OUT', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(map[string]any{"a": 3}, map[string]any{"a": []any{"`$EXACT`", 4}}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT", out, errs.List) + + // errs = [] + // out = validate({}, { '`$EXACT`': 1 }, undefined, errs) + // console.log('OUT', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"`$EXACT`": 1}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT", out, errs.List) + + // errs = [] + // out = validate({}, { a: '`$EXACT`' }, undefined, errs) + // console.log('OUT', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"a": "`$EXACT`"}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT", out, errs.List) + + // errs = [] + // out = validate({}, { a: [1, '`$EXACT`'] }, undefined, errs) + // console.log('OUT', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"a": []any{1, "`$EXACT`"}}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT", out, errs.List) + + // errs = [] + // out = validate({}, { a: ['`$ONE`', '`$STRING`', '`$NUMBER`'] }, undefined, errs) + // console.log('OUT', out, errs) + errs = voxgigstruct.ListRefCreate[any]() + out, _ = voxgigstruct.Validate(map[string]any{}, map[string]any{"a": []any{"`$ONE`", "`$STRING`", "`$NUMBER`"}}, &voxgigstruct.Injection{Errs: errs}) + fmt.Println("OUT", out, errs.List) */ - + // This is the only uncommented test from direct.ts - errs = voxgigstruct.ListRefCreate[any]() + errs := voxgigstruct.ListRefCreate[any]() out, _ = voxgigstruct.Validate( map[string]any{ // kind: undefined @@ -116,4 +115,4 @@ func DirectTest() { // Run runs the direct tests func Run() { DirectTest() -} \ No newline at end of file +} diff --git a/go/testutil/runner.go b/go/testutil/runner.go index 9c455684..3e631eaf 100644 --- a/go/testutil/runner.go +++ b/go/testutil/runner.go @@ -18,8 +18,6 @@ import ( "unicode" ) - - // Client interface defines the minimum needed to work with the runner type Client interface { Utility() Utility @@ -40,49 +38,48 @@ type StructUtility struct { Stringify func(val any, maxlen ...int) string Walk func(val any, apply voxgigstruct.WalkApply, opts ...any) any - DelProp func(parent any, key any) any - EscRe func(s string) string - EscUrl func(s string) string - Filter func(val any, check func([2]any) bool) []any - Flatten func(list any, depths ...int) any - GetDef func(val any, alt any) any - GetElem func(val any, key any, alts ...any) any - GetProp func(val any, key any, alts ...any) any - HasKey func(val any, key any) bool - IsEmpty func(val any) bool - IsFunc func(val any) bool - IsKey func(val any) bool - IsList func(val any) bool - IsMap func(val any) bool - Join func(arr []any, args ...any) string - Jsonify func(val any, flags ...map[string]any) string - KeysOf func(val any) []string - Merge func(val any, maxdepths ...int) any - Pad func(str any, args ...any) string - Pathify func(val any, from ...int) string - Select func(children any, query any) []any - SetPath func(store any, path any, val any, injdefs ...map[string]any) any - SetProp func(parent any, key any, newval any) any - Size func(val any) int - Slice func(val any, args ...any) any - StrKey func(key any) string - Transform func(data any, spec any, injdefs ...*voxgigstruct.Injection) any - Typify func(value any) int - Typename func(t int) string - Validate func(data any, spec any, injdefs ...*voxgigstruct.Injection) (any, error) + DelProp func(parent any, key any) any + EscRe func(s string) string + EscUrl func(s string) string + Filter func(val any, check func([2]any) bool) []any + Flatten func(list any, depths ...int) any + GetDef func(val any, alt any) any + GetElem func(val any, key any, alts ...any) any + GetProp func(val any, key any, alts ...any) any + HasKey func(val any, key any) bool + IsEmpty func(val any) bool + IsFunc func(val any) bool + IsKey func(val any) bool + IsList func(val any) bool + IsMap func(val any) bool + Join func(arr []any, args ...any) string + Jsonify func(val any, flags ...map[string]any) string + KeysOf func(val any) []string + Merge func(val any, maxdepths ...int) any + Pad func(str any, args ...any) string + Pathify func(val any, from ...int) string + Select func(children any, query any) []any + SetPath func(store any, path any, val any, injdefs ...map[string]any) any + SetProp func(parent any, key any, newval any) any + Size func(val any) int + Slice func(val any, args ...any) any + StrKey func(key any) string + Transform func(data any, spec any, injdefs ...*voxgigstruct.Injection) any + Typify func(value any) int + Typename func(t int) string + Validate func(data any, spec any, injdefs ...*voxgigstruct.Injection) (any, error) SKIP any DELETE any - Jo func(kv ...any) map[string]any - Ja func(v ...any) []any + Jm func(kv ...any) map[string]any + Jt func(v ...any) []any CheckPlacement func(modes int, ijname string, parentTypes int, inj *voxgigstruct.Injection) bool InjectorArgs func(argTypes []int, args []any) []any InjectChild func(child any, store any, inj *voxgigstruct.Injection) *voxgigstruct.Injection } - type Subject func(args ...any) (any, error) type RunSet func( @@ -107,21 +104,18 @@ type RunPack struct { } type TestPack struct { - Name string // Optional name field + Name string // Optional name field Client Client Subject Subject Utility Utility } - - var ( NULLMARK = "__NULL__" // Value is JSON null UNDEFMARK = "__UNDEF__" // Value is not present (thus, undefined) EXISTSMARK = "__EXISTS__" // Value exists (not undefined) ) - // MakeRunner creates a runner function that can be used to run tests func MakeRunner(testfile string, client Client) func(name string, store any) (*RunPack, error) { @@ -135,7 +129,7 @@ func MakeRunner(testfile string, client Client) func(name string, store any) (*R if err != nil { return nil, err } - + subject, err := resolveSubject(name, utility) if err != nil { return nil, err @@ -150,7 +144,7 @@ func MakeRunner(testfile string, client Client) func(name string, store any) (*R if testsubject != nil { subject = subjectify(testsubject) } - + flags = resolveFlags(flags) var testspecmap = fixJSON( @@ -161,7 +155,6 @@ func MakeRunner(testfile string, client Client) func(name string, store any) (*R testset, ok := testspecmap["set"].([]any) if !ok { panic(fmt.Sprintf("No test set in %v", name)) - return } for _, entryVal := range testset { @@ -243,20 +236,18 @@ func MakeRunner(testfile string, client Client) func(name string, store any) (*R } } - // // Runner is a convenience function that creates a runner with default settings // func Runner(name string, store any, testfile string) (*RunPack, error) { // runner := MakeRunner(testfile) // return runner(name, store) // } - func resolveSpec( name string, testfile string, ) map[string]any { - data, err := os.ReadFile(filepath.Join(".", testfile)) + data, err := os.ReadFile(filepath.Join(".", testfile)) // #nosec G304 -- fixed test-corpus path, not user input. if err != nil { panic(err) } @@ -290,7 +281,6 @@ func resolveSpec( return spec } - func resolveClients( spec map[string]any, store any, @@ -372,7 +362,6 @@ func resolveClients( return clients, nil } - func resolveSubject( name string, container any, @@ -381,7 +370,7 @@ func resolveSubject( name = uppercaseFirstLetter(name) val := reflect.ValueOf(container) - + if _, ok := container.(Utility); ok { subjectVal := val.MethodByName(name) subjectIF := subjectVal.Interface() @@ -389,7 +378,6 @@ func resolveSubject( return subject, nil } - if val.Kind() == reflect.Ptr { val = val.Elem() } @@ -409,7 +397,7 @@ func resolveSubject( fn := fieldVal.Interface() var sfn Subject - + sfn, ok := fn.(Subject) if !ok { sfn = subjectify(fn) @@ -418,7 +406,6 @@ func resolveSubject( return sfn, nil } - func resolveFlags(flags map[string]bool) map[string]bool { if nil == flags { @@ -432,7 +419,6 @@ func resolveFlags(flags map[string]bool) map[string]bool { return flags } - func resolveEntry(entryVal any, flags map[string]bool) map[string]any { entry := entryVal.(map[string]any) @@ -448,7 +434,6 @@ func resolveEntry(entryVal any, flags map[string]bool) map[string]any { return entry } - func checkResult( t *testing.T, entry map[string]any, @@ -494,11 +479,11 @@ func checkResult( structUtils, ) if err != nil { - t.Error(fmt.Sprintf("match error: %v", err)) + t.Errorf("match error: %v", err) return } if !pass { - t.Error(fmt.Sprintf("match fail: %v", err)) + t.Errorf("match fail: %v", err) return } } @@ -561,13 +546,13 @@ func handleError( } if nil == entryErr { - t.Error(fmt.Sprintf("%s\n\nENTRY: %s", testerr.Error(), structUtils.Stringify(entry))) + t.Errorf("%s\n\nENTRY: %s", testerr.Error(), structUtils.Stringify(entry)) return } boolErr, hasBoolErr := entryErr.(bool) if hasBoolErr && !boolErr { - t.Error(fmt.Sprintf("%s\n\nENTRY: %s", testerr.Error(), structUtils.Stringify(entry))) + t.Errorf("%s\n\nENTRY: %s", testerr.Error(), structUtils.Stringify(entry)) return } @@ -583,10 +568,10 @@ func handleError( matchErr, err := MatchNode(entryErr, errStr, structUtils) - if err != nil { - t.Error(fmt.Sprintf("match error: %v", err)) - return - } + if err != nil { + t.Errorf("match error: %v", err) + return + } if boolErr || matchErr { if entry["match"] != nil { @@ -603,20 +588,20 @@ func handleError( ) if !matchErr { - t.Error(fmt.Sprintf("match failed: %v", matchErr)) + t.Errorf("match failed: %v", matchErr) } if nil != err { - t.Error(fmt.Sprintf("match failed: %v", err)) + t.Errorf("match failed: %v", err) } } } else { // If we didn't match, then fail with an error message. - t.Error(fmt.Sprintf("ERROR MATCH: [%s] <=> [%s]", + t.Errorf("ERROR MATCH: [%s] <=> [%s]", structUtils.Stringify(entryErr), errStr, - )) + ) } } @@ -693,7 +678,6 @@ func resolveTestPack( return testpack, err } - func MatchNode( check any, base any, @@ -786,7 +770,7 @@ func subjectify(fn any) Subject { if ok { return sfn } - + fnType := v.Type() return func(args ...any) (any, error) { @@ -850,7 +834,6 @@ func subjectify(fn any) Subject { } } - func fixJSON(data any, flags map[string]bool) any { // Ensure flags is initialized if flags == nil { @@ -929,7 +912,6 @@ func fixJSON(data any, flags map[string]bool) any { } } - func NullModifier( val any, key any, @@ -984,7 +966,6 @@ func fdti(data any, indent string) string { return result } - func ToJSONString(data any) string { jsonBytes, err := json.Marshal(data) if err != nil { @@ -993,12 +974,11 @@ func ToJSONString(data any) string { return string(jsonBytes) } - func uppercaseFirstLetter(s string) string { if len(s) == 0 { return s } - + runes := []rune(s) runes[0] = unicode.ToUpper(runes[0]) return string(runes) diff --git a/go/testutil/sdk.go b/go/testutil/sdk.go index 968b2cbf..0cd6fb59 100644 --- a/go/testutil/sdk.go +++ b/go/testutil/sdk.go @@ -2,7 +2,7 @@ package runner import ( "fmt" - + voxgigstruct "github.com/voxgig/struct/go" ) @@ -64,7 +64,7 @@ func NewSDK(opts map[string]any) *SDK { sdk := &SDK{ opts: opts, } - + // Create the StructUtility structUtil := &StructUtility{ IsNode: voxgigstruct.IsNode, @@ -76,48 +76,48 @@ func NewSDK(opts map[string]any) *SDK { Stringify: voxgigstruct.Stringify, Walk: voxgigstruct.Walk, - DelProp: voxgigstruct.DelProp, - EscRe: voxgigstruct.EscRe, - EscUrl: voxgigstruct.EscUrl, - Filter: voxgigstruct.Filter, - Flatten: voxgigstruct.Flatten, - GetDef: voxgigstruct.GetDef, - GetElem: voxgigstruct.GetElem, - GetProp: voxgigstruct.GetProp, - HasKey: voxgigstruct.HasKey, - IsEmpty: voxgigstruct.IsEmpty, - IsFunc: voxgigstruct.IsFunc, - IsKey: voxgigstruct.IsKey, - IsList: voxgigstruct.IsList, - IsMap: voxgigstruct.IsMap, - Join: voxgigstruct.Join, - Jsonify: voxgigstruct.Jsonify, - KeysOf: voxgigstruct.KeysOf, - Merge: voxgigstruct.Merge, - Pad: voxgigstruct.Pad, - Pathify: voxgigstruct.Pathify, - Select: voxgigstruct.Select, - SetPath: voxgigstruct.SetPath, - SetProp: voxgigstruct.SetProp, - Size: voxgigstruct.Size, - Slice: voxgigstruct.Slice, - StrKey: voxgigstruct.StrKey, - Transform: voxgigstruct.Transform, - Typify: voxgigstruct.Typify, - Typename: voxgigstruct.Typename, - Validate: voxgigstruct.Validate, + DelProp: voxgigstruct.DelProp, + EscRe: voxgigstruct.EscRe, + EscUrl: voxgigstruct.EscUrl, + Filter: voxgigstruct.Filter, + Flatten: voxgigstruct.Flatten, + GetDef: voxgigstruct.GetDef, + GetElem: voxgigstruct.GetElem, + GetProp: voxgigstruct.GetProp, + HasKey: voxgigstruct.HasKey, + IsEmpty: voxgigstruct.IsEmpty, + IsFunc: voxgigstruct.IsFunc, + IsKey: voxgigstruct.IsKey, + IsList: voxgigstruct.IsList, + IsMap: voxgigstruct.IsMap, + Join: voxgigstruct.Join, + Jsonify: voxgigstruct.Jsonify, + KeysOf: voxgigstruct.KeysOf, + Merge: voxgigstruct.Merge, + Pad: voxgigstruct.Pad, + Pathify: voxgigstruct.Pathify, + Select: voxgigstruct.Select, + SetPath: voxgigstruct.SetPath, + SetProp: voxgigstruct.SetProp, + Size: voxgigstruct.Size, + Slice: voxgigstruct.Slice, + StrKey: voxgigstruct.StrKey, + Transform: voxgigstruct.Transform, + Typify: voxgigstruct.Typify, + Typename: voxgigstruct.Typename, + Validate: voxgigstruct.Validate, SKIP: voxgigstruct.SKIP, DELETE: voxgigstruct.DELETE, - Jo: voxgigstruct.Jo, - Ja: voxgigstruct.Ja, + Jm: voxgigstruct.Jm, + Jt: voxgigstruct.Jt, CheckPlacement: voxgigstruct.CheckPlacement, InjectorArgs: voxgigstruct.InjectorArgs, InjectChild: voxgigstruct.InjectChild, } - + // Create the utility sdk.utility = &SDKUtility{ sdk: sdk, diff --git a/go/voxgigstruct.go b/go/voxgigstruct.go index de9af10d..a15423b1 100644 --- a/go/voxgigstruct.go +++ b/go/voxgigstruct.go @@ -135,8 +135,8 @@ const ( T_map = 1 << 13 T_instance = 1 << 12 // 4 bits reserved - T_scalar = 1 << 7 - T_node = 1 << 6 + T_scalar = 1 << 7 + T_node = 1 << 6 ) // TYPENAME maps bit position (via leading zeros count) to type name string. @@ -511,7 +511,7 @@ func Typename(t int) string { if t <= 0 { return S_any } - idx := bits.LeadingZeros32(uint32(t)) + idx := bits.LeadingZeros32(uint32(t)) // #nosec G115 -- t is a small type bitcode (well under 2^31). if idx < len(TYPENAME) && TYPENAME[idx] != "" { return TYPENAME[idx] } @@ -862,20 +862,18 @@ func KeysOf(val any) []string { return make([]string, 0) } - // Value of property with name key in node val is defined. func HasKey(val any, key any) bool { return nil != GetProp(val, key) } - // List the sorted keys of a map or list as an array of tuples of the form [key, value]. func Items(val any) [][2]any { if IsMap(val) { m := val.(map[string]any) out := make([][2]any, 0, len(m)) - keys := KeysOf(val) + keys := KeysOf(val) // keys := make([]string, 0, len(m)) // for k := range m { // keys = append(keys, k) @@ -899,7 +897,7 @@ func Items(val any) [][2]any { return out } - return make([][2]any, 0, 0) + return make([][2]any, 0) } // List items with an optional apply callback that maps over each [key, value] tuple. @@ -959,7 +957,6 @@ func Filter(val any, check func([2]any) bool) []any { return out } - // Escape regular expression. func EscRe(s string) string { if s == "" { @@ -984,7 +981,7 @@ var ( func JoinUrl(parts []any) string { var filtered []string for _, p := range parts { - if "" != p && nil != p { + if p != "" && p != nil { ps, ok := p.(string) if !ok { ps = Stringify(p) @@ -1016,7 +1013,6 @@ func JoinUrl(parts []any) string { return strings.Join(finalParts, "/") } - // Concatenate string array elements, merging separator chars as needed. // Optional args: sep (string, default ","), url (bool, default false). func Join(arr []any, args ...any) string { @@ -1037,7 +1033,7 @@ func Join(arr []any, args ...any) string { } var sepre string - if 1 == len(sep) { + if len(sep) == 1 { sepre = EscRe(sep) } @@ -1069,7 +1065,7 @@ func Join(arr []any, args ...any) string { reLeading := regexp.MustCompile(`^` + sepre + `+`) reInternal := regexp.MustCompile(`([^` + sepre + `])` + sepre + `+([^` + sepre + `])`) - if urlMode && 0 == idx { + if urlMode && idx == 0 { s = reTrailing.ReplaceAllString(s, S_MT) } else { if 0 < idx { @@ -1092,7 +1088,6 @@ func Join(arr []any, args ...any) string { return strings.Join(parts, sep) } - // Output JSON in a "standard" format, with 2 space indents, each property on a new line, // and spaces after {[: and before ]}. Any "weird" values (NaN, etc) are output as null. // In general, the behavior of JavaScript's JSON.stringify(val,null,2) is followed. @@ -1339,9 +1334,9 @@ func CloneFlags(val any, flags map[string]bool) any { } } -// Define a JSON Object from alternating key-value arguments. -// jo("a", 1, "b", 2) => {"a": 1, "b": 2} -func Jo(kv ...any) map[string]any { +// Jm defines a JSON object from alternating key/value arguments. +// Jm("a", 1, "b", 2) => {"a": 1, "b": 2} +func Jm(kv ...any) map[string]any { o := make(map[string]any) kvsize := len(kv) for i := 0; i < kvsize; i += 2 { @@ -1355,9 +1350,9 @@ func Jo(kv ...any) map[string]any { return o } -// Define a JSON Array from arguments. -// ja(1, "x", true) => [1, "x", true] -func Ja(v ...any) []any { +// Jt defines a JSON array (tuple) from positional arguments. +// Jt(1, "x", true) => [1, "x", true] +func Jt(v ...any) []any { a := make([]any, len(v)) for i := 0; i < len(v); i++ { a[i] = GetProp(v, i) @@ -1383,8 +1378,6 @@ func DelProp(parent any, key any) any { if err != nil { return parent } - ki = int(math.Floor(float64(ki))) - if lr, isLR := parent.(*ListRef[any]); isLR { psize := len(lr.List) if 0 <= ki && ki < psize { @@ -1559,7 +1552,7 @@ func Walk( opts ...any, ) any { var after WalkApply - var maxdepth int = 32 + maxdepth := 32 if len(opts) > 0 { if opts[0] != nil { @@ -1596,7 +1589,6 @@ func Walk( return _walkDescend(val, apply, nil, maxdepth, nil, nil, nil, pool) } - // WalkDescend performs a post-order walk from an arbitrary start point // (with explicit key, parent, and path). Unlike Walk it does not share a // pool across recursive calls: it allocates per-call path arrays. Intended @@ -1611,7 +1603,6 @@ func WalkDescend( return _walkDescendAlloc(val, nil, apply, 32, key, parent, path) } - // _walkDescend is the pool-threaded core used by Walk. The `path` slice it // hands to callbacks is reused across sibling visits: each recursive call // reuses pool[depth+1] (growing the pool on demand), writes its own key in @@ -1685,7 +1676,6 @@ func _walkDescend( return out } - // _walkDescendAlloc is the legacy per-call-allocating descent used by // WalkDescend, preserved for callers that enter the recursion at an // arbitrary point without a shared pool. @@ -1748,7 +1738,7 @@ func Merge(val any, maxdepths ...int) any { } } - var out any = nil + var out any if !IsList(val) { return val @@ -2286,11 +2276,6 @@ func Inject( // Perform the val mode injection on the child value. Inject(childval, store, childinj) - // The injection may modify child processing. - nkI = childinj.KeyI - nodekeys = childinj.Keys.List - val = childinj.Parent - // Perform the key:post mode injection on the child key. childinj.Mode = M_KEYPOST _injectStr(nodekey, store, childinj) @@ -2485,7 +2470,7 @@ var Transform_MERGE Injector = func( } // Remove the $MERGE command from a parent map. - DelProp(inj.Parent, inj.Key) + DelProp(inj.Parent, inj.Key) list, ok := _asList(args) if !ok { @@ -2507,7 +2492,6 @@ var Transform_MERGE Injector = func( return nil } - // Convert a node to a list. // Format: ['`$EACH`', '`source-path-of-node`', child-template] var Transform_EACH Injector = func( @@ -2609,9 +2593,7 @@ var Transform_EACH Injector = func( copy(tpath, inj.Path.List[:len(inj.Path.List)-1]) dpath := []string{S_DTOP} - for _, p := range strings.Split(srcpath, S_DT) { - dpath = append(dpath, p) - } + dpath = append(dpath, strings.Split(srcpath, S_DT)...) dpath = append(dpath, "$:"+ckey) // Parent structure. @@ -2653,7 +2635,6 @@ var Transform_EACH Injector = func( return nil } - // transform_PACK => `$PACK` var Transform_PACK Injector = func( inj *Injection, @@ -2797,9 +2778,7 @@ var Transform_PACK Injector = func( } dpath := []string{S_DTOP} - for _, p := range strings.Split(srcpath, S_DT) { - dpath = append(dpath, p) - } + dpath = append(dpath, strings.Split(srcpath, S_DT)...) dpath = append(dpath, "$:"+ckey) tcur := map[string]any{ckey: tsrc} @@ -3026,7 +3005,6 @@ var Transform_APPLY Injector = func( return out } - // transform_FORMAT => `$FORMAT` // injectChild resolves a child value via injection, going up the injection chain // to get the correct data context. @@ -3211,8 +3189,7 @@ var Transform_FORMAT Injector = func( return nil } - var out any - out = Walk(resolved, formatter) + out := Walk(resolved, formatter) // Set on parent output if target != nil { @@ -3222,7 +3199,6 @@ var Transform_FORMAT Injector = func( return out } - // --------------------------------------------------------------------- // Transform function: top-level @@ -3278,10 +3254,6 @@ func TransformModifyHandler( } } - if extraData == nil { - extraData = map[string]any{} - } - var dataClone any if data == nil { dataClone = nil @@ -3296,10 +3268,10 @@ func TransformModifyHandler( _ = origspec store := map[string]any{ - S_DTOP: dataClone, + S_DTOP: dataClone, S_DSPEC: func() any { return origspec }, - "$BT": func() any { return S_BT }, - "$DS": func() any { return S_DS }, + "$BT": func() any { return S_BT }, + "$DS": func() any { return S_DS }, "$WHEN": func() any { return time.Now().UTC().Format(time.RFC3339) }, @@ -3371,10 +3343,7 @@ func transformModifyCore( } } - // Create empty maps if nil - if extraData == nil { - extraData = map[string]any{} - } + // Create empty map if nil if data == nil { data = map[string]any{} } @@ -3487,42 +3456,6 @@ var validate_STRING Injector = func( return out } -var validate_NUMBER Injector = func( - inj *Injection, - _val any, - ref *string, - store any, -) any { - out := GetProp(inj.Dparent, inj.Key) - - t := Typify(out) - if 0 == (T_number & t) { - msg := _invalidTypeMsg(inj.Path.List, S_number, Typename(t), out) - inj.Errs.Append(msg) - return nil - } - - return out -} - -var validate_BOOLEAN Injector = func( - inj *Injection, - _val any, - ref *string, - store any, -) any { - out := GetProp(inj.Dparent, inj.Key) - - t := Typify(out) - if 0 == (T_boolean & t) { - msg := _invalidTypeMsg(inj.Path.List, S_boolean, Typename(t), out) - inj.Errs.Append(msg) - return nil - } - - return out -} - var validate_OBJECT Injector = func( inj *Injection, _val any, @@ -3537,7 +3470,7 @@ var validate_OBJECT Injector = func( msg := _invalidTypeMsg(inj.Path.List, S_object, Typename(t), out) inj.Errs.Append(msg) - return nil + return nil } return out @@ -3561,24 +3494,6 @@ var validate_ARRAY Injector = func( return out } -var validate_FUNCTION Injector = func( - inj *Injection, - _val any, - ref *string, - store any, -) any { - out := GetProp(inj.Dparent, inj.Key) - - t := Typify(out) - if 0 == (T_function & t) { - msg := _invalidTypeMsg(inj.Path.List, S_function, Typename(t), out) - inj.Errs.Append(msg) - return nil - } - - return out -} - var validate_ANY Injector = func( inj *Injection, _val any, @@ -3771,7 +3686,7 @@ func init_validate_ONE() { // Clean up structure by replacing [$ONE, ...] with current value SetProp(grandparent, grandkey, inj.Dparent) - inj.Parent = inj.Dparent + inj.Parent = inj.Dparent // Adjust the path inj.Path.List = inj.Path.List[:len(inj.Path.List)-1] @@ -3877,7 +3792,7 @@ func init_validate_EXACT() { // Clean up structure by replacing [$EXACT, ...] with current value SetProp(grandparent, grandkey, inj.Dparent) - inj.Parent = inj.Dparent + inj.Parent = inj.Dparent // Adjust the path inj.Path.List = inj.Path.List[:len(inj.Path.List)-1] @@ -3897,21 +3812,21 @@ func init_validate_EXACT() { // See if we can find an exact value match var currentStr *string for _, tval := range tvals { - exactMatch := false - - if !exactMatch { - // Unwrap ListRefs for comparison since data and spec may have - // different wrapping levels. - unwrapFlags := map[string]bool{"unwrap": true} - utval := CloneFlags(tval, unwrapFlags) - ucurrent := CloneFlags(inj.Dparent, unwrapFlags) - exactMatch = reflect.DeepEqual(utval, ucurrent) - } - + exactMatch := false + + if !exactMatch { + // Unwrap ListRefs for comparison since data and spec may have + // different wrapping levels. + unwrapFlags := map[string]bool{"unwrap": true} + utval := CloneFlags(tval, unwrapFlags) + ucurrent := CloneFlags(inj.Dparent, unwrapFlags) + exactMatch = reflect.DeepEqual(utval, ucurrent) + } + if !exactMatch && IsNode(tval) { if nil == currentStr { tmpstr := Stringify(inj.Dparent) - currentStr = &tmpstr + currentStr = &tmpstr } tvalStr := Stringify(tval) exactMatch = tvalStr == *currentStr @@ -3991,7 +3906,7 @@ func makeValidation(exact bool) Modify { ptype := Typify(pval) // Delete any special commands remaining. - if 0 < (T_string & ptype) && pval != nil { + if 0 < (T_string&ptype) && pval != nil { if strVal, ok := pval.(string); ok && strings.Contains(strVal, S_DS) { return } @@ -4076,8 +3991,6 @@ func makeValidation(exact bool) Modify { // Spec value was a default, copy over data SetProp(parent, key, cval) } - - return } } @@ -4091,7 +4004,7 @@ var _validatehandler Injector = func( ref *string, store any, ) any { - out := val + var out any refStr := "" if ref != nil { @@ -4135,7 +4048,6 @@ func Validate( errs = ListRefCreate[any]() } - // Initialize validate_ONE if not already initialized. // This avoids a circular reference error, validate_ONE calls Validate. if validate_ONE == nil { @@ -4151,7 +4063,7 @@ func Validate( store := map[string]any{ // Remove the transform commands "$DELETE": nil, - "$COPY": nil, + "$COPY": nil, "$KEY": nil, "$META": nil, "$MERGE": nil, @@ -4182,10 +4094,8 @@ func Validate( } // Add any extra validation commands - if extra != nil { - for k, fn := range extra { - store[k] = fn - } + for k, fn := range extra { + store[k] = fn } // A special top level value to collect errors @@ -4215,7 +4125,7 @@ func Validate( // Run the transformation with validation and _validatehandler out := TransformModifyHandler(data, spec, store, validationFn, _validatehandler, errs, meta) - // Generate an error if we collected any errors and the caller didn't provide + // Generate an error if we collected any errors and the caller didn't provide // their own error collection var err error generr := 0 < len(errs.List) && collecterrs == nil @@ -4235,7 +4145,6 @@ func Validate( return out, err } - // Mode names for injection modes. var MODENAME = map[int]string{ M_VAL: "val", @@ -4296,7 +4205,6 @@ func InjectorArgs(argTypes []int, args []any) []any { return found } - // Select helpers - internal injectors for query matching. var select_AND Injector = func( @@ -4468,7 +4376,6 @@ var select_CMP Injector = func( return nil } - // Internal exact-mode validation for Select. // Like Validate but uses exact scalar comparison. func validateCollectExact( @@ -4520,10 +4427,8 @@ func validateCollectExact( "$EXACT": validate_EXACT, } - if extra != nil { - for k, fn := range extra { - store[k] = fn - } + for k, fn := range extra { + store[k] = fn } store["$ERRS"] = errs @@ -4532,7 +4437,6 @@ func validateCollectExact( TransformModifyHandler(data, spec, store, makeValidation(true), _validatehandler, errs, meta) } - // Select children from a node that match a query. // Uses validate internally with query operators ($AND, $OR, $NOT, // $GT, $LT, $GTE, $LTE, $LIKE). @@ -4604,7 +4508,6 @@ func Select(children any, query any) []any { return results } - // Internal utilities // ================== @@ -4618,25 +4521,14 @@ func ListRefCreate[T any]() *ListRef[T] { } } - func (l *ListRef[T]) Append(elem T) { l.List = append(l.List, elem) } - func (l *ListRef[T]) Prepend(elem T) { l.List = append([]T{elem}, l.List...) } -func _join(vals []any, sep string) string { - strVals := make([]string, len(vals)) - for i, v := range vals { - strVals[i] = fmt.Sprint(v) - } - return strings.Join(strVals, sep) -} - - func _invalidTypeMsg(path []string, needtype string, vt string, v any, whence ...string) string { vs := "no value" if v != nil { @@ -4664,14 +4556,6 @@ func _invalidTypeMsg(path []string, needtype string, vt string, v any, whence .. return message + "." } -func _getType(v any) string { - if nil == v { - return "nil" - } - return reflect.TypeOf(v).String() -} - - // StrKey converts different types of keys to string representation. // String keys are returned as is. // Number keys are converted to strings. @@ -4709,7 +4593,6 @@ func StrKey(key any) string { } } - func _resolveStrings(input []any) []string { var result []string @@ -4724,7 +4607,6 @@ func _resolveStrings(input []any) []string { return result } - // Extract a bare []any from either a []any or a *ListRef[any]. // Recursively unwrap *ListRef[any] to []any for JSON marshaling. func _unwrapListRefs(val any) any { @@ -4769,7 +4651,6 @@ func _asList(val any) ([]any, bool) { return nil, false } - func _listify(src any) []any { if lr, ok := src.(*ListRef[any]); ok { return lr.List @@ -4797,7 +4678,6 @@ func _listify(src any) []any { return nil } - // toFloat64 helps unify numeric types for floor conversion. func _toFloat64(val any) (float64, error) { switch n := val.(type) { @@ -4831,12 +4711,11 @@ func _toFloat64(val any) (float64, error) { } } - // _parseInt is a helper to convert a string to int safely. func _parseInt(s string) (int, error) { // We'll do a very simple parse: var x int - var sign int = 1 + sign := 1 for i, c := range s { if c == '-' && i == 0 { sign = -1 @@ -4850,15 +4729,12 @@ func _parseInt(s string) (int, error) { return x * sign, nil } - type ParseIntError struct{ input string } - func (e *ParseIntError) Error() string { return "cannot parse int from: " + e.input } - func _makeArrayType(values []any, target any) any { targetElem := reflect.TypeOf(target).Elem() out := reflect.MakeSlice(reflect.SliceOf(targetElem), len(values), len(values)) @@ -4875,7 +4751,6 @@ func _makeArrayType(values []any, target any) any { return out.Interface() } - func _stringifyValue(v any) string { switch vv := v.(type) { case string: @@ -4886,97 +4761,3 @@ func _stringifyValue(v any) string { return Stringify(v) } } - - - - -// DEBUG - -func fdt(data any) string { - return fdti(data, "") -} - -func fdti(data any, indent string) string { - result := "" - - if data == nil { - return indent + "nil\n" - } - - // Get a pointer for memory address - memoryAddr := "0x???" - val := reflect.ValueOf(data) - - // For non-pointer types that are addressable, get their pointer - if val.Kind() != reflect.Ptr && val.CanAddr() { - ptr := val.Addr() - memoryAddr = fmt.Sprintf("0x%x", ptr.Pointer()) - } else if val.Kind() == reflect.Ptr { - // For pointer types, use the pointer value directly - memoryAddr = fmt.Sprintf("0x%x", val.Pointer()) - } else if val.Kind() == reflect.Map || val.Kind() == reflect.Slice { - // For maps and slices, use the pointer to internal data - memoryAddr = fmt.Sprintf("0x%x", val.Pointer()) - } - - switch v := data.(type) { - case map[string]any: - result += indent + fmt.Sprintf("{ @%s\n", memoryAddr) - for key, value := range v { - result += fmt.Sprintf("%s \"%s\": %s", indent, key, fdti(value, indent+" ")) - } - result += indent + "}\n" - - case []any: - result += indent + fmt.Sprintf("[ @%s\n", memoryAddr) - for _, value := range v { - result += fmt.Sprintf("%s - %s", indent, fdti(value, indent+" ")) - } - result += indent + "]\n" - - default: - // Check if it's a struct using reflection - typ := val.Type() - - // Handle pointers by dereferencing - isPtr := false - if val.Kind() == reflect.Ptr { - isPtr = true - if val.IsNil() { - return indent + "nil\n" - } - val = val.Elem() - typ = val.Type() - } - - if val.Kind() == reflect.Struct { - structName := typ.Name() - if isPtr { - structName = "*" + structName - } - result += indent + fmt.Sprintf("struct %s @%s {\n", structName, memoryAddr) - - // Iterate over all fields of the struct - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - fieldType := typ.Field(i) - - // Skip unexported fields (lowercase field names) - if !fieldType.IsExported() { - continue - } - - fieldName := fieldType.Name - fieldValue := field.Interface() - - result += fmt.Sprintf("%s %s: %s", indent, fieldName, fdti(fieldValue, indent+" ")) - } - result += indent + "}\n" - } else { - // For non-struct types, just format value with its type - result += fmt.Sprintf("%v (%s) @%s\n", v, reflect.TypeOf(v), memoryAddr) - } - } - - return result -} diff --git a/go/voxgigstruct_test.go b/go/voxgigstruct_test.go index 818cd43c..1fb4d9a4 100644 --- a/go/voxgigstruct_test.go +++ b/go/voxgigstruct_test.go @@ -1,8 +1,6 @@ - // RUN: go test // RUN-SOME: go test -v -run=TestStruct/getpath - package voxgigstruct_test import ( @@ -16,12 +14,11 @@ import ( "github.com/voxgig/struct/go/testutil" ) - // NOTE: tests are in order of increasing dependence. func TestStruct(t *testing.T) { store := make(map[string]any) - + // Create an SDK client for the runner sdk, err := runner.TestSDK(nil) if err != nil { @@ -34,20 +31,19 @@ func TestStruct(t *testing.T) { t.Fatalf("Failed to create runner struct: %v", err) } - var spec map[string]any = runnerMap.Spec - var runset runner.RunSet = runnerMap.RunSet - var runsetFlags runner.RunSetFlags = runnerMap.RunSetFlags + spec := runnerMap.Spec + runset := runnerMap.RunSet + runsetFlags := runnerMap.RunSetFlags - var minorSpec = spec["minor"].(map[string]any) - var walkSpec = spec["walk"].(map[string]any) - var mergeSpec = spec["merge"].(map[string]any) - var getpathSpec = spec["getpath"].(map[string]any) - var injectSpec = spec["inject"].(map[string]any) + var minorSpec = spec["minor"].(map[string]any) + var walkSpec = spec["walk"].(map[string]any) + var mergeSpec = spec["merge"].(map[string]any) + var getpathSpec = spec["getpath"].(map[string]any) + var injectSpec = spec["inject"].(map[string]any) var transformSpec = spec["transform"].(map[string]any) - var validateSpec = spec["validate"].(map[string]any) - var selectSpec = spec["select"].(map[string]any) + var validateSpec = spec["validate"].(map[string]any) + var selectSpec = spec["select"].(map[string]any) - // minor tests // =========== @@ -60,30 +56,30 @@ func TestStruct(t *testing.T) { "getelem": voxgigstruct.GetElem, "getprop": voxgigstruct.GetProp, - "getpath": voxgigstruct.GetPath, - "haskey": voxgigstruct.HasKey, - "inject": voxgigstruct.Inject, - "isempty": voxgigstruct.IsEmpty, - "isfunc": voxgigstruct.IsFunc, - - "iskey": voxgigstruct.IsKey, - "islist": voxgigstruct.IsList, - "ismap": voxgigstruct.IsMap, - "isnode": voxgigstruct.IsNode, - "items": voxgigstruct.Items, - - "joinurl": voxgigstruct.JoinUrl, - "jsonify": voxgigstruct.Jsonify, - "keysof": voxgigstruct.KeysOf, - "merge": voxgigstruct.Merge, - "pad": voxgigstruct.Pad, - "pathify": voxgigstruct.Pathify, - - "select": voxgigstruct.Select, - "setpath": voxgigstruct.SetPath, - "size": voxgigstruct.Size, - "slice": voxgigstruct.Slice, - "setprop": voxgigstruct.SetProp, + "getpath": voxgigstruct.GetPath, + "haskey": voxgigstruct.HasKey, + "inject": voxgigstruct.Inject, + "isempty": voxgigstruct.IsEmpty, + "isfunc": voxgigstruct.IsFunc, + + "iskey": voxgigstruct.IsKey, + "islist": voxgigstruct.IsList, + "ismap": voxgigstruct.IsMap, + "isnode": voxgigstruct.IsNode, + "items": voxgigstruct.Items, + + "joinurl": voxgigstruct.JoinUrl, + "jsonify": voxgigstruct.Jsonify, + "keysof": voxgigstruct.KeysOf, + "merge": voxgigstruct.Merge, + "pad": voxgigstruct.Pad, + "pathify": voxgigstruct.Pathify, + + "select": voxgigstruct.Select, + "setpath": voxgigstruct.SetPath, + "size": voxgigstruct.Size, + "slice": voxgigstruct.Slice, + "setprop": voxgigstruct.SetProp, "strkey": voxgigstruct.StrKey, "stringify": voxgigstruct.Stringify, @@ -100,37 +96,30 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-isnode", func(t *testing.T) { runset(t, minorSpec["isnode"], voxgigstruct.IsNode) }) - - t.Run("minor-ismap", func(t *testing.T) { + t.Run("minor-ismap", func(t *testing.T) { runset(t, minorSpec["ismap"], voxgigstruct.IsMap) }) - t.Run("minor-islist", func(t *testing.T) { runset(t, minorSpec["islist"], voxgigstruct.IsList) }) - t.Run("minor-iskey", func(t *testing.T) { runsetFlags(t, minorSpec["iskey"], map[string]bool{"null": false}, voxgigstruct.IsKey) }) - t.Run("minor-strkey", func(t *testing.T) { runsetFlags(t, minorSpec["strkey"], map[string]bool{"null": false}, voxgigstruct.StrKey) }) - t.Run("minor-isempty", func(t *testing.T) { runsetFlags(t, minorSpec["isempty"], map[string]bool{"null": false}, voxgigstruct.IsEmpty) }) - t.Run("minor-isfunc", func(t *testing.T) { runset(t, minorSpec["isfunc"], voxgigstruct.IsFunc) @@ -147,7 +136,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-clone", func(t *testing.T) { runsetFlags( t, @@ -156,27 +144,24 @@ func TestStruct(t *testing.T) { voxgigstruct.Clone, ) - f0 := func() any { return nil } - expected0 := map[string]any{"a":f0} - result0 := voxgigstruct.Clone(map[string]any{"a":f0}) + f0 := func() any { return nil } + expected0 := map[string]any{"a": f0} + result0 := voxgigstruct.Clone(map[string]any{"a": f0}) if !reflect.DeepEqual(runner.Fdt(expected0), runner.Fdt(result0)) { t.Errorf("Expected: %v, Got: %v", expected0, result0) } }) - t.Run("minor-escre", func(t *testing.T) { runset(t, minorSpec["escre"], voxgigstruct.EscRe) }) - t.Run("minor-escurl", func(t *testing.T) { runset(t, minorSpec["escurl"], func(in string) string { return strings.ReplaceAll(voxgigstruct.EscUrl(fmt.Sprint(in)), "+", "%20") }) }) - t.Run("minor-stringify", func(t *testing.T) { runset(t, minorSpec["stringify"], func(v any) any { m := v.(map[string]any) @@ -195,7 +180,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-pathify", func(t *testing.T) { runsetFlags( t, @@ -231,12 +215,10 @@ func TestStruct(t *testing.T) { ) }) - t.Run("minor-items", func(t *testing.T) { runset(t, minorSpec["items"], voxgigstruct.Items) }) - t.Run("minor-getprop", func(t *testing.T) { runsetFlags( t, @@ -255,7 +237,6 @@ func TestStruct(t *testing.T) { ) }) - t.Run("minor-edge-getprop", func(t *testing.T) { strarr := []string{"a", "b", "c", "d", "e"} expectedA := "c" @@ -284,7 +265,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-setprop", func(t *testing.T) { runsetFlags( t, @@ -300,7 +280,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-edge-setprop", func(t *testing.T) { strarr0 := []string{"a", "b", "c", "d", "e"} strarr1 := []string{"a", "b", "c", "d", "e"} @@ -333,7 +312,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-haskey", func(t *testing.T) { runsetFlags(t, minorSpec["haskey"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -343,12 +321,10 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-keysof", func(t *testing.T) { runset(t, minorSpec["keysof"], voxgigstruct.KeysOf) }) - t.Run("minor-filter", func(t *testing.T) { checkmap := map[string]func([2]any) bool{ "gt3": func(n [2]any) bool { @@ -372,7 +348,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-flatten", func(t *testing.T) { runset(t, minorSpec["flatten"], func(v any) any { m := v.(map[string]any) @@ -385,7 +360,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-join", func(t *testing.T) { runsetFlags(t, minorSpec["join"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -400,22 +374,18 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-typename", func(t *testing.T) { runset(t, minorSpec["typename"], voxgigstruct.Typename) }) - t.Run("minor-typify", func(t *testing.T) { runsetFlags(t, minorSpec["typify"], map[string]bool{"null": false}, voxgigstruct.Typify) }) - t.Run("minor-size", func(t *testing.T) { runsetFlags(t, minorSpec["size"], map[string]bool{"null": false}, voxgigstruct.Size) }) - t.Run("minor-slice", func(t *testing.T) { runsetFlags(t, minorSpec["slice"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -426,7 +396,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-pad", func(t *testing.T) { runsetFlags(t, minorSpec["pad"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -437,7 +406,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-getelem", func(t *testing.T) { runsetFlags(t, minorSpec["getelem"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -451,7 +419,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-edge-getelem", func(t *testing.T) { result := voxgigstruct.GetElem([]any{}, 1, func() int { return 2 }) if result != 2 { @@ -459,7 +426,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-delprop", func(t *testing.T) { runset(t, minorSpec["delprop"], func(v any) any { m := v.(map[string]any) @@ -469,7 +435,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-edge-delprop", func(t *testing.T) { strarr0 := []any{"a", "b", "c", "d", "e"} strarr1 := []any{"a", "b", "c", "d", "e"} @@ -498,7 +463,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-setpath", func(t *testing.T) { runsetFlags(t, minorSpec["setpath"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -509,7 +473,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-edge-setpath", func(t *testing.T) { x := map[string]any{"y": map[string]any{"z": 1, "q": 2}} result := voxgigstruct.SetPath(x, "y.q", voxgigstruct.DELETE) @@ -523,7 +486,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-jsonify", func(t *testing.T) { runsetFlags(t, minorSpec["jsonify"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -535,7 +497,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("minor-edge-jsonify", func(t *testing.T) { result := voxgigstruct.Jsonify(func() int { return 1 }) if result != "null" { @@ -543,7 +504,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-edge-stringify", func(t *testing.T) { a := make(map[string]any) a["a"] = a @@ -553,7 +513,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-edge-clone", func(t *testing.T) { // Functions are preserved (same reference, not deep-cloned). f0 := func() int { return 22 } @@ -590,7 +549,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("minor-edge-typify", func(t *testing.T) { // nil → T_scalar | T_null tNil := voxgigstruct.Typify(nil) @@ -614,7 +572,6 @@ func TestStruct(t *testing.T) { } }) - // walk tests // ========== @@ -625,7 +582,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("walk-log", func(t *testing.T) { test := voxgigstruct.Clone(walkSpec["log"]).(map[string]any) @@ -685,7 +641,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("walk-basic", func(t *testing.T) { walkpath := func(k *string, val any, parent any, path []string) any { if str, ok := val.(string); ok { @@ -702,7 +657,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("walk-depth", func(t *testing.T) { runsetFlags(t, walkSpec["depth"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -743,7 +697,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("walk-copy", func(t *testing.T) { runset(t, walkSpec["copy"], func(v any) any { var cur []any @@ -792,7 +745,6 @@ func TestStruct(t *testing.T) { }) }) - // merge tests // =========== @@ -803,7 +755,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("merge-basic", func(t *testing.T) { test := mergeSpec["basic"].(map[string]any) inVal := test["in"] @@ -814,14 +765,12 @@ func TestStruct(t *testing.T) { } }) - t.Run("merge-cases", func(t *testing.T) { runset(t, mergeSpec["cases"], func(v any) any { return voxgigstruct.Merge(v) }) }) - t.Run("merge-array", func(t *testing.T) { runset(t, mergeSpec["array"], func(v any) any { return voxgigstruct.Merge(v) @@ -834,45 +783,43 @@ func TestStruct(t *testing.T) { }) }) - t.Run("merge-special", func(t *testing.T) { f0 := func() int { return 11 } - + result0 := voxgigstruct.Merge([]any{f0}) - var fr0 = result0.(func() int) + var fr0 = result0.(func() int) - if f0() != fr0() { + if f0() != fr0() { t.Errorf("Expected same function reference (A)") } - + result1 := voxgigstruct.Merge([]any{nil, f0}) - var fr1 = result1.(func() int) + var fr1 = result1.(func() int) if f0() != fr1() { t.Errorf("Expected same function reference (B)") } - + result2 := voxgigstruct.Merge([]any{map[string]any{"a": f0}}).(map[string]any) - var fr2 = result2["a"].(func() int) + var fr2 = result2["a"].(func() int) if f0() != fr2() { t.Errorf("Expected object with function reference") } result3 := voxgigstruct.Merge([]any{[]any{f0}}).([]any) - var fr3 = result3[0].(func() int) + var fr3 = result3[0].(func() int) if f0() != fr3() { t.Errorf("Expected array with function reference") } - + result4 := voxgigstruct.Merge([]any{map[string]any{"a": map[string]any{"b": f0}}}) - var b = result4.(map[string]any)["a"].(map[string]any) - var fr4 = b["b"].(func() int) + var b = result4.(map[string]any)["a"].(map[string]any) + var fr4 = b["b"].(func() int) if f0() != fr4() { t.Errorf("Expected deep object with function reference") } }) - t.Run("merge-depth", func(t *testing.T) { runset(t, mergeSpec["depth"], func(v any) any { m := v.(map[string]any) @@ -885,7 +832,6 @@ func TestStruct(t *testing.T) { }) }) - // getpath tests // ============= @@ -896,7 +842,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("getpath-basic", func(t *testing.T) { runset(t, getpathSpec["basic"], func(v any) any { m := v.(map[string]any) @@ -907,7 +852,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("getpath-relative", func(t *testing.T) { runset(t, getpathSpec["relative"], func(v any) any { m := v.(map[string]any) @@ -930,7 +874,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("getpath-special", func(t *testing.T) { runset(t, getpathSpec["special"], func(v any) any { m := v.(map[string]any) @@ -956,7 +899,6 @@ func TestStruct(t *testing.T) { }) }) - // inject tests // ============ @@ -967,7 +909,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("inject-basic", func(t *testing.T) { subtest := injectSpec["basic"].(map[string]any) inVal := subtest["in"].(map[string]any) @@ -979,7 +920,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("inject-string", func(t *testing.T) { runset(t, injectSpec["string"], func(v any) any { m := v.(map[string]any) @@ -990,7 +930,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("inject-deep", func(t *testing.T) { runset(t, injectSpec["deep"], func(v any) any { m := v.(map[string]any) @@ -1000,7 +939,6 @@ func TestStruct(t *testing.T) { }) }) - // transform tests // =============== @@ -1011,7 +949,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("transform-basic", func(t *testing.T) { subtest := transformSpec["basic"].(map[string]any) inVal := subtest["in"].(map[string]any) @@ -1024,7 +961,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("transform-paths", func(t *testing.T) { runset(t, transformSpec["paths"], func(v any) any { m := v.(map[string]any) @@ -1034,7 +970,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("transform-cmds", func(t *testing.T) { runset(t, transformSpec["cmds"], func(v any) any { m := v.(map[string]any) @@ -1044,7 +979,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("transform-each", func(t *testing.T) { runset(t, transformSpec["each"], func(v any) any { m := v.(map[string]any) @@ -1054,7 +988,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("transform-pack", func(t *testing.T) { runset(t, transformSpec["pack"], func(v any) any { m := v.(map[string]any) @@ -1064,7 +997,6 @@ func TestStruct(t *testing.T) { }) }) - // NOTE: transform-ref has some edge case failures in $REF handling // (cyclic refs, nested self-refs). Kept as opt-in for now. t.Run("transform-ref", func(t *testing.T) { @@ -1076,7 +1008,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("transform-format", func(t *testing.T) { runsetFlags(t, transformSpec["format"], map[string]bool{"null": false}, func(v any) any { m := v.(map[string]any) @@ -1086,7 +1017,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("transform-apply", func(t *testing.T) { runset(t, transformSpec["apply"], func(v any) (any, error) { m := v.(map[string]any) @@ -1110,7 +1040,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("transform-modify", func(t *testing.T) { runset(t, transformSpec["modify"], func(v any) any { m := v.(map[string]any) @@ -1135,7 +1064,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("transform-extra", func(t *testing.T) { data := map[string]any{"a": 1} spec := map[string]any{ @@ -1178,43 +1106,41 @@ func TestStruct(t *testing.T) { if !reflect.DeepEqual(result, output) { t.Errorf("Expected: %s, \nGot: %s \nExpected JSON: %s \nGot JSON: %s", runner.Fdt(output), - runner.Fdt(result), - runner.ToJSONString(output), - runner.ToJSONString(result), - ) + runner.Fdt(result), + runner.ToJSONString(output), + runner.ToJSONString(result), + ) } }) - t.Run("transform-funcval", func(t *testing.T) { - f0 := func() int { return 22 } - + f0 := func() int { return 22 } + result1 := voxgigstruct.Transform(map[string]any{}, map[string]any{"x": 1}) expected1 := map[string]any{"x": 1} if !reflect.DeepEqual(expected1, result1) { t.Errorf("Expected simple value transform result") } - + result2 := voxgigstruct.Transform(map[string]any{}, map[string]any{"x": f0}) - var fr0 = result2.(map[string]any)["x"].(func() int) - if f0() != fr0() { - t.Errorf("Expected x to be f0") - } - - result3 := voxgigstruct.Transform(map[string]any{"a": 1}, map[string]any{"x": "`a`"}) - expected3 := map[string]any{"x": 1} - if !reflect.DeepEqual(expected3, result3) { - t.Errorf("Expected value lookup transform to work") - } - - result4 := voxgigstruct.Transform(map[string]any{"f0": f0}, map[string]any{"x": "`f0`"}) - var fr4 = result4.(map[string]any)["x"].(func() int) - if 22 != fr4() { - t.Errorf("Expected function to be preserved") - } - }) - - + var fr0 = result2.(map[string]any)["x"].(func() int) + if f0() != fr0() { + t.Errorf("Expected x to be f0") + } + + result3 := voxgigstruct.Transform(map[string]any{"a": 1}, map[string]any{"x": "`a`"}) + expected3 := map[string]any{"x": 1} + if !reflect.DeepEqual(expected3, result3) { + t.Errorf("Expected value lookup transform to work") + } + + result4 := voxgigstruct.Transform(map[string]any{"f0": f0}, map[string]any{"x": "`f0`"}) + var fr4 = result4.(map[string]any)["x"].(func() int) + if 22 != fr4() { + t.Errorf("Expected function to be preserved") + } + }) + // validate tests // =============== @@ -1225,7 +1151,6 @@ func TestStruct(t *testing.T) { } }) - t.Run("validate-basic", func(t *testing.T) { runsetFlags(t, validateSpec["basic"], map[string]bool{"null": false}, func(v any) (any, error) { m := v.(map[string]any) @@ -1235,7 +1160,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("validate-child", func(t *testing.T) { runset(t, validateSpec["child"], func(v any) (any, error) { m := v.(map[string]any) @@ -1248,7 +1172,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("validate-one", func(t *testing.T) { runset(t, validateSpec["one"], func(v any) (any, error) { m := v.(map[string]any) @@ -1258,7 +1181,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("validate-exact", func(t *testing.T) { runset(t, validateSpec["exact"], func(v any) (any, error) { m := v.(map[string]any) @@ -1268,7 +1190,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("validate-invalid", func(t *testing.T) { runsetFlags(t, validateSpec["invalid"], map[string]bool{"null": false}, func(v any) (any, error) { m := v.(map[string]any) @@ -1276,7 +1197,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("validate-special", func(t *testing.T) { runset(t, validateSpec["special"], func(v any) (any, error) { m := v.(map[string]any) @@ -1299,7 +1219,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("validate-custom", func(t *testing.T) { errs := voxgigstruct.ListRefCreate[any]() // make([]any,0) @@ -1310,8 +1229,8 @@ func TestStruct(t *testing.T) { store any, ) any { out := voxgigstruct.GetProp(inj.Dparent, inj.Key) - - switch x := out.(type) { + + switch x := out.(type) { case int: return x default: @@ -1356,7 +1275,7 @@ func TestStruct(t *testing.T) { if nil != err { t.Error(err) } - + expected1 := map[string]any{"a": "A"} if !reflect.DeepEqual(out, expected1) { t.Errorf("Expected: %v, Got: %v", expected1, out) @@ -1369,7 +1288,6 @@ func TestStruct(t *testing.T) { }) - t.Run("validate-edge", func(t *testing.T) { // $INSTANCE validator should fail for integer, map, and list values. spec := map[string]any{"x": "`$INSTANCE`"} @@ -1402,7 +1320,6 @@ func TestStruct(t *testing.T) { } }) - // select tests // ============ @@ -1415,7 +1332,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("select-operators", func(t *testing.T) { runset(t, selectSpec["operators"], func(v any) any { m := v.(map[string]any) @@ -1425,7 +1341,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("select-edge", func(t *testing.T) { runset(t, selectSpec["edge"], func(v any) any { m := v.(map[string]any) @@ -1435,7 +1350,6 @@ func TestStruct(t *testing.T) { }) }) - t.Run("select-alts", func(t *testing.T) { runset(t, selectSpec["alts"], func(v any) any { m := v.(map[string]any) @@ -1445,35 +1359,33 @@ func TestStruct(t *testing.T) { }) }) - // JSON Builder // ============ t.Run("json-builder", func(t *testing.T) { expected0 := "{\n \"a\": 1\n}" - result0 := voxgigstruct.Jsonify(voxgigstruct.Jo("a", 1)) + result0 := voxgigstruct.Jsonify(voxgigstruct.Jm("a", 1)) if result0 != expected0 { t.Errorf("Expected: %v, Got: %v", expected0, result0) } expected1 := "[\n \"b\",\n 2\n]" - result1 := voxgigstruct.Jsonify(voxgigstruct.Ja("b", 2)) + result1 := voxgigstruct.Jsonify(voxgigstruct.Jt("b", 2)) if result1 != expected1 { t.Errorf("Expected: %v, Got: %v", expected1, result1) } expected2 := "{\n \"c\": \"C\",\n \"d\": {\n \"x\": true\n },\n \"e\": [\n null,\n false\n ]\n}" - result2 := voxgigstruct.Jsonify(voxgigstruct.Jo( + result2 := voxgigstruct.Jsonify(voxgigstruct.Jm( "c", "C", - "d", voxgigstruct.Jo("x", true), - "e", voxgigstruct.Ja(nil, false), + "d", voxgigstruct.Jm("x", true), + "e", voxgigstruct.Jt(nil, false), )) if result2 != expected2 { t.Errorf("Expected:\n%v\nGot:\n%v", expected2, result2) } }) - // getpath-handler test // ==================== @@ -1507,7 +1419,6 @@ func TestStruct(t *testing.T) { }) } - func IsSameFunc(target any, candidate any) bool { if reflect.TypeOf(target).Kind() != reflect.Func || reflect.TypeOf(candidate).Kind() != reflect.Func { return false diff --git a/java/Makefile b/java/Makefile index a0d76923..de9cdda8 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test clean reset +.PHONY: inspect build test lint checkstyle spotbugs clean reset inspect: @echo "Java version:" @@ -13,6 +13,16 @@ build: test: mvn -q test +# Code quality: Checkstyle (style) + SpotBugs (bug pattern detection). +lint: + mvn -q -DskipTests compile checkstyle:check spotbugs:check + +checkstyle: + mvn -q checkstyle:check + +spotbugs: + mvn -q -DskipTests compile spotbugs:check + clean: mvn -q clean diff --git a/java/checkstyle.xml b/java/checkstyle.xml new file mode 100644 index 00000000..682a5428 --- /dev/null +++ b/java/checkstyle.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/pom.xml b/java/pom.xml index 563f9d44..b1b27493 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -14,6 +14,9 @@ UTF-8 5.10.2 2.11.0 + 3.6.0 + 10.20.1 + 4.8.6.6 @@ -53,6 +56,58 @@ false + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle.plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + ${project.basedir}/checkstyle.xml + true + true + true + warning + + + + checkstyle + verify + + check + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.plugin.version} + + Max + Medium + false + ${project.basedir}/spotbugs-exclude.xml + + + + spotbugs + verify + + check + + + + diff --git a/java/spotbugs-exclude.xml b/java/spotbugs-exclude.xml new file mode 100644 index 00000000..e51c4bbb --- /dev/null +++ b/java/spotbugs-exclude.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/java/src/test/Runner.java b/java/src/test/Runner.java index b894d916..08ee90a7 100644 --- a/java/src/test/Runner.java +++ b/java/src/test/Runner.java @@ -9,7 +9,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; diff --git a/js/.prettierignore b/js/.prettierignore new file mode 100644 index 00000000..25fbf5a1 --- /dev/null +++ b/js/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +coverage/ diff --git a/js/.prettierrc.json b/js/.prettierrc.json new file mode 100644 index 00000000..b0b2d026 --- /dev/null +++ b/js/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all", + "arrowParens": "always" +} diff --git a/js/Makefile b/js/Makefile index 502bbb87..9f2a6e46 100644 --- a/js/Makefile +++ b/js/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test clean reset +.PHONY: inspect build test lint format-check clean reset audit inspect: @echo "Node.js version:" @@ -13,8 +13,20 @@ build: test: npm test +# Code quality: ESLint + Prettier format check. +lint: + npm run lint + npm run format:check + +format-check: + npm run format:check + clean: @echo "Nothing to clean" reset: clean rm -rf node_modules + +# Supply-chain: scan dependencies for known vulnerabilities. +audit: + npm audit --audit-level=high diff --git a/js/eslint.config.mjs b/js/eslint.config.mjs new file mode 100644 index 00000000..245e08f5 --- /dev/null +++ b/js/eslint.config.mjs @@ -0,0 +1,24 @@ +// Flat ESLint config for the JavaScript port. + +import js from '@eslint/js' +import globals from 'globals' + +export default [ + { + ignores: ['node_modules/', 'coverage/'], + }, + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'commonjs', + globals: { ...globals.node }, + }, + rules: { + 'no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + }, + }, +] diff --git a/js/package.json b/js/package.json index 6d70c3ba..7564bd89 100644 --- a/js/package.json +++ b/js/package.json @@ -24,6 +24,10 @@ "test-some": "node --enable-source-maps --test-name-pattern=\"$npm_config_pattern\" --test test/struct.test.js", "watch": "tsc --build src test -w", "build": "tsc --build src test", + "lint": "eslint src test", + "lint:fix": "eslint src test --fix", + "format": "prettier --write \"src/**/*.js\" \"test/**/*.js\"", + "format:check": "prettier --check \"src/**/*.js\" \"test/**/*.js\"", "clean": "rm -rf dist dist-test node_modules yarn.lock package-lock.json", "reset": "npm run clean && npm i && npm run build && npm test", "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;", @@ -35,5 +39,11 @@ "files": [ "LICENSE", "src" - ] + ], + "devDependencies": { + "@eslint/js": "^9.14.0", + "eslint": "^9.14.0", + "globals": "^15.12.0", + "prettier": "^3.3.3" + } } diff --git a/js/src/struct.js b/js/src/struct.js index 75e63e06..ded44726 100644 --- a/js/src/struct.js +++ b/js/src/struct.js @@ -99,7 +99,7 @@ const S_VIZ = ': ' // Types let t = 31 const T_any = (1 << t--) - 1 -const T_noval = 1 << t--; // Means property absent, undefined. Also NOT a scalar! +const T_noval = 1 << t-- // Means property absent, undefined. Also NOT a scalar! const T_boolean = 1 << t-- const T_decimal = 1 << t-- const T_integer = 1 << t-- @@ -107,7 +107,7 @@ const T_number = 1 << t-- const T_string = 1 << t-- const T_function = 1 << t-- const T_symbol = 1 << t-- -const T_null = 1 << t--; // The actual JSON null value. +const T_null = 1 << t-- // The actual JSON null value. t -= 7 const T_list = 1 << t-- const T_map = 1 << t-- @@ -116,24 +116,32 @@ t -= 4 const T_scalar = 1 << t-- const T_node = 1 << t-- const TYPENAME = [ - S_any, - S_nil, - S_boolean, - S_decimal, - S_integer, - S_number, - S_string, - S_function, - S_symbol, - S_null, - '', '', '', - '', '', '', '', - S_list, - S_map, - S_instance, - '', '', '', '', - S_scalar, - S_node, + S_any, + S_nil, + S_boolean, + S_decimal, + S_integer, + S_number, + S_string, + S_function, + S_symbol, + S_null, + '', + '', + '', + '', + '', + '', + '', + S_list, + S_map, + S_instance, + '', + '', + '', + '', + S_scalar, + S_node, ] // The standard undefined value for this language. const NONE = undefined @@ -141,85 +149,81 @@ const NONE = undefined const SKIP = { '`$SKIP`': true } const DELETE = { '`$DELETE`': true } // Regular expression constants -const R_INTEGER_KEY = /^[-0-9]+$/; // Match integer keys (including <0). -const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g; // Chars that need escaping in regexp. -const R_TRAILING_SLASH = /\/+$/; // Trailing slashes in URLs. -const R_LEADING_TRAILING_SLASH = /([^\/])\/+/; // Multiple slashes in URL middle. -const R_LEADING_SLASH = /^\/+/; // Leading slashes in URLs. -const R_QUOTES = /"/g; // Double quotes for removal. -const R_DOT = /\./g; // Dots in path strings. -const R_CLONE_REF = /^`\$REF:([0-9]+)`$/; // Copy reference in cloning. -const R_META_PATH = /^([^$]+)\$([=~])(.+)$/; // Meta path syntax. -const R_DOUBLE_DOLLAR = /\$\$/g; // Double dollar escape sequence. -const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g; // Transform command names. -const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/; // Full string injection pattern. -const R_BT_ESCAPE = /\$BT/g; // Backtick escape sequence. -const R_DS_ESCAPE = /\$DS/g; // Dollar sign escape sequence. -const R_INJECTION_PARTIAL = /`([^`]+)`/g; // Partial string injection pattern. +const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0). +const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g // Chars that need escaping in regexp. +const R_QUOTES = /"/g // Double quotes for removal. +const R_DOT = /\./g // Dots in path strings. +const R_CLONE_REF = /^`\$REF:([0-9]+)`$/ // Copy reference in cloning. +const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax. +const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence. +const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names. +const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ // Full string injection pattern. +const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence. +const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence. +const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern. // Default max depth (for walk etc). const MAXDEPTH = 32 // Return type string for narrowest type. function typename(t) { - return getelem(TYPENAME, Math.clz32(t), TYPENAME[0]) + return getelem(TYPENAME, Math.clz32(t), TYPENAME[0]) } // Get a defined value. Returns alt if val is undefined. function getdef(val, alt) { - if (NONE === val) { - return alt - } - return val + if (NONE === val) { + return alt + } + return val } // Value is a node - defined, and a map (hash) or list (array). // NOTE: typescript // things function isnode(val) { - return null != val && S_object == typeof val + return null != val && S_object == typeof val } // Value is a defined map (hash) with string keys. function ismap(val) { - return null != val && S_object == typeof val && !Array.isArray(val) + return null != val && S_object == typeof val && !Array.isArray(val) } // Value is a defined list (array) with integer keys (indexes). function islist(val) { - return Array.isArray(val) + return Array.isArray(val) } // Value is a defined string (non-empty) or integer key. function iskey(key) { - const keytype = typeof key - return (S_string === keytype && S_MT !== key) || S_number === keytype + const keytype = typeof key + return (S_string === keytype && S_MT !== key) || S_number === keytype } // Check for an "empty" value - undefined, empty string, array, object. function isempty(val) { - return null == val || S_MT === val || - (Array.isArray(val) && 0 === val.length) || - (S_object === typeof val && 0 === Object.keys(val).length) + return ( + null == val || + S_MT === val || + (Array.isArray(val) && 0 === val.length) || + (S_object === typeof val && 0 === Object.keys(val).length) + ) } // Value is a function. function isfunc(val) { - return S_function === typeof val + return S_function === typeof val } // The integer size of the value. For arrays and strings, the length, // for numbers, the integer part, for boolean, true is 1 and falso 0, for all other values, 0. function size(val) { - if (islist(val)) { - return val.length - } - else if (ismap(val)) { - return Object.keys(val).length - } - const valtype = typeof val - if (S_string == valtype) { - return val.length - } - else if (S_number == typeof val) { - return Math.floor(val) - } - else if (S_boolean == typeof val) { - return true === val ? 1 : 0 - } - else { - return 0 - } + if (islist(val)) { + return val.length + } else if (ismap(val)) { + return Object.keys(val).length + } + const valtype = typeof val + if (S_string == valtype) { + return val.length + } else if (S_number == typeof val) { + return Math.floor(val) + } else if (S_boolean == typeof val) { + return true === val ? 1 : 0 + } else { + return 0 + } } // Extract part of an array or string into a new value, from the start // point to the end point. If no end is specified, extract to the @@ -229,157 +233,142 @@ function size(val) { // NOTE: input lists are not mutated by default. Use the mutate // argument to mutate lists in place. function slice(val, start, end, mutate) { - if (S_number === typeof val) { - start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start - end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1 - return Math.min(Math.max(val, start), end) - } - const vlen = size(val) - if (null != end && null == start) { - start = 0 - } - if (null != start) { - if (start < 0) { - end = vlen + start - if (end < 0) { - end = 0 - } - start = 0 - } - else if (null != end) { - if (end < 0) { - end = vlen + end - if (end < 0) { - end = 0 - } - } - else if (vlen < end) { - end = vlen - } - } - else { - end = vlen - } - if (vlen < start) { - start = vlen - } - if (-1 < start && start <= end && end <= vlen) { - if (islist(val)) { - if (mutate) { - for (let i = 0, j = start; j < end; i++, j++) { - val[i] = val[j] - } - val.length = (end - start) - } - else { - val = val.slice(start, end) - } - } - else if (S_string === typeof val) { - val = val.substring(start, end) - } - } - else { - if (islist(val)) { - val = [] - } - else if (S_string === typeof val) { - val = S_MT - } - } - } - return val + if (S_number === typeof val) { + start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start + end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1 + return Math.min(Math.max(val, start), end) + } + const vlen = size(val) + if (null != end && null == start) { + start = 0 + } + if (null != start) { + if (start < 0) { + end = vlen + start + if (end < 0) { + end = 0 + } + start = 0 + } else if (null != end) { + if (end < 0) { + end = vlen + end + if (end < 0) { + end = 0 + } + } else if (vlen < end) { + end = vlen + } + } else { + end = vlen + } + if (vlen < start) { + start = vlen + } + if (-1 < start && start <= end && end <= vlen) { + if (islist(val)) { + if (mutate) { + for (let i = 0, j = start; j < end; i++, j++) { + val[i] = val[j] + } + val.length = end - start + } else { + val = val.slice(start, end) + } + } else if (S_string === typeof val) { + val = val.substring(start, end) + } + } else { + if (islist(val)) { + val = [] + } else if (S_string === typeof val) { + val = S_MT + } + } + } + return val } // String padding. function pad(str, padding, padchar) { - str = S_string === typeof str ? str : stringify(str) - padding = null == padding ? 44 : padding - padchar = null == padchar ? S_SP : ((padchar + S_SP)[0]) - return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar) + str = S_string === typeof str ? str : stringify(str) + padding = null == padding ? 44 : padding + padchar = null == padchar ? S_SP : (padchar + S_SP)[0] + return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar) } // Determine the type of a value as a bit code. function typify(value) { - if (undefined === value) { - return T_noval - } - const typestr = typeof value - if (null === value) { - return T_scalar | T_null - } - else if (S_number === typestr) { - if (Number.isInteger(value)) { - return T_scalar | T_number | T_integer - } - else if (isNaN(value)) { - return T_noval - } - else { - return T_scalar | T_number | T_decimal - } - } - else if (S_string === typestr) { - return T_scalar | T_string - } - else if (S_boolean === typestr) { - return T_scalar | T_boolean - } - else if (S_function === typestr) { - return T_scalar | T_function - } - // For languages that have symbolic atoms. - else if (S_symbol === typestr) { - return T_scalar | T_symbol - } - else if (Array.isArray(value)) { - return T_node | T_list - } - else if (S_object === typestr) { - if (value.constructor instanceof Function) { - let cname = value.constructor.name - if ('Object' !== cname && 'Array' !== cname) { - return T_node | T_instance - } - } - return T_node | T_map - } - // Anything else (e.g. bigint) is considered T_any - return T_any + if (undefined === value) { + return T_noval + } + const typestr = typeof value + if (null === value) { + return T_scalar | T_null + } else if (S_number === typestr) { + if (Number.isInteger(value)) { + return T_scalar | T_number | T_integer + } else if (isNaN(value)) { + return T_noval + } else { + return T_scalar | T_number | T_decimal + } + } else if (S_string === typestr) { + return T_scalar | T_string + } else if (S_boolean === typestr) { + return T_scalar | T_boolean + } else if (S_function === typestr) { + return T_scalar | T_function + } + // For languages that have symbolic atoms. + else if (S_symbol === typestr) { + return T_scalar | T_symbol + } else if (Array.isArray(value)) { + return T_node | T_list + } else if (S_object === typestr) { + if (value.constructor instanceof Function) { + let cname = value.constructor.name + if ('Object' !== cname && 'Array' !== cname) { + return T_node | T_instance + } + } + return T_node | T_map + } + // Anything else (e.g. bigint) is considered T_any + return T_any } // Get a list element. The key should be an integer, or a string // that can parse to an integer only. Negative integers count from the end of the list. function getelem(val, key, alt) { - let out = NONE - if (NONE === val || NONE === key) { - return alt - } - if (islist(val)) { - let nkey = parseInt(key) - if (Number.isInteger(nkey) && ('' + key).match(R_INTEGER_KEY)) { - if (nkey < 0) { - key = val.length + nkey - } - out = val[key] - } - } - if (NONE === out) { - return 0 < (T_function & typify(alt)) ? alt() : alt - } - return out + let out = NONE + if (NONE === val || NONE === key) { + return alt + } + if (islist(val)) { + let nkey = parseInt(key) + if (Number.isInteger(nkey) && ('' + key).match(R_INTEGER_KEY)) { + if (nkey < 0) { + key = val.length + nkey + } + out = val[key] + } + } + if (NONE === out) { + return 0 < (T_function & typify(alt)) ? alt() : alt + } + return out } // Safely get a property of a node. Undefined arguments return undefined. // If the key is not found, return the alternative value, if any. function getprop(val, key, alt) { - let out = alt - if (NONE === val || NONE === key) { - return alt - } - if (isnode(val)) { - out = val[key] - } - if (NONE === out) { - return alt - } - return out + let out = alt + if (NONE === val || NONE === key) { + return alt + } + if (isnode(val)) { + out = val[key] + } + if (NONE === out) { + return alt + } + return out } // Convert different types of keys to string representation. // String keys are returned as is. @@ -387,38 +376,35 @@ function getprop(val, key, alt) { // Floats are truncated to integers. // Booleans, objects, arrays, null, undefined all return empty string. function strkey(key = NONE) { - if (NONE === key) { - return S_MT - } - const t = typify(key) - if (0 < (T_string & t)) { - return key - } - else if (0 < (T_boolean & t)) { - return S_MT - } - else if (0 < (T_number & t)) { - return key % 1 === 0 ? String(key) : String(Math.floor(key)) - } + if (NONE === key) { + return S_MT + } + const t = typify(key) + if (0 < (T_string & t)) { + return key + } else if (0 < (T_boolean & t)) { return S_MT + } else if (0 < (T_number & t)) { + return key % 1 === 0 ? String(key) : String(Math.floor(key)) + } + return S_MT } // Sorted keys of a map, or indexes (as strings) of a list. // Root utility - only uses language facilities. function keysof(val) { - return !isnode(val) ? [] : - ismap(val) ? Object.keys(val).sort() : val.map((_n, i) => S_MT + i) + return !isnode(val) ? [] : ismap(val) ? Object.keys(val).sort() : val.map((_n, i) => S_MT + i) } // Value of property with name key in node val is defined. // Root utility - only uses language facilities. function haskey(val, key) { - return NONE !== getprop(val, key) + return NONE !== getprop(val, key) } function items(val, apply) { - let out = keysof(val).map((k) => [k, val[k]]) - if (null != apply) { - out = out.map(apply) - } - return out + let out = keysof(val).map((k) => [k, val[k]]) + if (null != apply) { + out = out.map(apply) + } + return out } // To replicate the array spread operator: // a=1, b=[2,3], c=[4,5] @@ -426,248 +412,264 @@ function items(val, apply) { // flatten([a,b,[c]]) -> [1,2,3,[4,5]] // NOTE: [c] ensures c is not expanded function flatten(list, depth) { - if (!islist(list)) { - return list - } - return list.flat(getdef(depth, 1)) + if (!islist(list)) { + return list + } + return list.flat(getdef(depth, 1)) } // Filter item values using check function. function filter(val, check) { - let all = items(val) - let numall = size(all) - let out = [] - for (let i = 0; i < numall; i++) { - if (check(all[i])) { - out.push(all[i][1]) - } + let all = items(val) + let numall = size(all) + let out = [] + for (let i = 0; i < numall; i++) { + if (check(all[i])) { + out.push(all[i][1]) } - return out + } + return out } // Escape regular expression. function escre(s) { - // s = null == s ? S_MT : s - return replace(s, R_ESCAPE_REGEXP, '\\$&') + // s = null == s ? S_MT : s + return replace(s, R_ESCAPE_REGEXP, '\\$&') } // Escape URLs. function escurl(s) { - s = null == s ? S_MT : s - return encodeURIComponent(s) + s = null == s ? S_MT : s + return encodeURIComponent(s) } // Replace a search string (all), or a regexp, in a source string. function replace(s, from, to) { - let rs = s - let ts = typify(s) - if (0 === (T_string & ts)) { - rs = stringify(s) - } - else if (0 < ((T_noval | T_null) & ts)) { - rs = S_MT - } - else { - rs = stringify(s) - } - return rs.replace(from, to) + let rs = s + let ts = typify(s) + if (0 === (T_string & ts)) { + rs = stringify(s) + } else if (0 < ((T_noval | T_null) & ts)) { + rs = S_MT + } else { + rs = stringify(s) + } + return rs.replace(from, to) } // Concatenate url part strings, merging sep char as needed. function join(arr, sep, url) { - const sarr = size(arr) - const sepdef = getdef(sep, S_CM) - const sepre = 1 === size(sepdef) ? escre(sepdef) : NONE - const out = filter(items( - // filter(arr, (n) => null != n[1] && S_MT !== n[1]), - filter(arr, (n) => (0 < (T_string & typify(n[1]))) && S_MT !== n[1]), (n) => { + const sarr = size(arr) + const sepdef = getdef(sep, S_CM) + const sepre = 1 === size(sepdef) ? escre(sepdef) : NONE + const out = filter( + items( + // filter(arr, (n) => null != n[1] && S_MT !== n[1]), + filter(arr, (n) => 0 < (T_string & typify(n[1])) && S_MT !== n[1]), + (n) => { let i = +n[0] let s = n[1] if (NONE !== sepre && S_MT !== sepre) { - if (url && 0 === i) { - s = replace(s, RegExp(sepre + '+$'), S_MT) - return s - } - if (0 < i) { - s = replace(s, RegExp('^' + sepre + '+'), S_MT) - } - if (i < sarr - 1 || !url) { - s = replace(s, RegExp(sepre + '+$'), S_MT) - } - s = replace(s, RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'), '$1' + sepdef + '$2') + if (url && 0 === i) { + s = replace(s, RegExp(sepre + '+$'), S_MT) + return s + } + if (0 < i) { + s = replace(s, RegExp('^' + sepre + '+'), S_MT) + } + if (i < sarr - 1 || !url) { + s = replace(s, RegExp(sepre + '+$'), S_MT) + } + s = replace( + s, + RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'), + '$1' + sepdef + '$2', + ) } return s - }), (n) => S_MT !== n[1]) - .join(sepdef) - return out + }, + ), + (n) => S_MT !== n[1], + ).join(sepdef) + return out } // Output JSON in a "standard" format, with 2 space indents, each property on a new line, // and spaces after {[: and before ]}. Any "wierd" values (NaN, etc) are output as null. // In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed. function jsonify(val, flags) { - let str = S_null - if (null != val) { - try { - const indent = getprop(flags, 'indent', 2) - str = JSON.stringify(val, null, indent) - if (NONE === str) { - str = S_null - } - const offset = getprop(flags, 'offset', 0) - if (0 < offset) { - // Left offset entire indented JSON so that it aligns with surrounding code - // indented by offset. Assume first brace is on line with asignment, so not offset. - str = '{\n' + - join(items(slice(str.split('\n'), 1), (n) => pad(n[1], 0 - offset - size(n[1]))), '\n') - } - } - catch (e) { - str = '__JSONIFY_FAILED__' - } - } - return str + let str = S_null + if (null != val) { + try { + const indent = getprop(flags, 'indent', 2) + str = JSON.stringify(val, null, indent) + if (NONE === str) { + str = S_null + } + const offset = getprop(flags, 'offset', 0) + if (0 < offset) { + // Left offset entire indented JSON so that it aligns with surrounding code + // indented by offset. Assume first brace is on line with asignment, so not offset. + str = + '{\n' + + join( + items(slice(str.split('\n'), 1), (n) => pad(n[1], 0 - offset - size(n[1]))), + '\n', + ) + } + } catch { + str = '__JSONIFY_FAILED__' + } + } + return str } // Safely stringify a value for humans (NOT JSON!). function stringify(val, maxlen, pretty) { - let valstr = S_MT - pretty = !!pretty - if (NONE === val) { - return pretty ? '<>' : valstr - } - if (S_string === typeof val) { - valstr = val - } - else { - try { - valstr = JSON.stringify(val, function (_key, val) { - if (val !== null && - typeof val === "object" && - !Array.isArray(val)) { - const sortedObj = {} - items(val, (n) => { - sortedObj[n[0]] = val[n[0]] - }) - return sortedObj - } - return val - }) - valstr = valstr.replace(R_QUOTES, S_MT) - } - catch (err) { - valstr = '__STRINGIFY_FAILED__' + let valstr = S_MT + pretty = !!pretty + if (NONE === val) { + return pretty ? '<>' : valstr + } + if (S_string === typeof val) { + valstr = val + } else { + try { + valstr = JSON.stringify(val, function (_key, val) { + if (val !== null && typeof val === 'object' && !Array.isArray(val)) { + const sortedObj = {} + items(val, (n) => { + sortedObj[n[0]] = val[n[0]] + }) + return sortedObj } - } - if (null != maxlen && -1 < maxlen) { - let js = valstr.substring(0, maxlen) - valstr = maxlen < valstr.length ? (js.substring(0, maxlen - 3) + '...') : valstr - } - if (pretty) { - // Indicate deeper JSON levels with different terminal colors (simplistic wrt strings). - let c = items([81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69], (n) => '\x1b[38;5;' + n[1] + 'm'), r = '\x1b[0m', d = 0, o = c[0], t = o - for (const ch of valstr) { - if (ch === '{' || ch === '[') { - d++ - o = c[d % c.length] - t += o + ch - } - else if (ch === '}' || ch === ']') { - t += o + ch - d-- - o = c[d % c.length] - } - else { - t += o + ch - } - } - return t + r - } - return valstr + return val + }) + valstr = valstr.replace(R_QUOTES, S_MT) + } catch { + valstr = '__STRINGIFY_FAILED__' + } + } + if (null != maxlen && -1 < maxlen) { + let js = valstr.substring(0, maxlen) + valstr = maxlen < valstr.length ? js.substring(0, maxlen - 3) + '...' : valstr + } + if (pretty) { + // Indicate deeper JSON levels with different terminal colors (simplistic wrt strings). + let c = items( + [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69], + (n) => '\x1b[38;5;' + n[1] + 'm', + ), + r = '\x1b[0m', + d = 0, + o = c[0], + t = o + for (const ch of valstr) { + if (ch === '{' || ch === '[') { + d++ + o = c[d % c.length] + t += o + ch + } else if (ch === '}' || ch === ']') { + t += o + ch + d-- + o = c[d % c.length] + } else { + t += o + ch + } + } + return t + r + } + return valstr } // Build a human friendly path string. function pathify(val, startin, endin) { - let pathstr = NONE - let path = islist(val) ? val : - S_string == typeof val ? [val] : - S_number == typeof val ? [val] : - NONE - const start = null == startin ? 0 : -1 < startin ? startin : 0 - const end = null == endin ? 0 : -1 < endin ? endin : 0 - if (NONE != path && 0 <= start) { - path = slice(path, start, path.length - end) - if (0 === path.length) { - pathstr = '' - } - else { - pathstr = join(items(filter(path, (n) => iskey(n[1])), (n) => { - let p = n[1] - return S_number === typeof p ? S_MT + Math.floor(p) : - p.replace(R_DOT, S_MT) - }), S_DT) - } - } - if (NONE === pathstr) { - pathstr = '' - } - return pathstr + let pathstr = NONE + let path = islist(val) + ? val + : S_string == typeof val + ? [val] + : S_number == typeof val + ? [val] + : NONE + const start = null == startin ? 0 : -1 < startin ? startin : 0 + const end = null == endin ? 0 : -1 < endin ? endin : 0 + if (NONE != path && 0 <= start) { + path = slice(path, start, path.length - end) + if (0 === path.length) { + pathstr = '' + } else { + pathstr = join( + items( + filter(path, (n) => iskey(n[1])), + (n) => { + let p = n[1] + return S_number === typeof p ? S_MT + Math.floor(p) : p.replace(R_DOT, S_MT) + }, + ), + S_DT, + ) + } + } + if (NONE === pathstr) { + pathstr = '' + } + return pathstr } // Clone a JSON-like data structure. // NOTE: function and instance values are copied, *not* cloned. function clone(val) { - const refs = [] - const reftype = T_function | T_instance - const replacer = (_k, v) => 0 < (reftype & typify(v)) ? - (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v - const reviver = (_k, v, m) => S_string === typeof v ? - (m = v.match(R_CLONE_REF), m ? refs[m[1]] : v) : v - const out = NONE === val ? NONE : JSON.parse(JSON.stringify(val, replacer), reviver) - return out + const refs = [] + const reftype = T_function | T_instance + const replacer = (_k, v) => + 0 < (reftype & typify(v)) ? (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v + const reviver = (_k, v, m) => + S_string === typeof v ? ((m = v.match(R_CLONE_REF)), m ? refs[m[1]] : v) : v + const out = NONE === val ? NONE : JSON.parse(JSON.stringify(val, replacer), reviver) + return out } // Define a JSON Object using function arguments. function jm(...kv) { - const kvsize = size(kv) - const o = {} - for (let i = 0; i < kvsize; i += 2) { - let k = getprop(kv, i, '$KEY' + i) - k = 'string' === typeof k ? k : stringify(k) - o[k] = getprop(kv, i + 1, null) - } - return o + const kvsize = size(kv) + const o = {} + for (let i = 0; i < kvsize; i += 2) { + let k = getprop(kv, i, '$KEY' + i) + k = 'string' === typeof k ? k : stringify(k) + o[k] = getprop(kv, i + 1, null) + } + return o } // Define a JSON Array using function arguments. function jt(...v) { - const vsize = size(v) - const a = new Array(vsize) - for (let i = 0; i < vsize; i++) { - a[i] = getprop(v, i, null) - } - return a -} -// Safely delete a property from an object or array element. + const vsize = size(v) + const a = new Array(vsize) + for (let i = 0; i < vsize; i++) { + a[i] = getprop(v, i, null) + } + return a +} +// Safely delete a property from an object or array element. // Undefined arguments and invalid keys are ignored. // Returns the (possibly modified) parent. // For objects, the property is deleted using the delete operator. // For arrays, the element at the index is removed and remaining elements are shifted down. // NOTE: parent list may be new list, thus update references. function delprop(parent, key) { - if (!iskey(key)) { - return parent - } - if (ismap(parent)) { - key = strkey(key) - delete parent[key] - } - else if (islist(parent)) { - // Ensure key is an integer. - let keyI = +key - if (isNaN(keyI)) { - return parent - } - keyI = Math.floor(keyI) - // Delete list element at position keyI, shifting later elements down. - const psize = size(parent) - if (0 <= keyI && keyI < psize) { - for (let pI = keyI; pI < psize - 1; pI++) { - parent[pI] = parent[pI + 1] - } - parent.length = parent.length - 1 - } - } + if (!iskey(key)) { return parent + } + if (ismap(parent)) { + key = strkey(key) + delete parent[key] + } else if (islist(parent)) { + // Ensure key is an integer. + let keyI = +key + if (isNaN(keyI)) { + return parent + } + keyI = Math.floor(keyI) + // Delete list element at position keyI, shifting later elements down. + const psize = size(parent) + if (0 <= keyI && keyI < psize) { + for (let pI = keyI; pI < psize - 1; pI++) { + parent[pI] = parent[pI + 1] + } + parent.length = parent.length - 1 + } + } + return parent } // Safely set a property. Undefined arguments and invalid keys are ignored. // Returns the (possibly modified) parent. @@ -675,32 +677,31 @@ function delprop(parent, key) { // NOTE: If the key is above the list size, append the value; below, prepend. // NOTE: parent list may be new list, thus update references. function setprop(parent, key, val) { - if (!iskey(key)) { - return parent - } - if (ismap(parent)) { - key = S_MT + key - const pany = parent - pany[key] = val - } - else if (islist(parent)) { - // Ensure key is an integer. - let keyI = +key - if (isNaN(keyI)) { - return parent - } - keyI = Math.floor(keyI) - // TODO: DELETE list element - // Set or append value at position keyI, or append if keyI out of bounds. - if (0 <= keyI) { - parent[slice(keyI, 0, size(parent) + 1)] = val - } - // Prepend value if keyI is negative - else { - parent.unshift(val) - } - } + if (!iskey(key)) { return parent + } + if (ismap(parent)) { + key = S_MT + key + const pany = parent + pany[key] = val + } else if (islist(parent)) { + // Ensure key is an integer. + let keyI = +key + if (isNaN(keyI)) { + return parent + } + keyI = Math.floor(keyI) + // TODO: DELETE list element + // Set or append value at position keyI, or append if keyI out of bounds. + if (0 <= keyI) { + parent[slice(keyI, 0, size(parent) + 1)] = val + } + // Prepend value if keyI is negative + else { + parent.unshift(val) + } + } + return parent } // Walk a data structure depth first, applying a function to each value. // The `path` argument passed to the before/after callbacks is a single @@ -709,869 +710,888 @@ function setprop(parent, key, val) { // path MUST clone it (e.g. `path.slice()`); the contents will otherwise // be overwritten by subsequent visits. function walk( -// These arguments are the public interface. -val, -// Before descending into a node. -before, -// After descending into a node. -after, -// Maximum recursive depth, default: 32. Use null for infinite depth. -maxdepth, -// These areguments are used for recursive state. -key, parent, path, pool) { - if (NONE === pool) { - pool = [[]] - } - if (NONE === path) { - path = pool[0] - } - const depth = path.length - let out = null == before ? val : before(key, val, parent, path) - maxdepth = null != maxdepth && 0 <= maxdepth ? maxdepth : MAXDEPTH - if (0 === maxdepth || (0 < maxdepth && maxdepth <= depth)) { - return out - } - if (isnode(out)) { - const childDepth = depth + 1 - let childPath = pool[childDepth] - if (NONE === childPath) { - childPath = new Array(childDepth) - pool[childDepth] = childPath - } - // Sync prefix [0..depth-1] from the current path. Only needed once per - // parent: siblings share the same prefix and will each overwrite slot - // [depth] below. - for (let i = 0; i < depth; i++) { - childPath[i] = path[i] - } - for (let [ckey, child] of items(out)) { - childPath[depth] = S_MT + ckey - setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath, pool)) - } - } - out = null == after ? out : after(key, out, parent, path) + // These arguments are the public interface. + val, + // Before descending into a node. + before, + // After descending into a node. + after, + // Maximum recursive depth, default: 32. Use null for infinite depth. + maxdepth, + // These areguments are used for recursive state. + key, + parent, + path, + pool, +) { + if (NONE === pool) { + pool = [[]] + } + if (NONE === path) { + path = pool[0] + } + const depth = path.length + let out = null == before ? val : before(key, val, parent, path) + maxdepth = null != maxdepth && 0 <= maxdepth ? maxdepth : MAXDEPTH + if (0 === maxdepth || (0 < maxdepth && maxdepth <= depth)) { return out + } + if (isnode(out)) { + const childDepth = depth + 1 + let childPath = pool[childDepth] + if (NONE === childPath) { + childPath = new Array(childDepth) + pool[childDepth] = childPath + } + // Sync prefix [0..depth-1] from the current path. Only needed once per + // parent: siblings share the same prefix and will each overwrite slot + // [depth] below. + for (let i = 0; i < depth; i++) { + childPath[i] = path[i] + } + for (let [ckey, child] of items(out)) { + childPath[depth] = S_MT + ckey + setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath, pool)) + } + } + out = null == after ? out : after(key, out, parent, path) + return out } // Merge a list of values into each other. Later values have // precedence. Nodes override scalars. Node kinds (list or map) // override each other, and do *not* merge. The first element is // modified. function merge(val, maxdepth) { - // const md: number = null == maxdepth ? MAXDEPTH : maxdepth < 0 ? 0 : maxdepth - const md = slice(maxdepth ?? MAXDEPTH, 0) - let out = NONE - // Handle edge cases. - if (!islist(val)) { - return val - } - const list = val - const lenlist = list.length - if (0 === lenlist) { - return NONE - } - else if (1 === lenlist) { - return list[0] - } - // Merge a list of values. - out = getprop(list, 0, {}) - for (let oI = 1; oI < lenlist; oI++) { - let obj = list[oI] - if (!isnode(obj)) { - // Nodes win. - out = obj - } + // const md: number = null == maxdepth ? MAXDEPTH : maxdepth < 0 ? 0 : maxdepth + const md = slice(maxdepth ?? MAXDEPTH, 0) + let out = NONE + // Handle edge cases. + if (!islist(val)) { + return val + } + const list = val + const lenlist = list.length + if (0 === lenlist) { + return NONE + } else if (1 === lenlist) { + return list[0] + } + // Merge a list of values. + out = getprop(list, 0, {}) + for (let oI = 1; oI < lenlist; oI++) { + let obj = list[oI] + if (!isnode(obj)) { + // Nodes win. + out = obj + } else { + // Current value at path end in overriding node. + let cur = [out] + // Current value at path end in destination node. + let dst = [out] + function before(key, val, _parent, path) { + const pI = size(path) + if (md <= pI) { + setprop(cur[pI - 1], key, val) + } + // Scalars just override directly. + else if (!isnode(val)) { + cur[pI] = val + } + // Descend into override node - Set up correct target in `after` function. else { - // Current value at path end in overriding node. - let cur = [out] - // Current value at path end in destination node. - let dst = [out] - function before(key, val, _parent, path) { - const pI = size(path) - if (md <= pI) { - setprop(cur[pI - 1], key, val) - } - // Scalars just override directly. - else if (!isnode(val)) { - cur[pI] = val - } - // Descend into override node - Set up correct target in `after` function. - else { - // Descend into destination node using same key. - dst[pI] = 0 < pI ? getprop(dst[pI - 1], key) : dst[pI] - const tval = dst[pI] - // Destination empty, so create node (unless override is class instance). - if (NONE === tval && 0 === (T_instance & typify(val))) { - cur[pI] = islist(val) ? [] : {} - } - // Matching override and destination so continue with their values. - else if (typify(val) === typify(tval)) { - cur[pI] = tval - } - // Override wins. - else { - cur[pI] = val - // No need to descend when override wins (destination is discarded). - val = NONE - } - } - // console.log('BEFORE-END', pathify(path), '@', pI, key, - // stringify(val, -1, 1), stringify(parent, -1, 1), - // 'CUR=', stringify(cur, -1, 1), 'DST=', stringify(dst, -1, 1)) - return val - } - function after(key, _val, _parent, path) { - const cI = size(path) - const target = cur[cI - 1] - const value = cur[cI] - // console.log('AFTER-PREP', pathify(path), '@', cI, cur, '|', - // stringify(key, -1, 1), stringify(value, -1, 1), 'T=', stringify(target, -1, 1)) - setprop(target, key, value) - return value - } - // Walk overriding node, creating paths in output as needed. - out = walk(obj, before, after, maxdepth) - // console.log('WALK-DONE', out, obj) - } - } - if (0 === md) { - out = getelem(list, -1) - out = islist(out) ? [] : ismap(out) ? {} : out - } - return out + // Descend into destination node using same key. + dst[pI] = 0 < pI ? getprop(dst[pI - 1], key) : dst[pI] + const tval = dst[pI] + // Destination empty, so create node (unless override is class instance). + if (NONE === tval && 0 === (T_instance & typify(val))) { + cur[pI] = islist(val) ? [] : {} + } + // Matching override and destination so continue with their values. + else if (typify(val) === typify(tval)) { + cur[pI] = tval + } + // Override wins. + else { + cur[pI] = val + // No need to descend when override wins (destination is discarded). + val = NONE + } + } + // console.log('BEFORE-END', pathify(path), '@', pI, key, + // stringify(val, -1, 1), stringify(parent, -1, 1), + // 'CUR=', stringify(cur, -1, 1), 'DST=', stringify(dst, -1, 1)) + return val + } + function after(key, _val, _parent, path) { + const cI = size(path) + const target = cur[cI - 1] + const value = cur[cI] + // console.log('AFTER-PREP', pathify(path), '@', cI, cur, '|', + // stringify(key, -1, 1), stringify(value, -1, 1), 'T=', stringify(target, -1, 1)) + setprop(target, key, value) + return value + } + // Walk overriding node, creating paths in output as needed. + out = walk(obj, before, after, maxdepth) + // console.log('WALK-DONE', out, obj) + } + } + if (0 === md) { + out = getelem(list, -1) + out = islist(out) ? [] : ismap(out) ? {} : out + } + return out } // Set a value using a path. Missing path parts are created. // String paths create only maps. Use a string list to create list parts. function setpath(store, path, val, injdef) { - const pathType = typify(path) - const parts = 0 < (T_list & pathType) ? path : - 0 < (T_string & pathType) ? path.split(S_DT) : - 0 < (T_number & pathType) ? [path] : NONE - if (NONE === parts) { - return NONE - } - const base = getprop(injdef, S_base) - const numparts = size(parts) - let parent = getprop(store, base, store) - for (let pI = 0; pI < numparts - 1; pI++) { - const partKey = getelem(parts, pI) - let nextParent = getprop(parent, partKey) - if (!isnode(nextParent)) { - nextParent = 0 < (T_number & typify(getelem(parts, pI + 1))) ? [] : {} - setprop(parent, partKey, nextParent) - } - parent = nextParent - } - if (DELETE === val) { - delprop(parent, getelem(parts, -1)) - } - else { - setprop(parent, getelem(parts, -1), val) - } - return parent + const pathType = typify(path) + const parts = + 0 < (T_list & pathType) + ? path + : 0 < (T_string & pathType) + ? path.split(S_DT) + : 0 < (T_number & pathType) + ? [path] + : NONE + if (NONE === parts) { + return NONE + } + const base = getprop(injdef, S_base) + const numparts = size(parts) + let parent = getprop(store, base, store) + for (let pI = 0; pI < numparts - 1; pI++) { + const partKey = getelem(parts, pI) + let nextParent = getprop(parent, partKey) + if (!isnode(nextParent)) { + nextParent = 0 < (T_number & typify(getelem(parts, pI + 1))) ? [] : {} + setprop(parent, partKey, nextParent) + } + parent = nextParent + } + if (DELETE === val) { + delprop(parent, getelem(parts, -1)) + } else { + setprop(parent, getelem(parts, -1), val) + } + return parent } function getpath(store, path, injdef) { - // Operate on a string array. - const parts = islist(path) ? path : - 'string' === typeof path ? path.split(S_DT) : - 'number' === typeof path ? [strkey(path)] : NONE - if (NONE === parts) { - return NONE - } - // let root = store - let val = store - const base = getprop(injdef, S_base) - const src = getprop(store, base, store) - const numparts = size(parts) - const dparent = getprop(injdef, 'dparent') - // An empty path (incl empty string) just finds the store. - if (null == path || null == store || (1 === numparts && S_MT === parts[0])) { - val = src - } - else if (0 < numparts) { - // Check for $ACTIONs - if (1 === numparts) { - val = getprop(store, parts[0]) - } - if (!isfunc(val)) { - val = src - const m = parts[0].match(R_META_PATH) - if (m && injdef && injdef.meta) { - val = getprop(injdef.meta, m[1]) - parts[0] = m[3] + // Operate on a string array. + const parts = islist(path) + ? path + : 'string' === typeof path + ? path.split(S_DT) + : 'number' === typeof path + ? [strkey(path)] + : NONE + if (NONE === parts) { + return NONE + } + // let root = store + let val = store + const base = getprop(injdef, S_base) + const src = getprop(store, base, store) + const numparts = size(parts) + const dparent = getprop(injdef, 'dparent') + // An empty path (incl empty string) just finds the store. + if (null == path || null == store || (1 === numparts && S_MT === parts[0])) { + val = src + } else if (0 < numparts) { + // Check for $ACTIONs + if (1 === numparts) { + val = getprop(store, parts[0]) + } + if (!isfunc(val)) { + val = src + const m = parts[0].match(R_META_PATH) + if (m && injdef && injdef.meta) { + val = getprop(injdef.meta, m[1]) + parts[0] = m[3] + } + const dpath = getprop(injdef, 'dpath') + for (let pI = 0; NONE !== val && pI < numparts; pI++) { + let part = parts[pI] + if (injdef && S_DKEY === part) { + part = getprop(injdef, S_key) + } else if (injdef && part.startsWith('$GET:')) { + // $GET:path$ -> get store value, use as path part (string) + part = stringify(getpath(src, slice(part, 5, -1))) + } else if (injdef && part.startsWith('$REF:')) { + // $REF:refpath$ -> get spec value, use as path part (string) + part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1))) + } else if (injdef && part.startsWith('$META:')) { + // $META:metapath$ -> get meta value, use as path part (string) + part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1))) + } + // $$ escapes $ + part = part.replace(R_DOUBLE_DOLLAR, '$') + if (S_MT === part) { + let ascends = 0 + while (S_MT === parts[1 + pI]) { + ascends++ + pI++ + } + if (injdef && 0 < ascends) { + if (pI === parts.length - 1) { + ascends-- } - const dpath = getprop(injdef, 'dpath') - for (let pI = 0; NONE !== val && pI < numparts; pI++) { - let part = parts[pI] - if (injdef && S_DKEY === part) { - part = getprop(injdef, S_key) - } - else if (injdef && part.startsWith('$GET:')) { - // $GET:path$ -> get store value, use as path part (string) - part = stringify(getpath(src, slice(part, 5, -1))) - } - else if (injdef && part.startsWith('$REF:')) { - // $REF:refpath$ -> get spec value, use as path part (string) - part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1))) - } - else if (injdef && part.startsWith('$META:')) { - // $META:metapath$ -> get meta value, use as path part (string) - part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1))) - } - // $$ escapes $ - part = part.replace(R_DOUBLE_DOLLAR, '$') - if (S_MT === part) { - let ascends = 0 - while (S_MT === parts[1 + pI]) { - ascends++ - pI++ - } - if (injdef && 0 < ascends) { - if (pI === parts.length - 1) { - ascends-- - } - if (0 === ascends) { - val = dparent - } - else { - // const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1)) - const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)]) - if (ascends <= size(dpath)) { - val = getpath(store, fullpath) - } - else { - val = NONE - } - break - } - } - else { - val = dparent - } - } - else { - val = getprop(val, part) - } + if (0 === ascends) { + val = dparent + } else { + // const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1)) + const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)]) + if (ascends <= size(dpath)) { + val = getpath(store, fullpath) + } else { + val = NONE + } + break } - } - } - // Inj may provide a custom handler to modify found value. - const handler = getprop(injdef, 'handler') - if (null != injdef && isfunc(handler)) { - const ref = pathify(path) - val = handler(injdef, val, ref, store) - } - // console.log('GETPATH', path, val) - return val + } else { + val = dparent + } + } else { + val = getprop(val, part) + } + } + } + } + // Inj may provide a custom handler to modify found value. + const handler = getprop(injdef, 'handler') + if (null != injdef && isfunc(handler)) { + const ref = pathify(path) + val = handler(injdef, val, ref, store) + } + // console.log('GETPATH', path, val) + return val } // Inject values from a data store into a node recursively, resolving // paths against the store, or current if they are local. The modify // argument allows custom modification of the result. The inj // (Injection) argument is used to maintain recursive state. function inject(val, store, injdef) { - const valtype = typeof val - let inj = injdef - // Create state if at root of injection. The input value is placed - // inside a virtual parent holder to simplify edge cases. - if (NONE === injdef || null == injdef.mode) { - // Set up state assuming we are starting in the virtual parent. - inj = new Injection(val, { [S_DTOP]: val }) - inj.dparent = store - inj.errs = getprop(store, S_DERRS, []) - inj.meta.__d = 0 - if (NONE !== injdef) { - inj.modify = null == injdef.modify ? inj.modify : injdef.modify - inj.extra = null == injdef.extra ? inj.extra : injdef.extra - inj.meta = null == injdef.meta ? inj.meta : injdef.meta - inj.handler = null == injdef.handler ? inj.handler : injdef.handler - } - } - inj.descend() - // console.log('INJ-START', val, inj.mode, inj.key, inj.val, - // 't=', inj.path, 'P=', inj.parent, 'dp=', inj.dparent, 'ST=', store.$TOP) - // Descend into node. - if (isnode(val)) { - // Keys are sorted alphanumerically to ensure determinism. - // Injection transforms ($FOO) are processed *after* other keys. - // NOTE: the optional digits suffix of the transform can thus be - // used to order the transforms. - let nodekeys - nodekeys = keysof(val) - if (ismap(val)) { - nodekeys = flatten([ - filter(nodekeys, (n => !n[1].includes(S_DS))), - filter(nodekeys, (n => n[1].includes(S_DS))), - ]) - } - else { - nodekeys = keysof(val) - } - // Each child key-value pair is processed in three injection phases: - // 1. inj.mode=M_KEYPRE - Key string is injected, returning a possibly altered key. - // 2. inj.mode=M_VAL - The child value is injected. - // 3. inj.mode=M_KEYPOST - Key string is injected again, allowing child mutation. - for (let nkI = 0; nkI < nodekeys.length; nkI++) { - const childinj = inj.child(nkI, nodekeys) - const nodekey = childinj.key - childinj.mode = M_KEYPRE - // Peform the key:pre mode injection on the child key. - const prekey = _injectstr(nodekey, store, childinj) - // The injection may modify child processing. - nkI = childinj.keyI - nodekeys = childinj.keys - // Prevent further processing by returning an undefined prekey - if (NONE !== prekey) { - childinj.val = getprop(val, prekey) - childinj.mode = M_VAL - // Perform the val mode injection on the child value. - // NOTE: return value is not used. - inject(childinj.val, store, childinj) - // The injection may modify child processing. - nkI = childinj.keyI - nodekeys = childinj.keys - // Peform the key:post mode injection on the child key. - childinj.mode = M_KEYPOST - _injectstr(nodekey, store, childinj) - // The injection may modify child processing. - nkI = childinj.keyI - nodekeys = childinj.keys - } - } - } - // Inject paths into string scalars. - else if (S_string === valtype) { - inj.mode = M_VAL - val = _injectstr(val, store, inj) - if (SKIP !== val) { - inj.setval(val) - } - } - // Custom modification. - if (inj.modify && SKIP !== val) { - let mkey = inj.key - let mparent = inj.parent - let mval = getprop(mparent, mkey) - inj.modify(mval, mkey, mparent, inj, store) - } - // console.log('INJ-VAL', val) - inj.val = val - // Original val reference may no longer be correct. - // This return value is only used as the top level result. - return getprop(inj.parent, S_DTOP) + const valtype = typeof val + let inj = injdef + // Create state if at root of injection. The input value is placed + // inside a virtual parent holder to simplify edge cases. + if (NONE === injdef || null == injdef.mode) { + // Set up state assuming we are starting in the virtual parent. + inj = new Injection(val, { [S_DTOP]: val }) + inj.dparent = store + inj.errs = getprop(store, S_DERRS, []) + inj.meta.__d = 0 + if (NONE !== injdef) { + inj.modify = null == injdef.modify ? inj.modify : injdef.modify + inj.extra = null == injdef.extra ? inj.extra : injdef.extra + inj.meta = null == injdef.meta ? inj.meta : injdef.meta + inj.handler = null == injdef.handler ? inj.handler : injdef.handler + } + } + inj.descend() + // console.log('INJ-START', val, inj.mode, inj.key, inj.val, + // 't=', inj.path, 'P=', inj.parent, 'dp=', inj.dparent, 'ST=', store.$TOP) + // Descend into node. + if (isnode(val)) { + // Keys are sorted alphanumerically to ensure determinism. + // Injection transforms ($FOO) are processed *after* other keys. + // NOTE: the optional digits suffix of the transform can thus be + // used to order the transforms. + let nodekeys + nodekeys = keysof(val) + if (ismap(val)) { + nodekeys = flatten([ + filter(nodekeys, (n) => !n[1].includes(S_DS)), + filter(nodekeys, (n) => n[1].includes(S_DS)), + ]) + } else { + nodekeys = keysof(val) + } + // Each child key-value pair is processed in three injection phases: + // 1. inj.mode=M_KEYPRE - Key string is injected, returning a possibly altered key. + // 2. inj.mode=M_VAL - The child value is injected. + // 3. inj.mode=M_KEYPOST - Key string is injected again, allowing child mutation. + for (let nkI = 0; nkI < nodekeys.length; nkI++) { + const childinj = inj.child(nkI, nodekeys) + const nodekey = childinj.key + childinj.mode = M_KEYPRE + // Peform the key:pre mode injection on the child key. + const prekey = _injectstr(nodekey, store, childinj) + // The injection may modify child processing. + nkI = childinj.keyI + nodekeys = childinj.keys + // Prevent further processing by returning an undefined prekey + if (NONE !== prekey) { + childinj.val = getprop(val, prekey) + childinj.mode = M_VAL + // Perform the val mode injection on the child value. + // NOTE: return value is not used. + inject(childinj.val, store, childinj) + // The injection may modify child processing. + nkI = childinj.keyI + nodekeys = childinj.keys + // Peform the key:post mode injection on the child key. + childinj.mode = M_KEYPOST + _injectstr(nodekey, store, childinj) + // The injection may modify child processing. + nkI = childinj.keyI + nodekeys = childinj.keys + } + } + } + // Inject paths into string scalars. + else if (S_string === valtype) { + inj.mode = M_VAL + val = _injectstr(val, store, inj) + if (SKIP !== val) { + inj.setval(val) + } + } + // Custom modification. + if (inj.modify && SKIP !== val) { + let mkey = inj.key + let mparent = inj.parent + let mval = getprop(mparent, mkey) + inj.modify(mval, mkey, mparent, inj, store) + } + // console.log('INJ-VAL', val) + inj.val = val + // Original val reference may no longer be correct. + // This return value is only used as the top level result. + return getprop(inj.parent, S_DTOP) } // The transform_* functions are special command inject handlers (see Injector). // Delete a key from a map or list. const transform_DELETE = (inj) => { - inj.setval(NONE) - return NONE + inj.setval(NONE) + return NONE } // Copy value from source data. const transform_COPY = (inj, _val) => { - const ijname = 'COPY' - if (!checkPlacement(M_VAL, ijname, T_any, inj)) { - return NONE - } - let out = getprop(inj.dparent, inj.key) - inj.setval(out) - return out + const ijname = 'COPY' + if (!checkPlacement(M_VAL, ijname, T_any, inj)) { + return NONE + } + let out = getprop(inj.dparent, inj.key) + inj.setval(out) + return out } // As a value, inject the key of the parent node. // As a key, defined the name of the key property in the source object. const transform_KEY = (inj) => { - const { mode, path, parent } = inj - // Do nothing in val mode - not an error. - if (M_VAL !== mode) { - return NONE - } - // Key is defined by $KEY meta property. - const keyspec = getprop(parent, S_BKEY) - if (NONE !== keyspec) { - delprop(parent, S_BKEY) - return getprop(inj.dparent, keyspec) - } - // Key is defined within general purpose $META object. - // return getprop(getprop(parent, S_BANNO), S_KEY, getprop(path, path.length - 2)) - return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2)) + const { mode, path, parent } = inj + // Do nothing in val mode - not an error. + if (M_VAL !== mode) { + return NONE + } + // Key is defined by $KEY meta property. + const keyspec = getprop(parent, S_BKEY) + if (NONE !== keyspec) { + delprop(parent, S_BKEY) + return getprop(inj.dparent, keyspec) + } + // Key is defined within general purpose $META object. + // return getprop(getprop(parent, S_BANNO), S_KEY, getprop(path, path.length - 2)) + return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2)) } // Annotate node. Does nothing itself, just used by // other injectors, and is removed when called. const transform_ANNO = (inj) => { - const { parent } = inj - delprop(parent, S_BANNO) - return NONE + const { parent } = inj + delprop(parent, S_BANNO) + return NONE } -// Merge a list of objects into the current object. +// Merge a list of objects into the current object. // Must be a key in an object. The value is merged over the current object. -// If the value is an array, the elements are first merged using `merge`. +// If the value is an array, the elements are first merged using `merge`. // If the value is the empty string, merge the top level store. // Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] } const transform_MERGE = (inj) => { - const { mode, key, parent } = inj - // Ensures $MERGE is removed from parent list (val mode). - let out = NONE - if (M_KEYPRE === mode) { - out = key - } - // Operate after child values have been transformed. - else if (M_KEYPOST === mode) { - out = key - let args = getprop(parent, key) - args = Array.isArray(args) ? args : [args] - // Remove the $MERGE command from a parent map. - inj.setval(NONE) - // Literals in the parent have precedence, but we still merge onto - // the parent object, so that node tree references are not changed. - const mergelist = flatten([[parent], args, [clone(parent)]]) - merge(mergelist) - } - return out + const { mode, key, parent } = inj + // Ensures $MERGE is removed from parent list (val mode). + let out = NONE + if (M_KEYPRE === mode) { + out = key + } + // Operate after child values have been transformed. + else if (M_KEYPOST === mode) { + out = key + let args = getprop(parent, key) + args = Array.isArray(args) ? args : [args] + // Remove the $MERGE command from a parent map. + inj.setval(NONE) + // Literals in the parent have precedence, but we still merge onto + // the parent object, so that node tree references are not changed. + const mergelist = flatten([[parent], args, [clone(parent)]]) + merge(mergelist) + } + return out } // Convert a node to a list. // Format: ['`$EACH`', '`source-path-of-node`', child-template] const transform_EACH = (inj, _val, _ref, store) => { - const ijname = 'EACH' - if (!checkPlacement(M_VAL, ijname, T_list, inj)) { - return NONE - } - // Remove remaining keys to avoid spurious processing. - slice(inj.keys, 0, 1, true) - // const [err, srcpath, child] = injectorArgs([T_string, T_any], inj) - const [err, srcpath, child] = injectorArgs([T_string, T_any], slice(inj.parent, 1)) - if (NONE !== err) { - inj.errs.push('$' + ijname + ': ' + err) - return NONE - } - // Source data. - const srcstore = getprop(store, inj.base, store) - const src = getpath(srcstore, srcpath, inj) - const srctype = typify(src) - // Create parallel data structures: - // source entries :: child templates - let tcur = [] - let tval = [] - const tkey = getelem(inj.path, -2) - const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) - // Create clones of the child template for each value of the current soruce. - if (0 < (T_list & srctype)) { - tval = items(src, () => clone(child)) - } - else if (0 < (T_map & srctype)) { - tval = items(src, (n => merge([ - clone(child), - // Make a note of the key for $KEY transforms. - { [S_BANNO]: { KEY: n[0] } } - ], 1))) - } - let rval = [] - if (0 < size(tval)) { - tcur = null == src ? NONE : Object.values(src) - const ckey = getelem(inj.path, -2) - const tpath = slice(inj.path, -1) - const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey]) - // Parent structure. - tcur = { [ckey]: tcur } - if (1 < size(tpath)) { - const pkey = getelem(inj.path, -3, S_DTOP) - tcur = { [pkey]: tcur } - dpath.push('$:' + pkey) - } - const tinj = inj.child(0, [ckey]) - tinj.path = tpath - tinj.nodes = slice(inj.nodes, -1) - tinj.parent = getelem(tinj.nodes, -1) - setprop(tinj.parent, ckey, tval) - tinj.val = tval - tinj.dpath = dpath - tinj.dparent = tcur - inject(tval, store, tinj) - rval = tinj.val - } - // _updateAncestors(inj, target, tkey, rval) - setprop(target, tkey, rval) - // Prevent callee from damaging first list entry (since we are in `val` mode). - return rval[0] + const ijname = 'EACH' + if (!checkPlacement(M_VAL, ijname, T_list, inj)) { + return NONE + } + // Remove remaining keys to avoid spurious processing. + slice(inj.keys, 0, 1, true) + // const [err, srcpath, child] = injectorArgs([T_string, T_any], inj) + const [err, srcpath, child] = injectorArgs([T_string, T_any], slice(inj.parent, 1)) + if (NONE !== err) { + inj.errs.push('$' + ijname + ': ' + err) + return NONE + } + // Source data. + const srcstore = getprop(store, inj.base, store) + const src = getpath(srcstore, srcpath, inj) + const srctype = typify(src) + // Create parallel data structures: + // source entries :: child templates + let tcur = [] + let tval = [] + const tkey = getelem(inj.path, -2) + const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) + // Create clones of the child template for each value of the current soruce. + if (0 < (T_list & srctype)) { + tval = items(src, () => clone(child)) + } else if (0 < (T_map & srctype)) { + tval = items(src, (n) => + merge( + [ + clone(child), + // Make a note of the key for $KEY transforms. + { [S_BANNO]: { KEY: n[0] } }, + ], + 1, + ), + ) + } + let rval = [] + if (0 < size(tval)) { + tcur = null == src ? NONE : Object.values(src) + const ckey = getelem(inj.path, -2) + const tpath = slice(inj.path, -1) + const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey]) + // Parent structure. + tcur = { [ckey]: tcur } + if (1 < size(tpath)) { + const pkey = getelem(inj.path, -3, S_DTOP) + tcur = { [pkey]: tcur } + dpath.push('$:' + pkey) + } + const tinj = inj.child(0, [ckey]) + tinj.path = tpath + tinj.nodes = slice(inj.nodes, -1) + tinj.parent = getelem(tinj.nodes, -1) + setprop(tinj.parent, ckey, tval) + tinj.val = tval + tinj.dpath = dpath + tinj.dparent = tcur + inject(tval, store, tinj) + rval = tinj.val + } + // _updateAncestors(inj, target, tkey, rval) + setprop(target, tkey, rval) + // Prevent callee from damaging first list entry (since we are in `val` mode). + return rval[0] } // Convert a node to a map. // Format: { '`$PACK`':['source-path', child-template]} const transform_PACK = (inj, _val, _ref, store) => { - const { mode, key, path, parent, nodes } = inj - const ijname = 'EACH' - if (!checkPlacement(M_KEYPRE, ijname, T_map, inj)) { - return NONE - } - // Get arguments. - const args = getprop(parent, key) - const [err, srcpath, origchildspec] = injectorArgs([T_string, T_any], args) - if (NONE !== err) { - inj.errs.push('$' + ijname + ': ' + err) - return NONE - } - // Find key and target node. - const tkey = getelem(path, -2) - const pathsize = size(path) - const target = getelem(nodes, pathsize - 2, () => getelem(nodes, pathsize - 1)) - // Source data - const srcstore = getprop(store, inj.base, store) - let src = getpath(srcstore, srcpath, inj) - // Prepare source as a list. - if (!islist(src)) { - if (ismap(src)) { - src = items(src, (item) => { - setprop(item[1], S_BANNO, { KEY: item[0] }) - return item[1] - }) - } - else { - src = NONE - } - } - if (null == src) { - return NONE - } - // Get keypath. - const keypath = getprop(origchildspec, S_BKEY) - const childspec = delprop(origchildspec, S_BKEY) - const child = getprop(childspec, S_BVAL, childspec) - // Build parallel target object. - let tval = {} - items(src, (item) => { - const srckey = item[0] - const srcnode = item[1] - let key = srckey - if (NONE !== keypath) { - if (keypath.startsWith('`')) { - key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1)) - } - else { - key = getpath(srcnode, keypath, inj) - } - } - const tchild = clone(child) - setprop(tval, key, tchild) - const anno = getprop(srcnode, S_BANNO) - if (NONE === anno) { - delprop(tchild, S_BANNO) - } - else { - setprop(tchild, S_BANNO, anno) - } - }) - let rval = {} - if (!isempty(tval)) { - // Build parallel source object. - let tsrc = {} - src.reduce((a, n, i) => { - let kn = null == keypath ? i : - keypath.startsWith('`') ? - inject(keypath, merge([{}, store, { $TOP: n }], 1)) : - getpath(n, keypath, inj) - setprop(a, kn, n) - return a - }, tsrc) - const tpath = slice(inj.path, -1) - const ckey = getelem(inj.path, -2) - const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey]) - let tcur = { [ckey]: tsrc } - if (1 < size(tpath)) { - const pkey = getelem(inj.path, -3, S_DTOP) - tcur = { [pkey]: tcur } - dpath.push('$:' + pkey) - } - const tinj = inj.child(0, [ckey]) - tinj.path = tpath - tinj.nodes = slice(inj.nodes, -1) - tinj.parent = getelem(tinj.nodes, -1) - tinj.val = tval - tinj.dpath = dpath - tinj.dparent = tcur - inject(tval, store, tinj) - rval = tinj.val - } - // _updateAncestors(inj, target, tkey, rval) - setprop(target, tkey, rval) - // Drop transform key. + const { key, path, parent, nodes } = inj + const ijname = 'EACH' + if (!checkPlacement(M_KEYPRE, ijname, T_map, inj)) { + return NONE + } + // Get arguments. + const args = getprop(parent, key) + const [err, srcpath, origchildspec] = injectorArgs([T_string, T_any], args) + if (NONE !== err) { + inj.errs.push('$' + ijname + ': ' + err) + return NONE + } + // Find key and target node. + const tkey = getelem(path, -2) + const pathsize = size(path) + const target = getelem(nodes, pathsize - 2, () => getelem(nodes, pathsize - 1)) + // Source data + const srcstore = getprop(store, inj.base, store) + let src = getpath(srcstore, srcpath, inj) + // Prepare source as a list. + if (!islist(src)) { + if (ismap(src)) { + src = items(src, (item) => { + setprop(item[1], S_BANNO, { KEY: item[0] }) + return item[1] + }) + } else { + src = NONE + } + } + if (null == src) { return NONE + } + // Get keypath. + const keypath = getprop(origchildspec, S_BKEY) + const childspec = delprop(origchildspec, S_BKEY) + const child = getprop(childspec, S_BVAL, childspec) + // Build parallel target object. + let tval = {} + items(src, (item) => { + const srckey = item[0] + const srcnode = item[1] + let key = srckey + if (NONE !== keypath) { + if (keypath.startsWith('`')) { + key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1)) + } else { + key = getpath(srcnode, keypath, inj) + } + } + const tchild = clone(child) + setprop(tval, key, tchild) + const anno = getprop(srcnode, S_BANNO) + if (NONE === anno) { + delprop(tchild, S_BANNO) + } else { + setprop(tchild, S_BANNO, anno) + } + }) + let rval = {} + if (!isempty(tval)) { + // Build parallel source object. + let tsrc = {} + src.reduce((a, n, i) => { + let kn = + null == keypath + ? i + : keypath.startsWith('`') + ? inject(keypath, merge([{}, store, { $TOP: n }], 1)) + : getpath(n, keypath, inj) + setprop(a, kn, n) + return a + }, tsrc) + const tpath = slice(inj.path, -1) + const ckey = getelem(inj.path, -2) + const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey]) + let tcur = { [ckey]: tsrc } + if (1 < size(tpath)) { + const pkey = getelem(inj.path, -3, S_DTOP) + tcur = { [pkey]: tcur } + dpath.push('$:' + pkey) + } + const tinj = inj.child(0, [ckey]) + tinj.path = tpath + tinj.nodes = slice(inj.nodes, -1) + tinj.parent = getelem(tinj.nodes, -1) + tinj.val = tval + tinj.dpath = dpath + tinj.dparent = tcur + inject(tval, store, tinj) + rval = tinj.val + } + // _updateAncestors(inj, target, tkey, rval) + setprop(target, tkey, rval) + // Drop transform key. + return NONE } // TODO: not found ref should removed key (setprop NONE) // Reference original spec (enables recursice transformations) // Format: ['`$REF`', '`spec-path`'] const transform_REF = (inj, val, _ref, store) => { - const { nodes } = inj - if (M_VAL !== inj.mode) { - return NONE - } - // Get arguments: ['`$REF`', 'ref-path']. - const refpath = getprop(inj.parent, 1) - inj.keyI = size(inj.keys) - // Spec reference. - const spec = getprop(store, S_DSPEC)() - const dpath = slice(inj.path, 1) - const ref = getpath(spec, refpath, { - // TODO: test relative refs - // dpath: inj.path.slice(1), - dpath, - // dparent: getpath(spec, inj.path.slice(1)) - dparent: getpath(spec, dpath), + const { nodes } = inj + if (M_VAL !== inj.mode) { + return NONE + } + // Get arguments: ['`$REF`', 'ref-path']. + const refpath = getprop(inj.parent, 1) + inj.keyI = size(inj.keys) + // Spec reference. + const spec = getprop(store, S_DSPEC)() + const dpath = slice(inj.path, 1) + const ref = getpath(spec, refpath, { + // TODO: test relative refs + // dpath: inj.path.slice(1), + dpath, + // dparent: getpath(spec, inj.path.slice(1)) + dparent: getpath(spec, dpath), + }) + let hasSubRef = false + if (isnode(ref)) { + walk(ref, (_k, v) => { + if ('`$REF`' === v) { + hasSubRef = true + } + return v }) - let hasSubRef = false - if (isnode(ref)) { - walk(ref, (_k, v) => { - if ('`$REF`' === v) { - hasSubRef = true - } - return v - }) - } - let tref = clone(ref) - const cpath = slice(inj.path, -3) - const tpath = slice(inj.path, -1) - let tcur = getpath(store, cpath) - let tval = getpath(store, tpath) - let rval = NONE - if (!hasSubRef || NONE !== tval) { - const tinj = inj.child(0, [getelem(tpath, -1)]) - tinj.path = tpath - tinj.nodes = slice(inj.nodes, -1) - tinj.parent = getelem(nodes, -2) - tinj.val = tref - tinj.dpath = flatten([cpath]) - tinj.dparent = tcur - inject(tref, store, tinj) - rval = tinj.val - } - else { - rval = NONE - } - const grandparent = inj.setval(rval, 2) - if (islist(grandparent) && inj.prior) { - inj.prior.keyI-- - } - return val + } + let tref = clone(ref) + const cpath = slice(inj.path, -3) + const tpath = slice(inj.path, -1) + let tcur = getpath(store, cpath) + let tval = getpath(store, tpath) + let rval = NONE + if (!hasSubRef || NONE !== tval) { + const tinj = inj.child(0, [getelem(tpath, -1)]) + tinj.path = tpath + tinj.nodes = slice(inj.nodes, -1) + tinj.parent = getelem(nodes, -2) + tinj.val = tref + tinj.dpath = flatten([cpath]) + tinj.dparent = tcur + inject(tref, store, tinj) + rval = tinj.val + } else { + rval = NONE + } + const grandparent = inj.setval(rval, 2) + if (islist(grandparent) && inj.prior) { + inj.prior.keyI-- + } + return val } const transform_FORMAT = (inj, _val, _ref, store) => { - // console.log('FORMAT-START', inj, _val) - // Remove remaining keys to avoid spurious processing. - slice(inj.keys, 0, 1, true) - if (M_VAL !== inj.mode) { - return NONE - } - // Get arguments: ['`$FORMAT`', 'name', child]. - // TODO: EACH and PACK should accept customm functions too - const name = getprop(inj.parent, 1) - const child = getprop(inj.parent, 2) - // Source data. - const tkey = getelem(inj.path, -2) - const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) - const cinj = injectChild(child, store, inj) - const resolved = cinj.val - let formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name) - if (NONE === formatter) { - inj.errs.push('$FORMAT: unknown format: ' + name + '.') - return NONE - } - let out = walk(resolved, formatter) - setprop(target, tkey, out) - // _updateAncestors(inj, target, tkey, out) - return out + // console.log('FORMAT-START', inj, _val) + // Remove remaining keys to avoid spurious processing. + slice(inj.keys, 0, 1, true) + if (M_VAL !== inj.mode) { + return NONE + } + // Get arguments: ['`$FORMAT`', 'name', child]. + // TODO: EACH and PACK should accept customm functions too + const name = getprop(inj.parent, 1) + const child = getprop(inj.parent, 2) + // Source data. + const tkey = getelem(inj.path, -2) + const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) + const cinj = injectChild(child, store, inj) + const resolved = cinj.val + let formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name) + if (NONE === formatter) { + inj.errs.push('$FORMAT: unknown format: ' + name + '.') + return NONE + } + let out = walk(resolved, formatter) + setprop(target, tkey, out) + // _updateAncestors(inj, target, tkey, out) + return out } const FORMATTER = { - identity: (_k, v) => v, - upper: (_k, v) => isnode(v) ? v : ('' + v).toUpperCase(), - lower: (_k, v) => isnode(v) ? v : ('' + v).toLowerCase(), - string: (_k, v) => isnode(v) ? v : ('' + v), - number: (_k, v) => { - if (isnode(v)) { - return v - } - else { - let n = Number(v) - if (isNaN(n)) { - n = 0 - } - return n - } - }, - integer: (_k, v) => { - if (isnode(v)) { - return v - } - else { - let n = Number(v) - if (isNaN(n)) { - n = 0 - } - return n | 0 - } - }, - concat: (k, v) => null == k && islist(v) ? join(items(v, (n => isnode(n[1]) ? S_MT : (S_MT + n[1]))), S_MT) : v + identity: (_k, v) => v, + upper: (_k, v) => (isnode(v) ? v : ('' + v).toUpperCase()), + lower: (_k, v) => (isnode(v) ? v : ('' + v).toLowerCase()), + string: (_k, v) => (isnode(v) ? v : '' + v), + number: (_k, v) => { + if (isnode(v)) { + return v + } else { + let n = Number(v) + if (isNaN(n)) { + n = 0 + } + return n + } + }, + integer: (_k, v) => { + if (isnode(v)) { + return v + } else { + let n = Number(v) + if (isNaN(n)) { + n = 0 + } + return n | 0 + } + }, + concat: (k, v) => + null == k && islist(v) + ? join( + items(v, (n) => (isnode(n[1]) ? S_MT : S_MT + n[1])), + S_MT, + ) + : v, } const transform_APPLY = (inj, _val, _ref, store) => { - const ijname = 'APPLY' - if (!checkPlacement(M_VAL, ijname, T_list, inj)) { - return NONE - } - // const [err, apply, child] = injectorArgs([T_function, T_any], inj) - const [err, apply, child] = injectorArgs([T_function, T_any], slice(inj.parent, 1)) - if (NONE !== err) { - inj.errs.push('$' + ijname + ': ' + err) - return NONE - } - const tkey = getelem(inj.path, -2) - const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) - const cinj = injectChild(child, store, inj) - const resolved = cinj.val - const out = apply(resolved, store, cinj) - setprop(target, tkey, out) - return out + const ijname = 'APPLY' + if (!checkPlacement(M_VAL, ijname, T_list, inj)) { + return NONE + } + // const [err, apply, child] = injectorArgs([T_function, T_any], inj) + const [err, apply, child] = injectorArgs([T_function, T_any], slice(inj.parent, 1)) + if (NONE !== err) { + inj.errs.push('$' + ijname + ': ' + err) + return NONE + } + const tkey = getelem(inj.path, -2) + const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) + const cinj = injectChild(child, store, inj) + const resolved = cinj.val + const out = apply(resolved, store, cinj) + setprop(target, tkey, out) + return out } // Transform data using spec. // Only operates on static JSON-like data. // Arrays are treated as if they are objects with indices as keys. -function transform(data, // Source data to transform into new data (original not mutated) -spec, // Transform specification; output follows this shape -injdef) { - // Clone the spec so that the clone can be modified in place as the transform result. - const origspec = spec - spec = clone(origspec) - const extra = injdef?.extra - const collect = null != injdef?.errs - const errs = injdef?.errs || [] - const extraTransforms = {} - const extraData = null == extra ? NONE : items(extra) - .reduce((a, n) => (n[0].startsWith(S_DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {}) - const dataClone = merge([ - isempty(extraData) ? NONE : clone(extraData), - clone(data), - ]) - // Define a top level store that provides transform operations. - const store = merge([ - { - // The inject function recognises this special location for the root of the source data. - // NOTE: to escape data that contains "`$FOO`" keys at the top level, - // place that data inside a holding map: { myholder: mydata }. - $TOP: dataClone, - $SPEC: () => origspec, - // Escape backtick (this also works inside backticks). - $BT: () => S_BT, - // Escape dollar sign (this also works inside backticks). - $DS: () => S_DS, - // Insert current date and time as an ISO string. - $WHEN: () => new Date().toISOString(), - $DELETE: transform_DELETE, - $COPY: transform_COPY, - $KEY: transform_KEY, - $ANNO: transform_ANNO, - $MERGE: transform_MERGE, - $EACH: transform_EACH, - $PACK: transform_PACK, - $REF: transform_REF, - $FORMAT: transform_FORMAT, - $APPLY: transform_APPLY, - }, - // Custom extra transforms, if any. - extraTransforms, - { - $ERRS: errs, - } - ], 1) - const out = inject(spec, store, injdef) - const generr = (0 < size(errs) && !collect) - if (generr) { - throw new Error(join(errs, ' | ')) - } - return out +function transform( + data, // Source data to transform into new data (original not mutated) + spec, // Transform specification; output follows this shape + injdef, +) { + // Clone the spec so that the clone can be modified in place as the transform result. + const origspec = spec + spec = clone(origspec) + const extra = injdef?.extra + const collect = null != injdef?.errs + const errs = injdef?.errs || [] + const extraTransforms = {} + const extraData = + null == extra + ? NONE + : items(extra).reduce( + (a, n) => (n[0].startsWith(S_DS) ? (extraTransforms[n[0]] = n[1]) : (a[n[0]] = n[1]), a), + {}, + ) + const dataClone = merge([isempty(extraData) ? NONE : clone(extraData), clone(data)]) + // Define a top level store that provides transform operations. + const store = merge( + [ + { + // The inject function recognises this special location for the root of the source data. + // NOTE: to escape data that contains "`$FOO`" keys at the top level, + // place that data inside a holding map: { myholder: mydata }. + $TOP: dataClone, + $SPEC: () => origspec, + // Escape backtick (this also works inside backticks). + $BT: () => S_BT, + // Escape dollar sign (this also works inside backticks). + $DS: () => S_DS, + // Insert current date and time as an ISO string. + $WHEN: () => new Date().toISOString(), + $DELETE: transform_DELETE, + $COPY: transform_COPY, + $KEY: transform_KEY, + $ANNO: transform_ANNO, + $MERGE: transform_MERGE, + $EACH: transform_EACH, + $PACK: transform_PACK, + $REF: transform_REF, + $FORMAT: transform_FORMAT, + $APPLY: transform_APPLY, + }, + // Custom extra transforms, if any. + extraTransforms, + { + $ERRS: errs, + }, + ], + 1, + ) + const out = inject(spec, store, injdef) + const generr = 0 < size(errs) && !collect + if (generr) { + throw new Error(join(errs, ' | ')) + } + return out } // A required string value. NOTE: Rejects empty strings. const validate_STRING = (inj) => { - let out = getprop(inj.dparent, inj.key) - const t = typify(out) - if (0 === (T_string & t)) { - let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010') - inj.errs.push(msg) - return NONE - } - if (S_MT === out) { - let msg = 'Empty string at ' + pathify(inj.path, 1) - inj.errs.push(msg) - return NONE - } - return out + let out = getprop(inj.dparent, inj.key) + const t = typify(out) + if (0 === (T_string & t)) { + let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010') + inj.errs.push(msg) + return NONE + } + if (S_MT === out) { + let msg = 'Empty string at ' + pathify(inj.path, 1) + inj.errs.push(msg) + return NONE + } + return out } const validate_TYPE = (inj, _val, ref) => { - const tname = slice(ref, 1).toLowerCase() - const typev = 1 << (31 - TYPENAME.indexOf(tname)) - let out = getprop(inj.dparent, inj.key) - const t = typify(out) - // console.log('TYPE', tname, typev, tn(typev), 'O=', t, tn(t), out, 'C=', t & typev) - if (0 === (t & typev)) { - inj.errs.push(_invalidTypeMsg(inj.path, tname, t, out, 'V1001')) - return NONE - } - return out + const tname = slice(ref, 1).toLowerCase() + const typev = 1 << (31 - TYPENAME.indexOf(tname)) + let out = getprop(inj.dparent, inj.key) + const t = typify(out) + // console.log('TYPE', tname, typev, tn(typev), 'O=', t, tn(t), out, 'C=', t & typev) + if (0 === (t & typev)) { + inj.errs.push(_invalidTypeMsg(inj.path, tname, t, out, 'V1001')) + return NONE + } + return out } // Allow any value. const validate_ANY = (inj) => { - let out = getprop(inj.dparent, inj.key) - return out + let out = getprop(inj.dparent, inj.key) + return out } // Specify child values for map or list. // Map syntax: {'`$CHILD`': child-template } // List syntax: ['`$CHILD`', child-template ] const validate_CHILD = (inj) => { - const { mode, key, parent, keys, path } = inj - // Setup data structures for validation by cloning child template. - // Map syntax. - if (M_KEYPRE === mode) { - const childtm = getprop(parent, key) - // Get corresponding current object. - const pkey = getelem(path, -2) - let tval = getprop(inj.dparent, pkey) - if (NONE == tval) { - tval = {} - } - else if (!ismap(tval)) { - inj.errs.push(_invalidTypeMsg(slice(inj.path, -1), S_object, typify(tval), tval), 'V0220') - return NONE - } - const ckeys = keysof(tval) - for (let ckey of ckeys) { - setprop(parent, ckey, clone(childtm)) - // NOTE: modifying inj! This extends the child value loop in inject. - keys.push(ckey) - } - // Remove $CHILD to cleanup ouput. - inj.setval(NONE) - return NONE - } - // List syntax. - if (M_VAL === mode) { - if (!islist(parent)) { - // $CHILD was not inside a list. - inj.errs.push('Invalid $CHILD as value') - return NONE - } - const childtm = getprop(parent, 1) - if (NONE === inj.dparent) { - // Empty list as default. - // parent.length = 0 - slice(parent, 0, 0, true) - return NONE - } - if (!islist(inj.dparent)) { - const msg = _invalidTypeMsg(slice(inj.path, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230') - inj.errs.push(msg) - inj.keyI = size(parent) - return inj.dparent - } - // Clone children abd reset inj key index. - // The inject child loop will now iterate over the cloned children, - // validating them againt the current list values. - items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm))) - slice(parent, 0, inj.dparent.length, true) - inj.keyI = 0 - const out = getprop(inj.dparent, 0) - return out - } + const { mode, key, parent, keys, path } = inj + // Setup data structures for validation by cloning child template. + // Map syntax. + if (M_KEYPRE === mode) { + const childtm = getprop(parent, key) + // Get corresponding current object. + const pkey = getelem(path, -2) + let tval = getprop(inj.dparent, pkey) + if (NONE == tval) { + tval = {} + } else if (!ismap(tval)) { + inj.errs.push(_invalidTypeMsg(slice(inj.path, -1), S_object, typify(tval), tval), 'V0220') + return NONE + } + const ckeys = keysof(tval) + for (let ckey of ckeys) { + setprop(parent, ckey, clone(childtm)) + // NOTE: modifying inj! This extends the child value loop in inject. + keys.push(ckey) + } + // Remove $CHILD to cleanup ouput. + inj.setval(NONE) return NONE + } + // List syntax. + if (M_VAL === mode) { + if (!islist(parent)) { + // $CHILD was not inside a list. + inj.errs.push('Invalid $CHILD as value') + return NONE + } + const childtm = getprop(parent, 1) + if (NONE === inj.dparent) { + // Empty list as default. + // parent.length = 0 + slice(parent, 0, 0, true) + return NONE + } + if (!islist(inj.dparent)) { + const msg = _invalidTypeMsg( + slice(inj.path, -1), + S_list, + typify(inj.dparent), + inj.dparent, + 'V0230', + ) + inj.errs.push(msg) + inj.keyI = size(parent) + return inj.dparent + } + // Clone children abd reset inj key index. + // The inject child loop will now iterate over the cloned children, + // validating them againt the current list values. + items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm))) + slice(parent, 0, inj.dparent.length, true) + inj.keyI = 0 + const out = getprop(inj.dparent, 0) + return out + } + return NONE } // TODO: implement SOME, ALL // FIX: ONE should mean exactly one, not at least one (=SOME) @@ -1579,167 +1599,201 @@ const validate_CHILD = (inj) => { // Match at least one of the specified shapes. // Syntax: ['`$ONE`', alt0, alt1, ...] const validate_ONE = (inj, _val, _ref, store) => { - const { mode, parent, keyI } = inj - // Only operate in val mode, since parent is a list. - if (M_VAL === mode) { - if (!islist(parent) || 0 !== keyI) { - inj.errs.push('The $ONE validator at field ' + - pathify(inj.path, 1, 1) + - ' must be the first element of an array.') - return - } - inj.keyI = size(inj.keys) - // Clean up structure, replacing [$ONE, ...] with current - inj.setval(inj.dparent, 2) - inj.path = slice(inj.path, -1) - inj.key = getelem(inj.path, -1) - let tvals = slice(parent, 1) - if (0 === size(tvals)) { - inj.errs.push('The $ONE validator at field ' + - pathify(inj.path, 1, 1) + - ' must have at least one argument.') - return - } - // See if we can find a match. - for (let tval of tvals) { - // If match, then errs.length = 0 - let terrs = [] - const vstore = merge([{}, store], 1) - vstore.$TOP = inj.dparent - const vcurrent = validate(inj.dparent, tval, { - extra: vstore, - errs: terrs, - meta: inj.meta, - }) - inj.setval(vcurrent, -2) - // Accept current value if there was a match - if (0 === size(terrs)) { - return - } - } - // There was no match. - const valdesc = replace(join(items(tvals, (n) => stringify(n[1])), ', '), R_TRANSFORM_NAME, (_m, p1) => p1.toLowerCase()) - inj.errs.push(_invalidTypeMsg(inj.path, (1 < size(tvals) ? 'one of ' : '') + valdesc, typify(inj.dparent), inj.dparent, 'V0210')) + const { mode, parent, keyI } = inj + // Only operate in val mode, since parent is a list. + if (M_VAL === mode) { + if (!islist(parent) || 0 !== keyI) { + inj.errs.push( + 'The $ONE validator at field ' + + pathify(inj.path, 1, 1) + + ' must be the first element of an array.', + ) + return } + inj.keyI = size(inj.keys) + // Clean up structure, replacing [$ONE, ...] with current + inj.setval(inj.dparent, 2) + inj.path = slice(inj.path, -1) + inj.key = getelem(inj.path, -1) + let tvals = slice(parent, 1) + if (0 === size(tvals)) { + inj.errs.push( + 'The $ONE validator at field ' + + pathify(inj.path, 1, 1) + + ' must have at least one argument.', + ) + return + } + // See if we can find a match. + for (let tval of tvals) { + // If match, then errs.length = 0 + let terrs = [] + const vstore = merge([{}, store], 1) + vstore.$TOP = inj.dparent + const vcurrent = validate(inj.dparent, tval, { + extra: vstore, + errs: terrs, + meta: inj.meta, + }) + inj.setval(vcurrent, -2) + // Accept current value if there was a match + if (0 === size(terrs)) { + return + } + } + // There was no match. + const valdesc = replace( + join( + items(tvals, (n) => stringify(n[1])), + ', ', + ), + R_TRANSFORM_NAME, + (_m, p1) => p1.toLowerCase(), + ) + inj.errs.push( + _invalidTypeMsg( + inj.path, + (1 < size(tvals) ? 'one of ' : '') + valdesc, + typify(inj.dparent), + inj.dparent, + 'V0210', + ), + ) + } } const validate_EXACT = (inj) => { - const { mode, parent, key, keyI } = inj - // Only operate in val mode, since parent is a list. - if (M_VAL === mode) { - if (!islist(parent) || 0 !== keyI) { - inj.errs.push('The $EXACT validator at field ' + - pathify(inj.path, 1, 1) + - ' must be the first element of an array.') - return - } - inj.keyI = size(inj.keys) - // Clean up structure, replacing [$EXACT, ...] with current data parent - inj.setval(inj.dparent, 2) - // inj.path = slice(inj.path, 0, size(inj.path) - 1) - inj.path = slice(inj.path, 0, -1) - inj.key = getelem(inj.path, -1) - let tvals = slice(parent, 1) - if (0 === size(tvals)) { - inj.errs.push('The $EXACT validator at field ' + - pathify(inj.path, 1, 1) + - ' must have at least one argument.') - return - } - // See if we can find an exact value match. - let currentstr = undefined - for (let tval of tvals) { - let exactmatch = tval === inj.dparent - if (!exactmatch && isnode(tval)) { - currentstr = undefined === currentstr ? stringify(inj.dparent) : currentstr - const tvalstr = stringify(tval) - exactmatch = tvalstr === currentstr - } - if (exactmatch) { - return - } - } - // There was no match. - const valdesc = replace(join(items(tvals, (n) => stringify(n[1])), ', '), R_TRANSFORM_NAME, (_m, p1) => p1.toLowerCase()) - inj.errs.push(_invalidTypeMsg(inj.path, (1 < size(inj.path) ? '' : 'value ') + - 'exactly equal to ' + (1 === size(tvals) ? '' : 'one of ') + valdesc, typify(inj.dparent), inj.dparent, 'V0110')) - } - else { - delprop(parent, key) + const { mode, parent, key, keyI } = inj + // Only operate in val mode, since parent is a list. + if (M_VAL === mode) { + if (!islist(parent) || 0 !== keyI) { + inj.errs.push( + 'The $EXACT validator at field ' + + pathify(inj.path, 1, 1) + + ' must be the first element of an array.', + ) + return } + inj.keyI = size(inj.keys) + // Clean up structure, replacing [$EXACT, ...] with current data parent + inj.setval(inj.dparent, 2) + // inj.path = slice(inj.path, 0, size(inj.path) - 1) + inj.path = slice(inj.path, 0, -1) + inj.key = getelem(inj.path, -1) + let tvals = slice(parent, 1) + if (0 === size(tvals)) { + inj.errs.push( + 'The $EXACT validator at field ' + + pathify(inj.path, 1, 1) + + ' must have at least one argument.', + ) + return + } + // See if we can find an exact value match. + let currentstr = undefined + for (let tval of tvals) { + let exactmatch = tval === inj.dparent + if (!exactmatch && isnode(tval)) { + currentstr = undefined === currentstr ? stringify(inj.dparent) : currentstr + const tvalstr = stringify(tval) + exactmatch = tvalstr === currentstr + } + if (exactmatch) { + return + } + } + // There was no match. + const valdesc = replace( + join( + items(tvals, (n) => stringify(n[1])), + ', ', + ), + R_TRANSFORM_NAME, + (_m, p1) => p1.toLowerCase(), + ) + inj.errs.push( + _invalidTypeMsg( + inj.path, + (1 < size(inj.path) ? '' : 'value ') + + 'exactly equal to ' + + (1 === size(tvals) ? '' : 'one of ') + + valdesc, + typify(inj.dparent), + inj.dparent, + 'V0110', + ), + ) + } else { + delprop(parent, key) + } } // This is the "modify" argument to inject. Use this to perform // generic validation. Runs *after* any special commands. const _validation = (pval, key, parent, inj) => { - if (NONE === inj) { - return - } - if (SKIP === pval) { - return - } - // select needs exact matches - const exact = getprop(inj.meta, S_BEXACT, false) - // Current val to verify. - const cval = getprop(inj.dparent, key) - if (NONE === inj || (!exact && NONE === cval)) { - return - } - const ptype = typify(pval) - // Delete any special commands remaining. - if (0 < (T_string & ptype) && pval.includes(S_DS)) { - return - } - const ctype = typify(cval) - // Type mismatch. - if (ptype !== ctype && NONE !== pval) { - inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010')) - return - } - if (ismap(cval)) { - if (!ismap(pval)) { - inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020')) - return - } - const ckeys = keysof(cval) - const pkeys = keysof(pval) - // Empty spec object {} means object can be open (any keys). - if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) { - const badkeys = [] - for (let ckey of ckeys) { - if (!haskey(pval, ckey)) { - badkeys.push(ckey) - } - } - // Closed object, so reject extra keys not in shape. - if (0 < size(badkeys)) { - const msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ') - inj.errs.push(msg) - } - } - else { - // Object is open, so merge in extra keys. - merge([pval, cval]) - if (isnode(pval)) { - delprop(pval, '`$OPEN`') - } - } - } - else if (islist(cval)) { - if (!islist(pval)) { - inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030')) - } - } - else if (exact) { - if (cval !== pval) { - const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT - inj.errs.push('Value ' + pathmsg + cval + - ' should equal ' + pval + S_DT) - } - } - else { - // Spec value was a default, copy over data - setprop(parent, key, cval) - } + if (NONE === inj) { return + } + if (SKIP === pval) { + return + } + // select needs exact matches + const exact = getprop(inj.meta, S_BEXACT, false) + // Current val to verify. + const cval = getprop(inj.dparent, key) + if (NONE === inj || (!exact && NONE === cval)) { + return + } + const ptype = typify(pval) + // Delete any special commands remaining. + if (0 < (T_string & ptype) && pval.includes(S_DS)) { + return + } + const ctype = typify(cval) + // Type mismatch. + if (ptype !== ctype && NONE !== pval) { + inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010')) + return + } + if (ismap(cval)) { + if (!ismap(pval)) { + inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020')) + return + } + const ckeys = keysof(cval) + const pkeys = keysof(pval) + // Empty spec object {} means object can be open (any keys). + if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) { + const badkeys = [] + for (let ckey of ckeys) { + if (!haskey(pval, ckey)) { + badkeys.push(ckey) + } + } + // Closed object, so reject extra keys not in shape. + if (0 < size(badkeys)) { + const msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ') + inj.errs.push(msg) + } + } else { + // Object is open, so merge in extra keys. + merge([pval, cval]) + if (isnode(pval)) { + delprop(pval, '`$OPEN`') + } + } + } else if (islist(cval)) { + if (!islist(pval)) { + inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030')) + } + } else if (exact) { + if (cval !== pval) { + const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT + inj.errs.push('Value ' + pathmsg + cval + ' should equal ' + pval + S_DT) + } + } else { + // Spec value was a default, copy over data + setprop(parent, key, cval) + } + return } // Validate a data structure against a shape specification. The shape // specification follows the "by example" principle. Plain data in @@ -1751,305 +1805,329 @@ const _validation = (pval, key, parent, inj) => { // provided to specify required values. Thus shape {a:'`$STRING`'} // validates {a:'A'} but not {a:1}. Empty map or list means the node // is open, and if missing an empty default is inserted. -function validate(data, // Source data to transform into new data (original not mutated) -spec, // Transform specification; output follows this shape -injdef) { - const extra = injdef?.extra - const collect = null != injdef?.errs - const errs = injdef?.errs || [] - const store = merge([ - { - // Remove the transform commands. - $DELETE: null, - $COPY: null, - $KEY: null, - $META: null, - $MERGE: null, - $EACH: null, - $PACK: null, - $STRING: validate_STRING, - $NUMBER: validate_TYPE, - $INTEGER: validate_TYPE, - $DECIMAL: validate_TYPE, - $BOOLEAN: validate_TYPE, - $NULL: validate_TYPE, - $NIL: validate_TYPE, - $MAP: validate_TYPE, - $LIST: validate_TYPE, - $FUNCTION: validate_TYPE, - $INSTANCE: validate_TYPE, - $ANY: validate_ANY, - $CHILD: validate_CHILD, - $ONE: validate_ONE, - $EXACT: validate_EXACT, - }, - getdef(extra, {}), - // A special top level value to collect errors. - // NOTE: collecterrs parameter always wins. - { - $ERRS: errs, - } - ], 1) - let meta = getprop(injdef, 'meta', {}) - setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false)) - const out = transform(data, spec, { - meta, - extra: store, - modify: _validation, - handler: _validatehandler, - errs, - }) - const generr = (0 < size(errs) && !collect) - if (generr) { - throw new Error(join(errs, ' | ')) - } - return out +function validate( + data, // Source data to transform into new data (original not mutated) + spec, // Transform specification; output follows this shape + injdef, +) { + const extra = injdef?.extra + const collect = null != injdef?.errs + const errs = injdef?.errs || [] + const store = merge( + [ + { + // Remove the transform commands. + $DELETE: null, + $COPY: null, + $KEY: null, + $META: null, + $MERGE: null, + $EACH: null, + $PACK: null, + $STRING: validate_STRING, + $NUMBER: validate_TYPE, + $INTEGER: validate_TYPE, + $DECIMAL: validate_TYPE, + $BOOLEAN: validate_TYPE, + $NULL: validate_TYPE, + $NIL: validate_TYPE, + $MAP: validate_TYPE, + $LIST: validate_TYPE, + $FUNCTION: validate_TYPE, + $INSTANCE: validate_TYPE, + $ANY: validate_ANY, + $CHILD: validate_CHILD, + $ONE: validate_ONE, + $EXACT: validate_EXACT, + }, + getdef(extra, {}), + // A special top level value to collect errors. + // NOTE: collecterrs parameter always wins. + { + $ERRS: errs, + }, + ], + 1, + ) + let meta = getprop(injdef, 'meta', {}) + setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false)) + const out = transform(data, spec, { + meta, + extra: store, + modify: _validation, + handler: _validatehandler, + errs, + }) + const generr = 0 < size(errs) && !collect + if (generr) { + throw new Error(join(errs, ' | ')) + } + return out } const select_AND = (inj, _val, _ref, store) => { - if (M_KEYPRE === inj.mode) { - const terms = getprop(inj.parent, inj.key) - const ppath = slice(inj.path, -1) - const point = getpath(store, ppath) - const vstore = merge([{}, store], 1) - vstore.$TOP = point - for (let term of terms) { - let terrs = [] - validate(point, term, { - extra: vstore, - errs: terrs, - meta: inj.meta, - }) - if (0 != size(terrs)) { - inj.errs.push('AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)) - } - } + if (M_KEYPRE === inj.mode) { + const terms = getprop(inj.parent, inj.key) + const ppath = slice(inj.path, -1) + const point = getpath(store, ppath) + const vstore = merge([{}, store], 1) + vstore.$TOP = point + for (let term of terms) { + let terrs = [] + validate(point, term, { + extra: vstore, + errs: terrs, + meta: inj.meta, + }) + if (0 != size(terrs)) { + inj.errs.push( + 'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms), + ) + } + } + const gkey = getelem(inj.path, -2) + const gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + } +} +const select_OR = (inj, _val, _ref, store) => { + if (M_KEYPRE === inj.mode) { + const terms = getprop(inj.parent, inj.key) + const ppath = slice(inj.path, -1) + const point = getpath(store, ppath) + const vstore = merge([{}, store], 1) + vstore.$TOP = point + for (let term of terms) { + let terrs = [] + validate(point, term, { + extra: vstore, + errs: terrs, + meta: inj.meta, + }) + if (0 === size(terrs)) { const gkey = getelem(inj.path, -2) const gp = getelem(inj.nodes, -2) setprop(gp, gkey, point) + return + } } -} -const select_OR = (inj, _val, _ref, store) => { - if (M_KEYPRE === inj.mode) { - const terms = getprop(inj.parent, inj.key) - const ppath = slice(inj.path, -1) - const point = getpath(store, ppath) - const vstore = merge([{}, store], 1) - vstore.$TOP = point - for (let term of terms) { - let terrs = [] - validate(point, term, { - extra: vstore, - errs: terrs, - meta: inj.meta, - }) - if (0 === size(terrs)) { - const gkey = getelem(inj.path, -2) - const gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - return - } - } - inj.errs.push('OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)) - } + inj.errs.push('OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)) + } } const select_NOT = (inj, _val, _ref, store) => { - if (M_KEYPRE === inj.mode) { - const term = getprop(inj.parent, inj.key) - const ppath = slice(inj.path, -1) - const point = getpath(store, ppath) - const vstore = merge([{}, store], 1) - vstore.$TOP = point - let terrs = [] - validate(point, term, { - extra: vstore, - errs: terrs, - meta: inj.meta, - }) - if (0 == size(terrs)) { - inj.errs.push('NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term)) - } - const gkey = getelem(inj.path, -2) - const gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) + if (M_KEYPRE === inj.mode) { + const term = getprop(inj.parent, inj.key) + const ppath = slice(inj.path, -1) + const point = getpath(store, ppath) + const vstore = merge([{}, store], 1) + vstore.$TOP = point + let terrs = [] + validate(point, term, { + extra: vstore, + errs: terrs, + meta: inj.meta, + }) + if (0 == size(terrs)) { + inj.errs.push('NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term)) } + const gkey = getelem(inj.path, -2) + const gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + } } const select_CMP = (inj, _val, ref, store) => { - if (M_KEYPRE === inj.mode) { - const term = getprop(inj.parent, inj.key) - // const src = getprop(store, inj.base, store) - const gkey = getelem(inj.path, -2) - // const tval = getprop(src, gkey) - const ppath = slice(inj.path, -1) - const point = getpath(store, ppath) - let pass = false - if ('$GT' === ref && point > term) { - pass = true - } - else if ('$LT' === ref && point < term) { - pass = true - } - else if ('$GTE' === ref && point >= term) { - pass = true - } - else if ('$LTE' === ref && point <= term) { - pass = true - } - else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) { - pass = true - } - if (pass) { - // Update spec to match found value so that _validate does not complain. - const gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - } - else { - inj.errs.push('CMP: ' + pathify(ppath) + S_VIZ + stringify(point) + - ' fail:' + ref + ' ' + stringify(term)) - } - } - return NONE + if (M_KEYPRE === inj.mode) { + const term = getprop(inj.parent, inj.key) + // const src = getprop(store, inj.base, store) + const gkey = getelem(inj.path, -2) + // const tval = getprop(src, gkey) + const ppath = slice(inj.path, -1) + const point = getpath(store, ppath) + let pass = false + if ('$GT' === ref && point > term) { + pass = true + } else if ('$LT' === ref && point < term) { + pass = true + } else if ('$GTE' === ref && point >= term) { + pass = true + } else if ('$LTE' === ref && point <= term) { + pass = true + } else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) { + pass = true + } + if (pass) { + // Update spec to match found value so that _validate does not complain. + const gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + } else { + inj.errs.push( + 'CMP: ' + + pathify(ppath) + + S_VIZ + + stringify(point) + + ' fail:' + + ref + + ' ' + + stringify(term), + ) + } + } + return NONE } // Select children from a top-level object that match a MongoDB-style query. // Supports $and, $or, and equality comparisons. // For arrays, children are elements; for objects, children are values. // TODO: swap arg order for consistency function select(children, query) { - if (!isnode(children)) { - return [] - } - if (ismap(children)) { - children = items(children, n => { - setprop(n[1], S_DKEY, n[0]) - return n[1] - }) - } - else { - children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1])) - } - const results = [] - const injdef = { - errs: [], - meta: { [S_BEXACT]: true }, - extra: { - $AND: select_AND, - $OR: select_OR, - $NOT: select_NOT, - $GT: select_CMP, - $LT: select_CMP, - $GTE: select_CMP, - $LTE: select_CMP, - $LIKE: select_CMP, - } - } - const q = clone(query) - walk(q, (_k, v) => { - if (ismap(v)) { - setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true)) - } - return v + if (!isnode(children)) { + return [] + } + if (ismap(children)) { + children = items(children, (n) => { + setprop(n[1], S_DKEY, n[0]) + return n[1] }) - for (const child of children) { - injdef.errs = [] - validate(child, clone(q), injdef) - if (0 === size(injdef.errs)) { - results.push(child) - } - } - return results + } else { + children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1])) + } + const results = [] + const injdef = { + errs: [], + meta: { [S_BEXACT]: true }, + extra: { + $AND: select_AND, + $OR: select_OR, + $NOT: select_NOT, + $GT: select_CMP, + $LT: select_CMP, + $GTE: select_CMP, + $LTE: select_CMP, + $LIKE: select_CMP, + }, + } + const q = clone(query) + walk(q, (_k, v) => { + if (ismap(v)) { + setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true)) + } + return v + }) + for (const child of children) { + injdef.errs = [] + validate(child, clone(q), injdef) + if (0 === size(injdef.errs)) { + results.push(child) + } + } + return results } // Injection state used for recursive injection into JSON - like data structures. class Injection { - constructor(val, parent) { - this.val = val - this.parent = parent - this.errs = [] - this.dparent = NONE - this.dpath = [S_DTOP] - this.mode = M_VAL - this.full = false - this.keyI = 0 - this.keys = [S_DTOP] - this.key = S_DTOP - this.path = [S_DTOP] - this.nodes = [parent] - this.handler = _injecthandler - this.base = S_DTOP - this.meta = {} - } - toString(prefix) { - return 'INJ' + (null == prefix ? '' : S_FS + prefix) + S_CN + - pad(pathify(this.path, 1)) + - MODENAME[this.mode] + (this.full ? '/full' : '') + S_CN + - 'key=' + this.keyI + S_FS + this.key + S_FS + S_OS + this.keys + S_CS + - ' p=' + stringify(this.parent, -1, 1) + - ' m=' + stringify(this.meta, -1, 1) + - ' d/' + pathify(this.dpath, 1) + '=' + stringify(this.dparent, -1, 1) + - ' r=' + stringify(this.nodes[0]?.[S_DTOP], -1, 1) - } - descend() { - this.meta.__d++ - const parentkey = getelem(this.path, -2) - // Resolve current node in store for local paths. - if (NONE === this.dparent) { - // Even if there's no data, dpath should continue to match path, so that - // relative paths work properly. - if (1 < size(this.dpath)) { - this.dpath = flatten([this.dpath, parentkey]) - } - } - else { - // this.dparent is the containing node of the current store value. - if (null != parentkey) { - this.dparent = getprop(this.dparent, parentkey) - let lastpart = getelem(this.dpath, -1) - if (lastpart === '$:' + parentkey) { - this.dpath = slice(this.dpath, -1) - } - else { - this.dpath = flatten([this.dpath, parentkey]) - } - } - } - // TODO: is this needed? - return this.dparent - } - child(keyI, keys) { - const key = strkey(keys[keyI]) - const val = this.val - const cinj = new Injection(getprop(val, key), val) - cinj.keyI = keyI - cinj.keys = keys - cinj.key = key - cinj.path = flatten([getdef(this.path, []), key]) - cinj.nodes = flatten([getdef(this.nodes, []), [val]]) - cinj.mode = this.mode - cinj.handler = this.handler - cinj.modify = this.modify - cinj.base = this.base - cinj.meta = this.meta - cinj.errs = this.errs - cinj.prior = this - cinj.dpath = flatten([this.dpath]) - cinj.dparent = this.dparent - return cinj - } - setval(val, ancestor) { - let parent = NONE - if (null == ancestor || ancestor < 2) { - parent = NONE === val ? - this.parent = delprop(this.parent, this.key) : - setprop(this.parent, this.key, val) - } - else { - const aval = getelem(this.nodes, 0 - ancestor) - const akey = getelem(this.path, 0 - ancestor) - parent = NONE === val ? - delprop(aval, akey) : - setprop(aval, akey, val) - } - // console.log('SETVAL', val, this.key, this.parent) - return parent - } + constructor(val, parent) { + this.val = val + this.parent = parent + this.errs = [] + this.dparent = NONE + this.dpath = [S_DTOP] + this.mode = M_VAL + this.full = false + this.keyI = 0 + this.keys = [S_DTOP] + this.key = S_DTOP + this.path = [S_DTOP] + this.nodes = [parent] + this.handler = _injecthandler + this.base = S_DTOP + this.meta = {} + } + toString(prefix) { + return ( + 'INJ' + + (null == prefix ? '' : S_FS + prefix) + + S_CN + + pad(pathify(this.path, 1)) + + MODENAME[this.mode] + + (this.full ? '/full' : '') + + S_CN + + 'key=' + + this.keyI + + S_FS + + this.key + + S_FS + + S_OS + + this.keys + + S_CS + + ' p=' + + stringify(this.parent, -1, 1) + + ' m=' + + stringify(this.meta, -1, 1) + + ' d/' + + pathify(this.dpath, 1) + + '=' + + stringify(this.dparent, -1, 1) + + ' r=' + + stringify(this.nodes[0]?.[S_DTOP], -1, 1) + ) + } + descend() { + this.meta.__d++ + const parentkey = getelem(this.path, -2) + // Resolve current node in store for local paths. + if (NONE === this.dparent) { + // Even if there's no data, dpath should continue to match path, so that + // relative paths work properly. + if (1 < size(this.dpath)) { + this.dpath = flatten([this.dpath, parentkey]) + } + } else { + // this.dparent is the containing node of the current store value. + if (null != parentkey) { + this.dparent = getprop(this.dparent, parentkey) + let lastpart = getelem(this.dpath, -1) + if (lastpart === '$:' + parentkey) { + this.dpath = slice(this.dpath, -1) + } else { + this.dpath = flatten([this.dpath, parentkey]) + } + } + } + // TODO: is this needed? + return this.dparent + } + child(keyI, keys) { + const key = strkey(keys[keyI]) + const val = this.val + const cinj = new Injection(getprop(val, key), val) + cinj.keyI = keyI + cinj.keys = keys + cinj.key = key + cinj.path = flatten([getdef(this.path, []), key]) + cinj.nodes = flatten([getdef(this.nodes, []), [val]]) + cinj.mode = this.mode + cinj.handler = this.handler + cinj.modify = this.modify + cinj.base = this.base + cinj.meta = this.meta + cinj.errs = this.errs + cinj.prior = this + cinj.dpath = flatten([this.dpath]) + cinj.dparent = this.dparent + return cinj + } + setval(val, ancestor) { + let parent = NONE + if (null == ancestor || ancestor < 2) { + parent = + NONE === val + ? (this.parent = delprop(this.parent, this.key)) + : setprop(this.parent, this.key, val) + } else { + const aval = getelem(this.nodes, 0 - ancestor) + const akey = getelem(this.path, 0 - ancestor) + parent = NONE === val ? delprop(aval, akey) : setprop(aval, akey, val) + } + // console.log('SETVAL', val, this.key, this.parent) + return parent + } } // Internal utilities // ================== @@ -2060,49 +2138,51 @@ class Injection { // } // Build a type validation error message. function _invalidTypeMsg(path, needtype, vt, v, _whence) { - let vs = null == v ? 'no value' : stringify(v) - return 'Expected ' + - (1 < size(path) ? ('field ' + pathify(path, 1) + ' to be ') : '') + - needtype + ', but found ' + - (null != v ? typename(vt) + S_VIZ : '') + vs + - // Uncomment to help debug validation errors. - // ' [' + _whence + ']' + - '.' + let vs = null == v ? 'no value' : stringify(v) + return ( + 'Expected ' + + (1 < size(path) ? 'field ' + pathify(path, 1) + ' to be ' : '') + + needtype + + ', but found ' + + (null != v ? typename(vt) + S_VIZ : '') + + vs + + // Uncomment to help debug validation errors. + // ' [' + _whence + ']' + + '.' + ) } // Default inject handler for transforms. If the path resolves to a function, // call the function passing the injection inj. This is how transforms operate. const _injecthandler = (inj, val, ref, store) => { - let out = val - const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS)) - // Only call val function if it is a special command ($NAME format). - // TODO: OR if meta.'$CALL' - if (iscmd) { - out = val(inj, val, ref, store) - } - // Update parent with value. Ensures references remain in node tree. - else if (M_VAL === inj.mode && inj.full) { - inj.setval(val) - } - return out + let out = val + const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS)) + // Only call val function if it is a special command ($NAME format). + // TODO: OR if meta.'$CALL' + if (iscmd) { + out = val(inj, val, ref, store) + } + // Update parent with value. Ensures references remain in node tree. + else if (M_VAL === inj.mode && inj.full) { + inj.setval(val) + } + return out } const _validatehandler = (inj, val, ref, store) => { - let out = val - const m = ref.match(R_META_PATH) - const ismetapath = null != m - if (ismetapath) { - if ('=' === m[2]) { - inj.setval([S_BEXACT, val]) - } - else { - inj.setval(val) - } - inj.keyI = -1 - out = SKIP - } - else { - out = _injecthandler(inj, val, ref, store) - } - return out + let out = val + const m = ref.match(R_META_PATH) + const ismetapath = null != m + if (ismetapath) { + if ('=' === m[2]) { + inj.setval([S_BEXACT, val]) + } else { + inj.setval(val) + } + inj.keyI = -1 + out = SKIP + } else { + out = _injecthandler(inj, val, ref, store) + } + return out } // Inject values from a data store into a string. Not a public utility - used by // `inject`. Inject are marked with `path` where path is resolved @@ -2114,182 +2194,206 @@ const _validatehandler = (inj, val, ref, store) => { // discarded. This syntax specifies the name of a transform, and // optionally allows transforms to be ordered by alphanumeric sorting. function _injectstr(val, store, inj) { - // Can't inject into non-strings - if (S_string !== typeof val || S_MT === val) { - return S_MT - } - let out = val - // Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`" - const m = val.match(R_INJECTION_FULL) - // Full string of the val is an injection. - if (m) { - if (null != inj) { - inj.full = true - } - let pathref = m[1] - // Special escapes inside injection. - if (3 < size(pathref)) { - pathref = pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) - } - // Get the extracted path reference. - out = getpath(store, pathref, inj) - } - else { - // Check for injections within the string. - const partial = (_m, ref) => { - // Special escapes inside injection. - if (3 < size(ref)) { - ref = ref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) - } - if (inj) { - inj.full = false - } - const found = getpath(store, ref, inj) - // Ensure inject value is a string. - return NONE === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found) - } - out = val.replace(R_INJECTION_PARTIAL, partial) - // Also call the inj handler on the entire string, providing the - // option for custom injection. - if (null != inj && isfunc(inj.handler)) { - inj.full = true - out = inj.handler(inj, out, val, store) - } - } - return out + // Can't inject into non-strings + if (S_string !== typeof val || S_MT === val) { + return S_MT + } + let out = val + // Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`" + const m = val.match(R_INJECTION_FULL) + // Full string of the val is an injection. + if (m) { + if (null != inj) { + inj.full = true + } + let pathref = m[1] + // Special escapes inside injection. + if (3 < size(pathref)) { + pathref = pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) + } + // Get the extracted path reference. + out = getpath(store, pathref, inj) + } else { + // Check for injections within the string. + const partial = (_m, ref) => { + // Special escapes inside injection. + if (3 < size(ref)) { + ref = ref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) + } + if (inj) { + inj.full = false + } + const found = getpath(store, ref, inj) + // Ensure inject value is a string. + return NONE === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found) + } + out = val.replace(R_INJECTION_PARTIAL, partial) + // Also call the inj handler on the entire string, providing the + // option for custom injection. + if (null != inj && isfunc(inj.handler)) { + inj.full = true + out = inj.handler(inj, out, val, store) + } + } + return out } // Handler Utilities // ================= const MODENAME = { - [M_VAL]: 'val', - [M_KEYPRE]: 'key:pre', - [M_KEYPOST]: 'key:post', + [M_VAL]: 'val', + [M_KEYPRE]: 'key:pre', + [M_KEYPOST]: 'key:post', } const PLACEMENT = { - [M_VAL]: 'value', - [M_KEYPRE]: S_key, - [M_KEYPOST]: S_key, + [M_VAL]: 'value', + [M_KEYPRE]: S_key, + [M_KEYPOST]: S_key, } function checkPlacement(modes, ijname, parentTypes, inj) { - if (0 === (modes & inj.mode)) { - inj.errs.push('$' + ijname + ': invalid placement as ' + PLACEMENT[inj.mode] + - ', expected: ' + join(items([M_KEYPRE, M_KEYPOST, M_VAL].filter(m => modes & m), (n) => PLACEMENT[n[1]]), ',') + '.') - return false - } - if (!isempty(parentTypes)) { - const ptype = typify(inj.parent) - if (0 === (parentTypes & ptype)) { - inj.errs.push('$' + ijname + ': invalid placement in parent ' + typename(ptype) + - ', expected: ' + typename(parentTypes) + '.') - return false - } - } - return true + if (0 === (modes & inj.mode)) { + inj.errs.push( + '$' + + ijname + + ': invalid placement as ' + + PLACEMENT[inj.mode] + + ', expected: ' + + join( + items( + [M_KEYPRE, M_KEYPOST, M_VAL].filter((m) => modes & m), + (n) => PLACEMENT[n[1]], + ), + ',', + ) + + '.', + ) + return false + } + if (!isempty(parentTypes)) { + const ptype = typify(inj.parent) + if (0 === (parentTypes & ptype)) { + inj.errs.push( + '$' + + ijname + + ': invalid placement in parent ' + + typename(ptype) + + ', expected: ' + + typename(parentTypes) + + '.', + ) + return false + } + } + return true } // function injectorArgs(argTypes: number[], inj: Injection): any { function injectorArgs(argTypes, args) { - const numargs = size(argTypes) - const found = new Array(1 + numargs) - found[0] = NONE - for (let argI = 0; argI < numargs; argI++) { - // const arg = inj.parent[1 + argI] - const arg = args[argI] - const argType = typify(arg) - if (0 === (argTypes[argI] & argType)) { - found[0] = 'invalid argument: ' + stringify(arg, 22) + - ' (' + typename(argType) + ' at position ' + (1 + argI) + - ') is not of type: ' + typename(argTypes[argI]) + '.' - break - } - found[1 + argI] = arg + const numargs = size(argTypes) + const found = new Array(1 + numargs) + found[0] = NONE + for (let argI = 0; argI < numargs; argI++) { + // const arg = inj.parent[1 + argI] + const arg = args[argI] + const argType = typify(arg) + if (0 === (argTypes[argI] & argType)) { + found[0] = + 'invalid argument: ' + + stringify(arg, 22) + + ' (' + + typename(argType) + + ' at position ' + + (1 + argI) + + ') is not of type: ' + + typename(argTypes[argI]) + + '.' + break } - return found + found[1 + argI] = arg + } + return found } function injectChild(child, store, inj) { - let cinj = inj - // Replace ['`$FORMAT`',...] with child - if (null != inj.prior) { - if (null != inj.prior.prior) { - cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys) - cinj.val = child - setprop(cinj.parent, inj.prior.key, child) - } - else { - cinj = inj.prior.child(inj.keyI, inj.keys) - cinj.val = child - setprop(cinj.parent, inj.key, child) - } - } - // console.log('FORMAT-INJECT-CHILD', child) - inject(child, store, cinj) - return cinj + let cinj = inj + // Replace ['`$FORMAT`',...] with child + if (null != inj.prior) { + if (null != inj.prior.prior) { + cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys) + cinj.val = child + setprop(cinj.parent, inj.prior.key, child) + } else { + cinj = inj.prior.child(inj.keyI, inj.keys) + cinj.val = child + setprop(cinj.parent, inj.key, child) + } + } + // console.log('FORMAT-INJECT-CHILD', child) + inject(child, store, cinj) + return cinj } class StructUtility { - constructor() { - this.clone = clone - this.delprop = delprop - this.escre = escre - this.escurl = escurl - this.filter = filter - this.flatten = flatten - this.getdef = getdef - this.getelem = getelem - this.getpath = getpath - this.getprop = getprop - this.haskey = haskey - this.inject = inject - this.isempty = isempty - this.isfunc = isfunc - this.iskey = iskey - this.islist = islist - this.ismap = ismap - this.isnode = isnode - this.items = items - this.join = join - this.jsonify = jsonify - this.keysof = keysof - this.merge = merge - this.pad = pad - this.pathify = pathify - this.select = select - this.setpath = setpath - this.setprop = setprop - this.size = size - this.slice = slice - this.strkey = strkey - this.stringify = stringify - this.transform = transform - this.typify = typify - this.typename = typename - this.validate = validate - this.walk = walk - this.SKIP = SKIP - this.DELETE = DELETE - this.jm = jm - this.jt = jt - this.tn = typename - this.T_any = T_any - this.T_noval = T_noval - this.T_boolean = T_boolean - this.T_decimal = T_decimal - this.T_integer = T_integer - this.T_number = T_number - this.T_string = T_string - this.T_function = T_function - this.T_symbol = T_symbol - this.T_null = T_null - this.T_list = T_list - this.T_map = T_map - this.T_instance = T_instance - this.T_scalar = T_scalar - this.T_node = T_node - this.checkPlacement = checkPlacement - this.injectorArgs = injectorArgs - this.injectChild = injectChild - } + constructor() { + this.clone = clone + this.delprop = delprop + this.escre = escre + this.escurl = escurl + this.filter = filter + this.flatten = flatten + this.getdef = getdef + this.getelem = getelem + this.getpath = getpath + this.getprop = getprop + this.haskey = haskey + this.inject = inject + this.isempty = isempty + this.isfunc = isfunc + this.iskey = iskey + this.islist = islist + this.ismap = ismap + this.isnode = isnode + this.items = items + this.join = join + this.jsonify = jsonify + this.keysof = keysof + this.merge = merge + this.pad = pad + this.pathify = pathify + this.select = select + this.setpath = setpath + this.setprop = setprop + this.size = size + this.slice = slice + this.strkey = strkey + this.stringify = stringify + this.transform = transform + this.typify = typify + this.typename = typename + this.validate = validate + this.walk = walk + this.SKIP = SKIP + this.DELETE = DELETE + this.jm = jm + this.jt = jt + this.tn = typename + this.T_any = T_any + this.T_noval = T_noval + this.T_boolean = T_boolean + this.T_decimal = T_decimal + this.T_integer = T_integer + this.T_number = T_number + this.T_string = T_string + this.T_function = T_function + this.T_symbol = T_symbol + this.T_null = T_null + this.T_list = T_list + this.T_map = T_map + this.T_instance = T_instance + this.T_scalar = T_scalar + this.T_node = T_node + this.checkPlacement = checkPlacement + this.injectorArgs = injectorArgs + this.injectChild = injectChild + } } - module.exports = { StructUtility, Injection, diff --git a/js/test/client.test.js b/js/test/client.test.js index 90c454d4..cc3da09c 100644 --- a/js/test/client.test.js +++ b/js/test/client.test.js @@ -1,20 +1,15 @@ - // RUN: npm test // RUN-SOME: npm run test-some --pattern=getpath const { test, describe } = require('node:test') -const { - makeRunner, -} = require('./runner') +const { makeRunner } = require('./runner') const { SDK } = require('./sdk.js') const TEST_JSON_FILE = '../../build/test/test.json' - describe('client', async () => { - const runner = await makeRunner(TEST_JSON_FILE, await SDK.test()) const { spec, runset, subject } = await runner('check') @@ -22,5 +17,4 @@ describe('client', async () => { test('client-check-basic', async () => { await runset(spec.basic, subject) }) - }) diff --git a/js/test/runner.js b/js/test/runner.js index 03906c05..21a6dc3b 100644 --- a/js/test/runner.js +++ b/js/test/runner.js @@ -4,32 +4,22 @@ const { readFileSync } = require('node:fs') const { join } = require('node:path') const { deepEqual, fail, AssertionError } = require('node:assert') - const NULLMARK = '__NULL__' // Value is JSON null const UNDEFMARK = '__UNDEF__' // Value is not present (thus, undefined). const EXISTSMARK = '__EXISTS__' // Value exists (not undefined). - async function makeRunner(testfile, client) { - - return async function runner( - name, - store = {} - ) { + return async function runner(name, store = {}) { store = store || {} const utility = client.utility() const structUtils = utility.struct - + let spec = resolveSpec(name, testfile) let clients = await resolveClients(client, spec, store, structUtils) let subject = resolveSubject(name, utility) - let runsetflags = async ( - testspec, - flags, - testsubject - ) => { + let runsetflags = async (testspec, flags, testsubject) => { subject = testsubject || subject flags = resolveFlags(flags) const testspecmap = fixJSON(testspec, flags) @@ -47,17 +37,13 @@ async function makeRunner(testfile, client) { entry.res = res checkResult(entry, args, res, structUtils) - } - catch (err) { + } catch (err) { handleError(entry, err, structUtils) } } } - let runset = async ( - testspec, - testsubject - ) => runsetflags(testspec, {}, testsubject) + let runset = async (testspec, testsubject) => runsetflags(testspec, {}, testsubject) const runpack = { spec, @@ -71,23 +57,14 @@ async function makeRunner(testfile, client) { } } - function resolveSpec(name, testfile) { - const alltests = - JSON.parse(readFileSync(join( - __dirname, testfile), 'utf8')) + const alltests = JSON.parse(readFileSync(join(__dirname, testfile), 'utf8')) let spec = alltests.primary?.[name] || alltests[name] || alltests return spec } - -async function resolveClients( - client, - spec, - store, - structUtils -) { +async function resolveClients(client, spec, store, structUtils) { const clients = {} if (spec.DEF && spec.DEF.client) { for (let cn in spec.DEF.client) { @@ -103,13 +80,11 @@ async function resolveClients( return clients } - function resolveSubject(name, container) { const subject = container[name] || container.struct[name] return subject } - function resolveFlags(flags) { if (null == flags) { flags = {} @@ -118,34 +93,29 @@ function resolveFlags(flags) { return flags } - function resolveEntry(entry, flags) { entry.out = null == entry.out && flags.null ? NULLMARK : entry.out return entry } - function checkResult(entry, args, res, structUtils) { let matched = false if (entry.err) { - return fail('Expected error did not occur: ' + entry.err + - '\n\nENTRY: ' + JSON.stringify(entry, null, 2)) + return fail( + 'Expected error did not occur: ' + entry.err + '\n\nENTRY: ' + JSON.stringify(entry, null, 2), + ) } if (entry.match) { const result = { in: entry.in, args, out: entry.res, ctx: entry.ctx } - match( - entry.match, - result, - structUtils - ) + match(entry.match, result, structUtils) matched = true } const out = entry.out - + if (out === res) { return } @@ -158,7 +128,6 @@ function checkResult(entry, args, res, structUtils) { deepEqual(null != res ? JSON.parse(JSON.stringify(res)) : res, entry.out) } - // Handle errors from test execution function handleError(entry, err, structUtils) { entry.thrown = err @@ -170,48 +139,38 @@ function handleError(entry, err, structUtils) { if (entry.match) { match( entry.match, - { in: entry.in, out: entry.res, ctx: entry.ctx, err:fixJSON(err) }, - structUtils + { in: entry.in, out: entry.res, ctx: entry.ctx, err: fixJSON(err) }, + structUtils, ) } return } - fail('ERROR MATCH: [' + structUtils.stringify(entry_err) + - '] <=> [' + err.message + ']') + fail('ERROR MATCH: [' + structUtils.stringify(entry_err) + '] <=> [' + err.message + ']') } // Unexpected error (test didn't specify an error expectation) else if (err instanceof AssertionError) { fail(err.message + '\n\nENTRY: ' + JSON.stringify(entry, null, 2)) - } - else { + } else { fail(err.stack + '\\nnENTRY: ' + JSON.stringify(entry, null, 2)) } } - -function resolveArgs( - entry, - testpack, - utility, - structUtils -) { +function resolveArgs(entry, testpack, utility, structUtils) { let args = [] if (entry.ctx) { args = [entry.ctx] - } - else if (entry.args) { + } else if (entry.args) { args = entry.args - } - else { + } else { args = [structUtils.clone(entry.in)] } if (entry.ctx || entry.args) { let first = args[0] - if(structUtils.ismap(first)) { + if (structUtils.ismap(first)) { first = structUtils.clone(first) first = utility.contextify(first) args[0] = first @@ -225,14 +184,7 @@ function resolveArgs( return args } - -function resolveTestPack( - name, - entry, - subject, - client, - clients -) { +function resolveTestPack(name, entry, subject, client, clients) { const testpack = { name, client, @@ -249,16 +201,11 @@ function resolveTestPack( return testpack } - -function match( - check, - base, - structUtils -) { +function match(check, base, structUtils) { base = structUtils.clone(base) - + structUtils.walk(check, (_key, val, _parent, path) => { - if(!structUtils.isnode(val)) { + if (!structUtils.isnode(val)) { let baseval = structUtils.getpath(base, path) if (baseval === val) { @@ -274,11 +221,17 @@ function match( if (EXISTSMARK === val && null != baseval) { return val } - + if (!matchval(val, baseval, structUtils)) { - fail('MATCH: ' + path.join('.') + - ': [' + structUtils.stringify(val) + - '] <=> [' + structUtils.stringify(baseval) + ']') + fail( + 'MATCH: ' + + path.join('.') + + ': [' + + structUtils.stringify(val) + + '] <=> [' + + structUtils.stringify(baseval) + + ']', + ) } } @@ -286,31 +239,23 @@ function match( }) } - -function matchval( - check, - base, - structUtils -) { +function matchval(check, base, structUtils) { // check = NULLMARK === check || UNDEFMARK === check ? undefined : check // check = NULLMARK === check ? undefined : check let pass = check === base if (!pass) { - if ('string' === typeof check) { let basestr = structUtils.stringify(base) let rem = check.match(/^\/(.+)\/$/) if (rem) { pass = new RegExp(rem[1]).test(basestr) - } - else { + } else { pass = basestr.toLowerCase().includes(structUtils.stringify(check).toLowerCase()) } - } - else if ('function' === typeof check) { + } else if ('function' === typeof check) { pass = true } } @@ -318,46 +263,38 @@ function matchval( return pass } - function fixJSON(val, flags) { if (null == val) { return flags.null ? NULLMARK : val } const replacer = (_k, v) => { - if(null == v && flags.null) { + if (null == v && flags.null) { return NULLMARK } - if(v instanceof Error) { + if (v instanceof Error) { return { ...v, name: v.name, message: v.message, } } - + return v } - + return JSON.parse(JSON.stringify(val, replacer)) } - -function nullModifier( - val, - key, - parent -) { - if ("__NULL__" === val) { +function nullModifier(val, key, parent) { + if ('__NULL__' === val) { parent[key] = null - } - else if ('string' === typeof val) { + } else if ('string' === typeof val) { parent[key] = val.replaceAll('__NULL__', 'null') } } - module.exports = { NULLMARK, nullModifier, diff --git a/js/test/sdk.js b/js/test/sdk.js index e30c471b..116a05bf 100644 --- a/js/test/sdk.js +++ b/js/test/sdk.js @@ -1,11 +1,9 @@ - const { StructUtility } = require('../src/struct') class SDK { - #opts = {} #utility = {} - + constructor(opts) { this.#opts = opts || {} this.#utility = { @@ -13,12 +11,13 @@ class SDK { contextify: (ctxmap) => ctxmap, check: (ctx) => { return { - zed: 'ZED' + + zed: + 'ZED' + (null == this.#opts ? '' : null == this.#opts.foo ? '' : this.#opts.foo) + '_' + - (null == ctx.meta.bar ? '0' : ctx.meta.bar) + (null == ctx.meta.bar ? '0' : ctx.meta.bar), } - } + }, } } @@ -30,11 +29,11 @@ class SDK { return new SDK(opts || this.#opts) } - utility() { - return this.#utility + utility() { + return this.#utility } } module.exports = { - SDK + SDK, } diff --git a/js/test/struct.test.js b/js/test/struct.test.js index 04f12c4c..e1ec92a2 100644 --- a/js/test/struct.test.js +++ b/js/test/struct.test.js @@ -1,15 +1,10 @@ - // RUN: npm test // RUN-SOME: npm run test-some --pattern=getpath const { test, describe } = require('node:test') const assert = require('node:assert') -const { - makeRunner, - nullModifier, - NULLMARK -} = require('./runner') +const { makeRunner, nullModifier, NULLMARK } = require('./runner') const { SDK } = require('./sdk.js') @@ -17,17 +12,14 @@ const TEST_JSON_FILE = '../../build/test/test.json' const { equal, deepEqual } = assert - // NOTE: tests are (mostly) in order of increasing dependence. describe('struct', async () => { - const runner = await makeRunner(TEST_JSON_FILE, await SDK.test()) const { spec, runset, runsetflags, client } = await runner('struct') const struct = client.utility().struct - test('exists', () => { const s = struct @@ -76,7 +68,6 @@ describe('struct', async () => { equal('function', typeof s.walk) }) - // minor tests // =========== @@ -107,9 +98,14 @@ describe('struct', async () => { test('minor-isfunc', async () => { const { isfunc } = struct await runset(spec.minor.isfunc, isfunc) - function f0() { return null } + function f0() { + return null + } equal(isfunc(f0), true) - equal(isfunc(() => null), true) + equal( + isfunc(() => null), + true, + ) }) test('minor-clone', async () => { @@ -127,7 +123,11 @@ describe('struct', async () => { deepEqual(x, xc) assert(x !== xc) - class A { constructor() { this.x = 1 } } + class A { + constructor() { + this.x = 1 + } + } const a = new A() let ac = clone(a) deepEqual(a, ac) @@ -157,7 +157,8 @@ describe('struct', async () => { test('minor-stringify', async () => { await runset(spec.minor.stringify, (vin) => - struct.stringify((NULLMARK === vin.val ? "null" : vin.val), vin.max)) + struct.stringify(NULLMARK === vin.val ? 'null' : vin.val, vin.max), + ) }) test('minor-edge-stringify', async () => { @@ -166,30 +167,34 @@ describe('struct', async () => { a.a = a equal(stringify(a), '__STRINGIFY_FAILED__') - equal(stringify({ a: [9] }, -1, true), + equal( + stringify({ a: [9] }, -1, true), '\x1B[38;5;81m\x1B[38;5;118m{\x1B[38;5;118ma\x1B[38;5;118m:' + - '\x1B[38;5;213m[\x1B[38;5;213m9\x1B[38;5;213m]\x1B[38;5;118m}\x1B[0m') + '\x1B[38;5;213m[\x1B[38;5;213m9\x1B[38;5;213m]\x1B[38;5;118m}\x1B[0m', + ) }) test('minor-jsonify', async () => { - await runsetflags(spec.minor.jsonify, { null: false }, - (vin) => struct.jsonify(vin.val, vin.flags)) + await runsetflags(spec.minor.jsonify, { null: false }, (vin) => + struct.jsonify(vin.val, vin.flags), + ) }) test('minor-edge-jsonify', async () => { const { jsonify } = struct - equal(jsonify(() => 1), 'null') + equal( + jsonify(() => 1), + 'null', + ) }) test('minor-pathify', async () => { - await runsetflags( - spec.minor.pathify, { null: true }, - (vin) => { - let path = NULLMARK == vin.path ? undefined : vin.path - let pathstr = struct.pathify(path, vin.from).replace('__NULL__.', '') - pathstr = NULLMARK === vin.path ? pathstr.replace('>', ':null>') : pathstr - return pathstr - }) + await runsetflags(spec.minor.pathify, { null: true }, (vin) => { + let path = NULLMARK == vin.path ? undefined : vin.path + let pathstr = struct.pathify(path, vin.from).replace('__NULL__.', '') + pathstr = NULLMARK === vin.path ? pathstr.replace(/>/g, ':null>') : pathstr + return pathstr + }) }) test('minor-items', async () => { @@ -200,24 +205,33 @@ describe('struct', async () => { const { items } = struct const a0 = [11, 22, 33] a0.x = 1 - deepEqual(items(a0), [['0', 11], ['1', 22], ['2', 33]]) + deepEqual(items(a0), [ + ['0', 11], + ['1', 22], + ['2', 33], + ]) }) test('minor-getelem', async () => { const { getelem } = struct await runsetflags(spec.minor.getelem, { null: false }, (vin) => - null == vin.alt ? getelem(vin.val, vin.key) : getelem(vin.val, vin.key, vin.alt)) + null == vin.alt ? getelem(vin.val, vin.key) : getelem(vin.val, vin.key, vin.alt), + ) }) test('minor-edge-getelem', async () => { const { getelem } = struct - equal(getelem([], 1, () => 2), 2) + equal( + getelem([], 1, () => 2), + 2, + ) }) test('minor-getprop', async () => { const { getprop } = struct await runsetflags(spec.minor.getprop, { null: false }, (vin) => - undefined === vin.alt ? getprop(vin.val, vin.key) : getprop(vin.val, vin.key, vin.alt)) + undefined === vin.alt ? getprop(vin.val, vin.key) : getprop(vin.val, vin.key, vin.alt), + ) }) test('minor-edge-getprop', async () => { @@ -233,8 +247,7 @@ describe('struct', async () => { }) test('minor-setprop', async () => { - await runset(spec.minor.setprop, (vin) => - struct.setprop(vin.parent, vin.key, vin.val)) + await runset(spec.minor.setprop, (vin) => struct.setprop(vin.parent, vin.key, vin.val)) }) test('minor-edge-setprop', async () => { @@ -252,8 +265,7 @@ describe('struct', async () => { }) test('minor-delprop', async () => { - await runset(spec.minor.delprop, (vin) => - struct.delprop(vin.parent, vin.key)) + await runset(spec.minor.delprop, (vin) => struct.delprop(vin.parent, vin.key)) }) test('minor-edge-delprop', async () => { @@ -271,8 +283,7 @@ describe('struct', async () => { }) test('minor-haskey', async () => { - await runsetflags(spec.minor.haskey, { null: false }, (vin) => - struct.haskey(vin.src, vin.key)) + await runsetflags(spec.minor.haskey, { null: false }, (vin) => struct.haskey(vin.src, vin.key)) }) test('minor-keysof', async () => { @@ -287,8 +298,9 @@ describe('struct', async () => { }) test('minor-join', async () => { - await runsetflags(spec.minor.join, { null: false }, - (vin) => struct.join(vin.val, vin.sep, vin.url)) + await runsetflags(spec.minor.join, { null: false }, (vin) => + struct.join(vin.val, vin.sep, vin.url), + ) }) test('minor-typename', async () => { @@ -300,16 +312,18 @@ describe('struct', async () => { }) test('minor-edge-typify', async () => { - const { - typify, T_noval, T_scalar, T_function, T_symbol, T_any, T_node, T_instance, T_null - } = struct - class X { } + const { typify, T_noval, T_scalar, T_function, T_symbol, T_any, T_node, T_instance, T_null } = + struct + class X {} const x = new X() equal(typify(), T_noval) equal(typify(undefined), T_noval) equal(typify(NaN), T_noval) equal(typify(null), T_scalar | T_null) - equal(typify(() => null), T_scalar | T_function) + equal( + typify(() => null), + T_scalar | T_function, + ) equal(typify(Symbol('S')), T_scalar | T_symbol) equal(typify(BigInt(1)), T_any) equal(typify(x), T_node | T_instance) @@ -320,18 +334,21 @@ describe('struct', async () => { }) test('minor-slice', async () => { - await runsetflags(spec.minor.slice, { null: false }, - (vin) => struct.slice(vin.val, vin.start, vin.end)) + await runsetflags(spec.minor.slice, { null: false }, (vin) => + struct.slice(vin.val, vin.start, vin.end), + ) }) test('minor-pad', async () => { - await runsetflags(spec.minor.pad, { null: false }, - (vin) => struct.pad(vin.val, vin.pad, vin.char)) + await runsetflags(spec.minor.pad, { null: false }, (vin) => + struct.pad(vin.val, vin.pad, vin.char), + ) }) test('minor-setpath', async () => { - await runsetflags(spec.minor.setpath, { null: false }, - (vin) => struct.setpath(vin.store, vin.path, vin.val)) + await runsetflags(spec.minor.setpath, { null: false }, (vin) => + struct.setpath(vin.store, vin.path, vin.val), + ) }) test('minor-edge-setpath', async () => { @@ -341,7 +358,6 @@ describe('struct', async () => { deepEqual(x, { y: { z: 1 } }) }) - // walk tests // ========== @@ -353,10 +369,16 @@ describe('struct', async () => { let log = [] function walklog(key, val, parent, path) { - log.push('k=' + stringify(key) + - ', v=' + stringify(val) + - ', p=' + stringify(parent) + - ', t=' + pathify(path)) + log.push( + 'k=' + + stringify(key) + + ', v=' + + stringify(val) + + ', p=' + + stringify(parent) + + ', t=' + + pathify(path), + ) return val } @@ -372,7 +394,6 @@ describe('struct', async () => { deepEqual(log, test.out.both) }) - test('walk-basic', async () => { function walkpath(_key, val, _parent, path) { return 'string' === typeof val ? val + '~' + path.join('.') : val @@ -381,33 +402,28 @@ describe('struct', async () => { await runset(spec.walk.basic, (vin) => struct.walk(vin, walkpath)) }) - test('walk-depth', async () => { - await runsetflags(spec.walk.depth, { null: false }, - (vin) => { - let top = undefined - let cur = undefined - function copy(key, val, _parent, _path) { - if (undefined === key || struct.isnode(val)) { - let child = struct.islist(val) ? [] : {} - if (undefined === key) { - top = cur = child - } - else { - cur = cur[key] = child - } - } - else { - cur[key] = val + await runsetflags(spec.walk.depth, { null: false }, (vin) => { + let top = undefined + let cur = undefined + function copy(key, val, _parent, _path) { + if (undefined === key || struct.isnode(val)) { + let child = struct.islist(val) ? [] : {} + if (undefined === key) { + top = cur = child + } else { + cur = cur[key] = child } - return val + } else { + cur[key] = val } - struct.walk(vin.src, copy, undefined, vin.maxdepth) - return top - }) + return val + } + struct.walk(vin.src, copy, undefined, vin.maxdepth) + return top + }) }) - test('walk-copy', async () => { const { walk, isnode, ismap, islist, size, setprop } = struct @@ -434,7 +450,6 @@ describe('struct', async () => { await runset(spec.walk.copy, (vin) => (walk(vin, walkcopy), cur[0])) }) - // merge tests // =========== @@ -474,7 +489,11 @@ describe('struct', async () => { deepEqual(merge([[global.fetch]]), [global.fetch]) deepEqual(merge([{ a: { b: global.fetch } }]), { a: { b: global.fetch } }) - class Bar { constructor() { this.x = 1 } } + class Bar { + constructor() { + this.x = 1 + } + } const b0 = new Bar() equal(merge([{ x: 10 }, b0]), b0) @@ -502,7 +521,6 @@ describe('struct', async () => { equal(b0 instanceof Bar, true) }) - // getpath tests // ============= @@ -512,13 +530,12 @@ describe('struct', async () => { test('getpath-relative', async () => { await runset(spec.getpath.relative, (vin) => - struct.getpath(vin.store, vin.path, - { dparent: vin.dparent, dpath: vin.dpath?.split('.') })) + struct.getpath(vin.store, vin.path, { dparent: vin.dparent, dpath: vin.dpath?.split('.') }), + ) }) test('getpath-special', async () => { - await runset(spec.getpath.special, (vin) => - struct.getpath(vin.store, vin.path, vin.inj)) + await runset(spec.getpath.special, (vin) => struct.getpath(vin.store, vin.path, vin.inj)) }) test('getpath-handler', async () => { @@ -532,12 +549,12 @@ describe('struct', async () => { { handler: (_inj, val, _ref, _store) => { return val() - } - } - )) + }, + }, + ), + ) }) - // inject tests // ============ @@ -549,14 +566,14 @@ describe('struct', async () => { test('inject-string', async () => { await runset(spec.inject.string, (vin) => - struct.inject(vin.val, vin.store, { modify: nullModifier })) + struct.inject(vin.val, vin.store, { modify: nullModifier }), + ) }) test('inject-deep', async () => { await runset(spec.inject.deep, (vin) => struct.inject(vin.val, vin.store)) }) - // transform tests // =============== @@ -567,38 +584,33 @@ describe('struct', async () => { }) test('transform-paths', async () => { - await runset(spec.transform.paths, (vin) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.paths, (vin) => struct.transform(vin.data, vin.spec)) }) test('transform-cmds', async () => { - await runset(spec.transform.cmds, (vin) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.cmds, (vin) => struct.transform(vin.data, vin.spec)) }) test('transform-each', async () => { - await runset(spec.transform.each, (vin) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.each, (vin) => struct.transform(vin.data, vin.spec)) }) test('transform-pack', async () => { - await runset(spec.transform.pack, (vin) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.pack, (vin) => struct.transform(vin.data, vin.spec)) }) test('transform-ref', async () => { - await runset(spec.transform.ref, (vin) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.ref, (vin) => struct.transform(vin.data, vin.spec)) }) test('transform-format', async () => { await runsetflags(spec.transform.format, { null: false }, (vin) => - struct.transform(vin.data, vin.spec)) + struct.transform(vin.data, vin.spec), + ) }) test('transform-apply', async () => { - await runset(spec.transform.apply, (vin) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.apply, (vin) => struct.transform(vin.data, vin.spec)) }) test('transform-edge-apply', async () => { @@ -608,36 +620,37 @@ describe('struct', async () => { test('transform-modify', async () => { await runset(spec.transform.modify, (vin) => - struct.transform( - vin.data, - vin.spec, - { - modify: (val, key, parent) => { - if (null != key && null != parent && 'string' === typeof val) { - val = parent[key] = '@' + val - } + struct.transform(vin.data, vin.spec, { + modify: (val, key, parent) => { + if (null != key && null != parent && 'string' === typeof val) { + val = parent[key] = '@' + val } - } - )) + }, + }), + ) }) test('transform-extra', async () => { - deepEqual(struct.transform( - { a: 1 }, - { x: '`a`', b: '`$COPY`', c: '`$UPPER`' }, + deepEqual( + struct.transform( + { a: 1 }, + { x: '`a`', b: '`$COPY`', c: '`$UPPER`' }, + { + extra: { + b: 2, + $UPPER: (inj) => { + const { path } = inj + return ('' + struct.getprop(path, path.length - 1)).toUpperCase() + }, + }, + }, + ), { - extra: { - b: 2, $UPPER: (inj) => { - const { path } = inj - return ('' + struct.getprop(path, path.length - 1)).toUpperCase() - } - } - } - ), { - x: 1, - b: 2, - c: 'C' - }) + x: 1, + b: 2, + c: 'C', + }, + ) }) test('transform-funcval', async () => { @@ -649,13 +662,13 @@ describe('struct', async () => { deepEqual(transform({ f0 }, { x: '`f0`' }), { x: f0 }) }) - // validate tests // =============== test('validate-basic', async () => { - await runsetflags(spec.validate.basic, { null: false }, - (vin) => struct.validate(vin.data, vin.spec)) + await runsetflags(spec.validate.basic, { null: false }, (vin) => + struct.validate(vin.data, vin.spec), + ) }) test('validate-child', async () => { @@ -671,13 +684,13 @@ describe('struct', async () => { }) test('validate-invalid', async () => { - await runsetflags(spec.validate.invalid, { null: false }, - (vin) => struct.validate(vin.data, vin.spec)) + await runsetflags(spec.validate.invalid, { null: false }, (vin) => + struct.validate(vin.data, vin.spec), + ) }) test('validate-special', async () => { - await runset(spec.validate.special, (vin) => - struct.validate(vin.data, vin.spec, vin.inj)) + await runset(spec.validate.special, (vin) => struct.validate(vin.data, vin.spec, vin.inj)) }) test('validate-edge', async () => { @@ -694,7 +707,7 @@ describe('struct', async () => { validate({ x: [] }, { x: '`$INSTANCE`' }, { errs }) equal(errs[0], 'Expected field x to be instance, but found list: [].') - class C { } + class C {} const c = new C() errs = [] validate({ x: c }, { x: '`$INSTANCE`' }, { errs }) @@ -729,7 +742,6 @@ describe('struct', async () => { deepEqual(errs, ['Not an integer at a: A']) }) - // select tests // ============ @@ -749,33 +761,23 @@ describe('struct', async () => { await runset(spec.select.alts, (vin) => struct.select(vin.obj, vin.query)) }) - // JSON Builder // ============ test('json-builder', async () => { const { jsonify, jm, jt } = struct - equal(jsonify(jm( - 'a', 1 - )), '{\n "a": 1\n}') + equal(jsonify(jm('a', 1)), '{\n "a": 1\n}') - equal(jsonify(jt( - 'b', 2 - )), '[\n "b",\n 2\n]') + equal(jsonify(jt('b', 2)), '[\n "b",\n 2\n]') - equal(jsonify(jm( - 'c', 'C', - 'd', jm('x', true), - 'e', jt(null, false) - )), '{\n "c": "C",\n "d": {\n "x": true\n },\n "e": [\n null,\n false\n ]\n}') + equal( + jsonify(jm('c', 'C', 'd', jm('x', true), 'e', jt(null, false))), + '{\n "c": "C",\n "d": {\n "x": true\n },\n "e": [\n null,\n false\n ]\n}', + ) - equal(jsonify(jm( - true, 1, - false, 2, - null, 3, - ['a'], 4, - { 'b': 0 }, 5 - )), '{\n "true": 1,\n "false": 2,\n "null": 3,\n "[a]": 4,\n "{b:0}": 5\n}') + equal( + jsonify(jm(true, 1, false, 2, null, 3, ['a'], 4, { b: 0 }, 5)), + '{\n "true": 1,\n "false": 2,\n "null": 3,\n "[a]": 4,\n "{b:0}": 5\n}', + ) }) - }) diff --git a/js/test/walk-bench.test.js b/js/test/walk-bench.test.js index 7b1e73c5..a4bb6487 100644 --- a/js/test/walk-bench.test.js +++ b/js/test/walk-bench.test.js @@ -8,10 +8,8 @@ const { test, describe } = require('node:test') const { walk } = require('../src/struct') - const BENCH = '1' === process.env.WALK_BENCH - // Build a balanced tree of maps with given width and depth. // Total nodes: (width^(depth+1) - 1) / (width - 1). function buildTree(width, depth) { @@ -25,7 +23,6 @@ function buildTree(width, depth) { return out } - function countNodes(val) { if (null == val || 'object' !== typeof val) { return 1 @@ -37,7 +34,6 @@ function countNodes(val) { return n } - function measure(label, tree, runs) { // Touch path to simulate a minimal consumer. Using path.length keeps the // work O(1) so we measure walk overhead rather than callback overhead. @@ -70,15 +66,13 @@ function measure(label, tree, runs) { console.log( `[walk-bench] ${label}: nodes=${nodes} runs=${runs} ` + - `min=${min.toFixed(2)}ms median=${median.toFixed(2)}ms ` + - `mean=${mean.toFixed(2)}ms max=${max.toFixed(2)}ms ` + - `ns/node=${nsPerNode.toFixed(1)} sink=${sink}` + `min=${min.toFixed(2)}ms median=${median.toFixed(2)}ms ` + + `mean=${mean.toFixed(2)}ms max=${max.toFixed(2)}ms ` + + `ns/node=${nsPerNode.toFixed(1)} sink=${sink}`, ) } - describe('walk-bench', () => { - test('walk-bench-wide-and-deep', { skip: !BENCH }, () => { // ~299k nodes: width=8, depth=6. const wideDeep = buildTree(8, 6) @@ -97,5 +91,4 @@ describe('walk-bench', () => { const deep = buildTree(2, 20) measure('deep (w=2,d=20)', deep, 5) }) - }) diff --git a/kt/.editorconfig b/kt/.editorconfig new file mode 100644 index 00000000..e6400e47 --- /dev/null +++ b/kt/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.{kt,kts}] +# ktlint configuration for the Kotlin port. +# This port is a faithful, line-by-line mirror of the canonical TypeScript +# implementation, so we deliberately relax a few ktlint "standard" rules: +# +# - function-naming / property-naming: the port keeps cross-language identifiers +# such as `_injectstr`, `_invalidTypeMsg`, `R_CMD_KEY`, `T_string`, `CORPUS`, +# and the escaped `val` field name, matching the canonical sources. +# - max-line-length: a handful of `when`/expression lines mirror long TS +# expressions verbatim; wrapping them would obscure the 1:1 correspondence. +# (Only the max-line-length *rule* is disabled; `max_line_length` itself is +# left at the conventional 140 so the function-signature/wrapping rules keep +# their familiar behaviour.) +ktlint_standard_function-naming = disabled +ktlint_standard_property-naming = disabled +ktlint_standard_max-line-length = disabled +max_line_length = 140 diff --git a/kt/Makefile b/kt/Makefile new file mode 100644 index 00000000..adfb8954 --- /dev/null +++ b/kt/Makefile @@ -0,0 +1,25 @@ +.PHONY: inspect build test lint clean reset + +GRADLE ?= ./gradlew + +inspect: + @echo "Java version:" + @java -version 2>&1 | head -1 + @echo "" + @$(GRADLE) --version | grep -E 'Gradle|Kotlin' + +build: + $(GRADLE) compileKotlin + +test: + $(GRADLE) test + +# Code quality: detekt (static analysis) + ktlint (style). +lint: + $(GRADLE) detekt ktlintCheck + +clean: + $(GRADLE) clean + +reset: clean + rm -rf .gradle build diff --git a/kt/build.gradle.kts b/kt/build.gradle.kts index 11347380..98f65bd8 100644 --- a/kt/build.gradle.kts +++ b/kt/build.gradle.kts @@ -1,5 +1,8 @@ plugins { kotlin("jvm") version "2.2.0" + // Code quality + id("io.gitlab.arturbosch.detekt") version "1.23.8" + id("org.jlleitschuh.gradle.ktlint") version "12.1.2" } group = "voxgig.struct" @@ -27,3 +30,21 @@ tasks.withType().configureEach tasks.withType().configureEach { options.release.set(17) } + +// ---- Code quality: detekt (static analysis) + ktlint (style) ---- +detekt { + buildUponDefaultConfig = true + config.setFrom(files("$rootDir/detekt.yml")) + basePath = rootDir.absolutePath +} + +ktlint { + ignoreFailures.set(false) +} + +// Convenience aggregate task: `gradle lint` +tasks.register("lint") { + group = "verification" + description = "Runs detekt and ktlint." + dependsOn("detekt", "ktlintCheck") +} diff --git a/kt/detekt.yml b/kt/detekt.yml new file mode 100644 index 00000000..1c180f50 --- /dev/null +++ b/kt/detekt.yml @@ -0,0 +1,79 @@ +# detekt configuration for the Kotlin port. +# https://detekt.dev/ +# This extends detekt's default rule set (buildUponDefaultConfig = true) and +# relaxes a few rules that conflict with mirroring the canonical TypeScript +# source layout. + +build: + maxIssues: 0 + +complexity: + # The ported functions are large by design; don't fail on size alone. + LongMethod: + active: false + LongParameterList: + active: false + CyclomaticComplexMethod: + active: false + NestedBlockDepth: + active: false + TooManyFunctions: + active: false + # The main Struct class and the StructTests harness are intentionally monolithic, + # mirroring the canonical single-file sources. + LargeClass: + active: false + # Boolean guards mirror the canonical TS conditions verbatim; splitting them + # would obscure the 1:1 correspondence. + ComplexCondition: + active: false + +exceptions: + # The ported code mirrors TS `try { ... } catch (e) { ... }` blocks (which catch + # everything); the Kotlin equivalent catches the broad `Exception`. + TooGenericExceptionCaught: + active: false + +style: + MagicNumber: + active: false + ReturnCount: + active: false + WildcardImport: + active: true + # `error(...)` / `check(...)` are used in new code, but some sites still build + # exceptions explicitly to match the canonical messages/types. + UseCheckOrError: + active: false + # The port (and its test harness) keeps a few helpers/constants that exist for + # parity with the canonical sources or for ad-hoc debugging (e.g. `R_CMD_KEY`, + # the `is*Case` test predicates). + UnusedPrivateMember: + active: false + UnusedPrivateProperty: + active: false + # The corpus/test runner loops use multiple break/continue statements for the + # per-entry skip logic, matching the other ports' runners. + LoopWithTooManyJumpStatements: + active: false + MaxLineLength: + # A handful of `when` / expression lines mirror long TS expressions verbatim. + maxLineLength: 200 + +potential-bugs: + # Diagnostic console output in the test runners uses String.format without an + # explicit Locale; this is test-only formatting, not library behaviour. + ImplicitDefaultLocale: + active: false + +naming: + # Type bit-flag constants are `T_string`, `M_VAL`, ... by cross-language convention. + VariableNaming: + active: false + TopLevelPropertyNaming: + active: false + ObjectPropertyNaming: + active: false + # Cross-language helpers keep their canonical names: `_injectstr`, `_invalidTypeMsg`, ... + FunctionNaming: + active: false diff --git a/kt/src/main/kotlin/voxgig/struct/Struct.kt b/kt/src/main/kotlin/voxgig/struct/Struct.kt index 33c19187..f9e8b7df 100644 --- a/kt/src/main/kotlin/voxgig/struct/Struct.kt +++ b/kt/src/main/kotlin/voxgig/struct/Struct.kt @@ -38,11 +38,12 @@ object Struct { const val M_KEYPOST: Int = 2 const val M_VAL: Int = 4 - val MODENAME: Map = mapOf( - M_VAL to "val", - M_KEYPRE to "key:pre", - M_KEYPOST to "key:post", - ) + val MODENAME: Map = + mapOf( + M_VAL to "val", + M_KEYPRE to "key:pre", + M_KEYPOST to "key:post", + ) private val R_META_PATH = Pattern.compile("^([^$]+)\\$([=~])(.+)$") private val R_INJECT_FULL = Pattern.compile("^`(\\$[A-Z]+|[^`]*)[0-9]*`$") @@ -52,7 +53,12 @@ object Struct { val SKIP: Any = Any() fun interface WalkApply { - fun apply(key: String?, value: Any?, parent: Any?, path: List): Any? + fun apply( + key: String?, + value: Any?, + parent: Any?, + path: List, + ): Any? } /** @@ -60,7 +66,12 @@ object Struct { * `$NAME` references during inject/transform/validate. */ fun interface Injector { - fun apply(inj: Injection, value: Any?, ref: String?, store: Any?): Any? + fun apply( + inj: Injection, + value: Any?, + ref: String?, + store: Any?, + ): Any? } /** @@ -68,7 +79,13 @@ object Struct { * applied after inject finishes a node. */ fun interface Modify { - fun apply(value: Any?, key: Any?, parent: Any?, inj: Injection?, store: Any?) + fun apply( + value: Any?, + key: Any?, + parent: Any?, + inj: Injection?, + store: Any?, + ) } /** @@ -77,24 +94,24 @@ object Struct { * Java `static class Injection` (Struct.java:3077). */ class Injection(value: Any?, parent: Any?) { - var mode: Int = M_VAL // M_KEYPRE | M_KEYPOST | M_VAL - var full: Boolean = false // injection consumed the whole key string - var keyI: Int = 0 // index of current key in keys - var keys: MutableList // sibling keys list (shared with prior) - var key: String // current key string - var `val`: Any? // current child value - var parent: Any? // current parent in spec - var path: MutableList // ancestor key chain ending in key - var nodes: MutableList // ancestor node stack ending in parent - var handler: Injector? = null // dispatch hook for `$NAME` references - var errs: MutableList // shared error collector - var meta: MutableMap // shared metadata bag (do not deep-copy) - var dparent: Any? = UNDEF // current data-side parent - var dpath: MutableList // current data-side path - var base: String? = null // base key in store, if any - var modify: Modify? = null // optional value-mutation hook - var prior: Injection? = null // calling injection (chain upwards) - var extra: Any? = null // free-form passthrough + var mode: Int = M_VAL // M_KEYPRE | M_KEYPOST | M_VAL + var full: Boolean = false // injection consumed the whole key string + var keyI: Int = 0 // index of current key in keys + var keys: MutableList // sibling keys list (shared with prior) + var key: String // current key string + var `val`: Any? // current child value + var parent: Any? // current parent in spec + var path: MutableList // ancestor key chain ending in key + var nodes: MutableList // ancestor node stack ending in parent + var handler: Injector? = null // dispatch hook for `$NAME` references + var errs: MutableList // shared error collector + var meta: MutableMap // shared metadata bag (do not deep-copy) + var dparent: Any? = UNDEF // current data-side parent + var dpath: MutableList // current data-side path + var base: String? = null // base key in store, if any + var modify: Modify? = null // optional value-mutation hook + var prior: Injection? = null // calling injection (chain upwards) + var extra: Any? = null // free-form passthrough init { this.`val` = value @@ -143,21 +160,28 @@ object Struct { } /** Build a child injection at keys[keyI], sharing meta/errs/handler/keys. */ - fun child(keyI: Int, keys: MutableList): Injection { + fun child( + keyI: Int, + keys: MutableList, + ): Injection { val key = strkey(keys[keyI]) val v = this.`val` val cinj = Injection(getprop(v, key), v) cinj.keyI = keyI cinj.keys = keys cinj.key = key - val np = path.toMutableList(); np.add(key); cinj.path = np - val nn = nodes.toMutableList(); nn.add(v); cinj.nodes = nn + val np = path.toMutableList() + np.add(key) + cinj.path = np + val nn = nodes.toMutableList() + nn.add(v) + cinj.nodes = nn cinj.mode = this.mode cinj.handler = this.handler cinj.modify = this.modify cinj.base = this.base - cinj.meta = this.meta // shared - cinj.errs = this.errs // shared + cinj.meta = this.meta // shared + cinj.errs = this.errs // shared cinj.prior = this cinj.dpath = this.dpath.toMutableList() cinj.dparent = this.dparent @@ -168,16 +192,20 @@ object Struct { fun setval(value: Any?): Any? = setval(value, 0) /** Set/delete on parent or an ancestor at -ancestor in nodes/path. */ - fun setval(value: Any?, ancestor: Int): Any? { + fun setval( + value: Any?, + ancestor: Int, + ): Any? { val out: Any? if (ancestor < 2) { - out = if (value === UNDEF) { - val p = delprop(this.parent, this.key) - this.parent = p - p - } else { - setprop(this.parent, this.key, value) - } + out = + if (value === UNDEF) { + val p = delprop(this.parent, this.key) + this.parent = p + p + } else { + setprop(this.parent, this.key, value) + } } else { val aval = getelem(this.nodes, 0 - ancestor) val akey = getelem(this.path, 0 - ancestor) @@ -203,17 +231,20 @@ object Struct { } } - private val TYPE_NAMES = arrayOf( - "any", "nil", "boolean", "decimal", "integer", "number", "string", - "function", "symbol", "null", - "", "", "", "", "", "", "", - "list", "map", "instance", - "", "", "", "", - "scalar", "node" - ) + private val TYPE_NAMES = + arrayOf( + "any", "nil", "boolean", "decimal", "integer", "number", "string", + "function", "symbol", "null", + "", "", "", "", "", "", "", + "list", "map", "instance", + "", "", "", "", + "scalar", "node", + ) fun isnode(value: Any?): Boolean = value is Map<*, *> || value is List<*> + fun ismap(value: Any?): Boolean = value is Map<*, *> + fun islist(value: Any?): Boolean = value is List<*> fun iskey(key: Any?): Boolean { @@ -263,14 +294,19 @@ object Struct { return when (value) { is Number -> { val d = value.toDouble() - if (d.isNaN()) T_NOVAL - else if (floor(d) == d) T_SCALAR or T_NUMBER or T_INTEGER - else T_SCALAR or T_NUMBER or T_DECIMAL + if (d.isNaN()) { + T_NOVAL + } else if (floor(d) == d) { + T_SCALAR or T_NUMBER or T_INTEGER + } else { + T_SCALAR or T_NUMBER or T_DECIMAL + } } is String -> T_SCALAR or T_STRING is Boolean -> T_SCALAR or T_BOOLEAN is Function<*>, is java.util.function.Function<*, *>, is Supplier<*>, - is Injector, is Modify, is WalkApply -> T_SCALAR or T_FUNCTION + is Injector, is Modify, is WalkApply, + -> T_SCALAR or T_FUNCTION is List<*> -> T_NODE or T_LIST is Map<*, *> -> T_NODE or T_MAP else -> T_NODE or T_INSTANCE @@ -300,9 +336,16 @@ object Struct { return keysof(value).map { k -> listOf(k, getprop(value, k, UNDEF)) } } - fun getelem(value: Any?, key: Any?): Any? = getelem(value, key, UNDEF) + fun getelem( + value: Any?, + key: Any?, + ): Any? = getelem(value, key, UNDEF) - fun getelem(value: Any?, key: Any?, alt: Any?): Any? { + fun getelem( + value: Any?, + key: Any?, + alt: Any?, + ): Any? { if (value !is List<*> || key == null || key === UNDEF) return resolveAlt(alt) val idx = parseIntKey(key) ?: return resolveAlt(alt) val useIdx = if (idx < 0) value.size + idx else idx @@ -311,9 +354,16 @@ object Struct { return if (out === UNDEF) alt else out } - fun getprop(value: Any?, key: Any?): Any? = getprop(value, key, UNDEF) + fun getprop( + value: Any?, + key: Any?, + ): Any? = getprop(value, key, UNDEF) - fun getprop(value: Any?, key: Any?, alt: Any?): Any? { + fun getprop( + value: Any?, + key: Any?, + alt: Any?, + ): Any? { if (value == null || value === UNDEF || key == null || key === UNDEF) return alt return when (value) { is Map<*, *> -> { @@ -328,9 +378,16 @@ object Struct { } } - fun haskey(value: Any?, key: Any?): Boolean = getprop(value, key, UNDEF) !== UNDEF + fun haskey( + value: Any?, + key: Any?, + ): Boolean = getprop(value, key, UNDEF) !== UNDEF - fun setprop(parent: Any?, key: Any?, value: Any?): Any? { + fun setprop( + parent: Any?, + key: Any?, + value: Any?, + ): Any? { if (!iskey(key)) return parent return when (parent) { is MutableMap<*, *> -> { @@ -356,7 +413,10 @@ object Struct { } } - fun delprop(parent: Any?, key: Any?): Any? { + fun delprop( + parent: Any?, + key: Any?, + ): Any? { if (!iskey(key)) return parent return when (parent) { is MutableMap<*, *> -> { @@ -375,7 +435,10 @@ object Struct { fun clone(value: Any?): Any? = cloneInner(value, IdentityHashMap()) - private fun cloneInner(value: Any?, seen: IdentityHashMap): Any? { + private fun cloneInner( + value: Any?, + seen: IdentityHashMap, + ): Any? { if (value == null || value === UNDEF) return value if (value is String || value is Number || value is Boolean || value is Function<*>) return value if (seen.containsKey(value)) return seen[value] @@ -398,25 +461,41 @@ object Struct { fun flatten(value: Any?): List = flatten(value, 1) - fun flatten(value: Any?, depth: Int?): List { + fun flatten( + value: Any?, + depth: Int?, + ): List { if (value !is List<*>) return emptyList() val out = mutableListOf() flattenInto(value, depth ?: 1, out) return out } - private fun flattenInto(input: List<*>, depth: Int, out: MutableList) { + private fun flattenInto( + input: List<*>, + depth: Int, + out: MutableList, + ) { input.forEach { - if (depth > 0 && it is List<*>) flattenInto(it, depth - 1, out) - else out.add(it) + if (depth > 0 && it is List<*>) { + flattenInto(it, depth - 1, out) + } else { + out.add(it) + } } } - fun filter(value: Any?, check: (List) -> Boolean): List { + fun filter( + value: Any?, + check: (List) -> Boolean, + ): List { return items(value).filter { check(it) }.map { it[1] } } - fun getdef(value: Any?, alt: Any?): Any? = if (value === UNDEF) alt else value + fun getdef( + value: Any?, + alt: Any?, + ): Any? = if (value === UNDEF) alt else value fun jm(vararg kv: Any?): MutableMap { val out = linkedMapOf() @@ -437,17 +516,23 @@ object Struct { return out } - fun replace(s: Any?, from: Any?, to: Any?): String { - val rs = when { - s === UNDEF || s == null -> "" - s is String -> s - else -> stringify(s) - } - val toStr = when { - to === UNDEF || to == null -> "" - to is String -> to - else -> stringify(to) - } + fun replace( + s: Any?, + from: Any?, + to: Any?, + ): String { + val rs = + when { + s === UNDEF || s == null -> "" + s is String -> s + else -> stringify(s) + } + val toStr = + when { + to === UNDEF || to == null -> "" + to is String -> to + else -> stringify(to) + } return when (from) { is Pattern -> from.matcher(rs).replaceAll(java.util.regex.Matcher.quoteReplacement(toStr)) is Regex -> from.replace(rs, toStr) @@ -466,7 +551,11 @@ object Struct { return URLEncoder.encode(s.toString(), StandardCharsets.UTF_8).replace("+", "%20") } - fun join(arr: Any?, sep: Any?, url: Any?): String { + fun join( + arr: Any?, + sep: Any?, + url: Any?, + ): String { if (arr !is List<*>) return "" val sepDef = if (sep == null || sep === UNDEF) "," else sep.toString() val urlMode = url == true @@ -490,7 +579,11 @@ object Struct { return out } - fun slice(value: Any?, startObj: Any?, endObj: Any?): Any? { + fun slice( + value: Any?, + startObj: Any?, + endObj: Any?, + ): Any? { var start = if (startObj is Number) floor(startObj.toDouble()).toInt() else null var end = if (endObj is Number) floor(endObj.toDouble()).toInt() else null @@ -507,9 +600,14 @@ object Struct { end = (vlen + start).coerceAtLeast(0) start = 0 } else if (end != null) { - if (end < 0) end = (vlen + end).coerceAtLeast(0) - else if (vlen < end) end = vlen - } else end = vlen + if (end < 0) { + end = (vlen + end).coerceAtLeast(0) + } else if (vlen < end) { + end = vlen + } + } else { + end = vlen + } if (vlen < start) start = vlen if (start >= 0 && start <= (end ?: 0) && (end ?: 0) <= vlen) { if (value is List<*>) return value.subList(start, end!!).toMutableList() @@ -522,30 +620,45 @@ object Struct { return value } - fun pad(value: Any?, paddingObj: Any?, padcharObj: Any?): String { + fun pad( + value: Any?, + paddingObj: Any?, + padcharObj: Any?, + ): String { val s = if (value is String) value else stringify(value) val padding = if (paddingObj is Number) floor(paddingObj.toDouble()).toInt() else 44 val pc = ((padcharObj?.toString() ?: " ") + " ").substring(0, 1) - return if (padding >= 0) s + pc.repeat((padding - s.length).coerceAtLeast(0)) - else pc.repeat((-padding - s.length).coerceAtLeast(0)) + s + return if (padding >= 0) { + s + pc.repeat((padding - s.length).coerceAtLeast(0)) + } else { + pc.repeat((-padding - s.length).coerceAtLeast(0)) + s + } } fun stringify(value: Any?): String = stringify(value, null) - fun stringify(value: Any?, maxlen: Int?): String { - val out = when { - value === UNDEF -> "" - value is String -> value - else -> try { - stringifyStable(value, IdentityHashMap()) - } catch (_: Exception) { - "__STRINGIFY_FAILED__" + fun stringify( + value: Any?, + maxlen: Int?, + ): String { + val out = + when { + value === UNDEF -> "" + value is String -> value + else -> + try { + stringifyStable(value, IdentityHashMap()) + } catch (_: Exception) { + "__STRINGIFY_FAILED__" + } } - } return if (maxlen != null && maxlen >= 0 && out.length > maxlen) out.substring(0, (maxlen - 3).coerceAtLeast(0)) + "..." else out } - private fun stringifyStable(value: Any?, seen: IdentityHashMap): String { + private fun stringifyStable( + value: Any?, + seen: IdentityHashMap, + ): String { if (value == null) return "null" if (value is String) return value if (value is Number) return numstr(value) @@ -555,27 +668,34 @@ object Struct { return when (value) { is List<*> -> { val parts = value.map { stringifyStable(it, seen) } - seen.remove(value); "[" + parts.joinToString(",") + "]" + seen.remove(value) + "[" + parts.joinToString(",") + "]" } is Map<*, *> -> { val keys = value.keys.map { it.toString() }.sorted() val parts = keys.map { "$it:${stringifyStable((value as Map)[it], seen)}" } - seen.remove(value); "{" + parts.joinToString(",") + "}" + seen.remove(value) + "{" + parts.joinToString(",") + "}" } else -> { - seen.remove(value); value.toString() + seen.remove(value) + value.toString() } } } fun jsonify(value: Any?): String = jsonify(value, null) - fun jsonify(value: Any?, flags: Any?): String { + fun jsonify( + value: Any?, + flags: Any?, + ): String { if (value === UNDEF) return "null" var indent = 2 var offset = 0 if (flags is Map<*, *>) { - val iv = flags["indent"]; val ov = flags["offset"] + val iv = flags["indent"] + val ov = flags["offset"] if (iv is Number) indent = iv.toInt() if (ov is Number) offset = ov.toInt() } @@ -595,7 +715,10 @@ object Struct { } } - private fun rewriteIndent(pretty: String, indent: Int): String { + private fun rewriteIndent( + pretty: String, + indent: Int, + ): String { return pretty.split("\n").joinToString("\n") { line -> val spaces = line.takeWhile { it == ' ' }.length val level = spaces / 2 @@ -603,7 +726,10 @@ object Struct { } } - private fun toJsonSafe(value: Any?, seen: IdentityHashMap): Any? { + private fun toJsonSafe( + value: Any?, + seen: IdentityHashMap, + ): Any? { if (value == null || value === UNDEF) return null if (value is String || value is Boolean) return value if (value is Number) return jsonNumber(value) @@ -613,15 +739,18 @@ object Struct { return when (value) { is List<*> -> { val out = value.map { toJsonSafe(it, seen) } - seen.remove(value); out + seen.remove(value) + out } is Map<*, *> -> { val out = linkedMapOf() value.forEach { (k, v) -> out[k.toString()] = toJsonSafe(v, seen) } - seen.remove(value); out + seen.remove(value) + out } else -> { - seen.remove(value); null + seen.remove(value) + null } } } @@ -637,16 +766,25 @@ object Struct { } fun pathify(value: Any?): String = pathify(value, null, null) - fun pathify(value: Any?, from: Any?): String = pathify(value, from, null) - fun pathify(value: Any?, startIn: Any?, endIn: Any?): String { + fun pathify( + value: Any?, + from: Any?, + ): String = pathify(value, from, null) + + fun pathify( + value: Any?, + startIn: Any?, + endIn: Any?, + ): String { val start = if (startIn is Number) startIn.toInt().coerceAtLeast(0) else 0 val end = if (endIn is Number) endIn.toInt().coerceAtLeast(0) else 0 - val path: MutableList? = when (value) { - is List<*> -> value.toMutableList() - is String, is Number -> mutableListOf(value) - else -> null - } + val path: MutableList? = + when (value) { + is List<*> -> value.toMutableList() + is String, is Number -> mutableListOf(value) + else -> null + } if (path != null) { val sp = slice(path, start, path.size - end) val use = (sp as? List<*>) ?: emptyList() @@ -661,13 +799,18 @@ object Struct { return "" } - fun setpath(store: Any?, path: Any?, value: Any?): Any? { - val parts: MutableList = when (path) { - is List<*> -> path.toMutableList() - is String -> path.split(".").toMutableList() - is Number -> mutableListOf(path) - else -> return UNDEF - } + fun setpath( + store: Any?, + path: Any?, + value: Any?, + ): Any? { + val parts: MutableList = + when (path) { + is List<*> -> path.toMutableList() + is String -> path.split(".").toMutableList() + is Number -> mutableListOf(path) + else -> return UNDEF + } if (parts.isEmpty()) return UNDEF var parent = store for (i in 0 until parts.size - 1) { @@ -685,9 +828,23 @@ object Struct { return parent } - fun walk(value: Any?, apply: WalkApply): Any? = walk(value, apply, null, 32) - fun walk(value: Any?, before: WalkApply?, after: WalkApply?): Any? = walk(value, before, after, 32) - fun walk(value: Any?, before: WalkApply?, after: WalkApply?, maxdepth: Int): Any? { + fun walk( + value: Any?, + apply: WalkApply, + ): Any? = walk(value, apply, null, 32) + + fun walk( + value: Any?, + before: WalkApply?, + after: WalkApply?, + ): Any? = walk(value, before, after, 32) + + fun walk( + value: Any?, + before: WalkApply?, + after: WalkApply?, + maxdepth: Int, + ): Any? { return walkDescend(value, before, after, maxdepth, null, null, mutableListOf()) } @@ -698,7 +855,7 @@ object Struct { maxdepth: Int, key: String?, parent: Any?, - path: MutableList + path: MutableList, ): Any? { var out = value if (before != null) out = before.apply(key, out, parent, path) @@ -721,7 +878,10 @@ object Struct { fun merge(value: Any?): Any? = merge(value, 32) - fun merge(value: Any?, maxdepthIn: Int): Any? { + fun merge( + value: Any?, + maxdepthIn: Int, + ): Any? { val md = if (maxdepthIn < 0) 0 else maxdepthIn if (value !is List<*>) return value if (value.isEmpty()) return null @@ -736,45 +896,54 @@ object Struct { val dst = arrayOfNulls(33) cur[0] = out dst[0] = out - val before = WalkApply { key, v, _, path -> - val pI = path.size - if (md <= pI) { - if (key != null) cur[pI - 1] = setprop(cur[pI - 1], key, v) - } else if (!isnode(v)) { - cur[pI] = v - } else { - if (pI > 0 && key != null) { - dst[pI] = getprop(dst[pI - 1], key, UNDEF).let { if (it === UNDEF) null else it } - } - val tval = dst[pI] - cur[pI] = when { - tval == null && (typify(v) and T_INSTANCE) == 0 -> if (islist(v)) mutableListOf() else linkedMapOf() - typify(v) == typify(tval) -> tval - else -> v + val before = + WalkApply { key, v, _, path -> + val pI = path.size + if (md <= pI) { + if (key != null) cur[pI - 1] = setprop(cur[pI - 1], key, v) + } else if (!isnode(v)) { + cur[pI] = v + } else { + if (pI > 0 && key != null) { + dst[pI] = getprop(dst[pI - 1], key, UNDEF).let { if (it === UNDEF) null else it } + } + val tval = dst[pI] + cur[pI] = + when { + tval == null && (typify(v) and T_INSTANCE) == 0 -> if (islist(v)) mutableListOf() else linkedMapOf() + typify(v) == typify(tval) -> tval + else -> v + } } + v + } + val after = + WalkApply { key, _, _, path -> + val cI = path.size + if (key == null || cI <= 0) return@WalkApply cur[0] + val v = cur[cI] + cur[cI - 1] = setprop(cur[cI - 1], key, v) + v } - v - } - val after = WalkApply { key, _, _, path -> - val cI = path.size - if (key == null || cI <= 0) return@WalkApply cur[0] - val v = cur[cI] - cur[cI - 1] = setprop(cur[cI - 1], key, v) - v - } walk(obj, before, after, md) out = cur[0] } } if (md == 0) { out = getelem(value, -1) - if (out is List<*>) out = mutableListOf() - else if (out is Map<*, *>) out = linkedMapOf() + if (out is List<*>) { + out = mutableListOf() + } else if (out is Map<*, *>) { + out = linkedMapOf() + } } return out } - fun getpath(store: Any?, path: Any?): Any? = getpath(store, path, null as Injection?) + fun getpath( + store: Any?, + path: Any?, + ): Any? = getpath(store, path, null as Injection?) /** * Injection-based getpath. Builds a transient view from inj.base/dparent/dpath/ @@ -782,14 +951,23 @@ object Struct { * lookup, then invokes inj.handler if set. Mirrors Java getpath(Object, Object, * Injection) (Struct.java:1461). */ - fun getpath(store: Any?, path: Any?, inj: Injection?): Any? { - val view: MutableMap? = if (inj == null) null else linkedMapOf().also { - if (inj.base != null) it["base"] = inj.base - it["dparent"] = inj.dparent - it["dpath"] = inj.dpath - it["meta"] = inj.meta - it["key"] = inj.key - } + fun getpath( + store: Any?, + path: Any?, + inj: Injection?, + ): Any? { + val view: MutableMap? = + if (inj == null) { + null + } else { + linkedMapOf().also { + if (inj.base != null) it["base"] = inj.base + it["dparent"] = inj.dparent + it["dpath"] = inj.dpath + it["meta"] = inj.meta + it["key"] = inj.key + } + } val parts = pathParts(path) var value = getpathInner(store, path, parts, view) if (inj?.handler != null) { @@ -798,7 +976,10 @@ object Struct { return value } - fun inject(value: Any?, store: Any?): Any? = inject(value, store, null as Injection?) + fun inject( + value: Any?, + store: Any?, + ): Any? = inject(value, store, null as Injection?) /** * Canonical Injection-based inject. Mirrors TS inject (StructUtility.ts:1264) @@ -806,7 +987,11 @@ object Struct { * three-phase machine (M_KEYPRE / M_VAL / M_KEYPOST) for map/list children * and dispatches `$NAME` references via inj.handler. */ - fun inject(value: Any?, store: Any?, injdef: Injection?): Any? { + fun inject( + value: Any?, + store: Any?, + injdef: Injection?, + ): Any? { var v = value val inj: Injection val isInitial = injdef == null || injdef.prior == null @@ -843,7 +1028,11 @@ object Struct { val nonDollar = mutableListOf() val dollar = mutableListOf() for (k in nodekeys) if (k.contains("$")) dollar.add(k) else nonDollar.add(k) - nodekeys = mutableListOf().also { it.addAll(nonDollar); it.addAll(dollar) } + nodekeys = + mutableListOf().also { + it.addAll(nonDollar) + it.addAll(dollar) + } } var nkI = 0 while (nkI < nodekeys.size) { @@ -889,7 +1078,11 @@ object Struct { * _injectstr (Struct.java:1490). Handles full `\`...\`` patterns (whole-string * match) and partial inline patterns, then invokes inj.handler on the result. */ - private fun _injectstr(value: String?, store: Any?, inj: Injection?): Any? { + private fun _injectstr( + value: String?, + store: Any?, + inj: Injection?, + ): Any? { if (value.isNullOrEmpty()) return "" val full = R_INJECT_FULL.matcher(value) if (full.matches()) { @@ -923,41 +1116,50 @@ object Struct { * resolved value back onto inj.parent[inj.key]. Mirrors TS _injecthandler * (StructUtility.ts) and Java _injecthandler (Struct.java:1631). */ - val _injecthandler: Injector = Injector { inj, value, ref, store -> - var out = value - val iscmd = isfunc(value) && (ref == null || ref.startsWith("$")) - if (iscmd) { - out = when (value) { - is Injector -> value.apply(inj, value, ref, store) - is java.util.function.Function<*, *> -> { - @Suppress("UNCHECKED_CAST") - (value as java.util.function.Function).apply(inj) - } - is Function1<*, *> -> { - @Suppress("UNCHECKED_CAST") - (value as (Any?) -> Any?).invoke(inj) - } - is java.util.function.Supplier<*> -> value.get() - else -> value + val _injecthandler: Injector = + Injector { inj, value, ref, store -> + var out = value + val iscmd = isfunc(value) && (ref == null || ref.startsWith("$")) + if (iscmd) { + out = + when (value) { + is Injector -> value.apply(inj, value, ref, store) + is java.util.function.Function<*, *> -> { + @Suppress("UNCHECKED_CAST") + (value as java.util.function.Function).apply(inj) + } + is Function1<*, *> -> { + @Suppress("UNCHECKED_CAST") + (value as (Any?) -> Any?).invoke(inj) + } + is java.util.function.Supplier<*> -> value.get() + else -> value + } + } else if (inj.mode == M_VAL && inj.full) { + inj.setval(value) } - } else if (inj.mode == M_VAL && inj.full) { - inj.setval(value) + out } - out - } // Mirrors TS checkPlacement (StructUtility.ts:2920) and Java (Struct.java:1537). - private val PLACEMENT: Map = mapOf( - M_VAL to "value", - M_KEYPRE to "key", - M_KEYPOST to "key", - ) + private val PLACEMENT: Map = + mapOf( + M_VAL to "value", + M_KEYPRE to "key", + M_KEYPOST to "key", + ) - fun checkPlacement(modes: Int, ijname: String, parentTypes: Int, inj: Injection): Boolean { + fun checkPlacement( + modes: Int, + ijname: String, + parentTypes: Int, + inj: Injection, + ): Boolean { if ((modes and inj.mode) == 0) { - val expected = listOf(M_KEYPRE, M_KEYPOST, M_VAL) - .filter { (modes and it) != 0 } - .joinToString(",") { PLACEMENT[it] ?: "?" } + val expected = + listOf(M_KEYPRE, M_KEYPOST, M_VAL) + .filter { (modes and it) != 0 } + .joinToString(",") { PLACEMENT[it] ?: "?" } inj.errs.add("\$$ijname: invalid placement as ${PLACEMENT[inj.mode]}, expected: $expected.") return false } @@ -972,7 +1174,10 @@ object Struct { } // Mirrors TS injectorArgs (StructUtility.ts:2947). Returns [errOrUNDEF, arg1, arg2, ...]. - fun injectorArgs(argTypes: IntArray, args: List): Array { + fun injectorArgs( + argTypes: IntArray, + args: List, + ): Array { val numargs = argTypes.size val found = arrayOfNulls(1 + numargs) found[0] = UNDEF @@ -990,7 +1195,11 @@ object Struct { // Mirrors TS injectChild (StructUtility.ts:2967). Walks inj.prior/inj.prior.prior // to relocate the child within a $FORMAT chain, then re-injects. - fun injectChild(child: Any?, store: Any?, inj: Injection): Injection { + fun injectChild( + child: Any?, + store: Any?, + inj: Injection, + ): Injection { var cinj: Injection = inj val prior = inj.prior if (prior != null) { @@ -1016,53 +1225,62 @@ object Struct { // Each Injector implements one of the 11 canonical transform commands. /** $DELETE: drop the current key. */ - val transform_DELETE: Injector = Injector { inj, _, _, _ -> - inj.setval(UNDEF) - UNDEF - } + val transform_DELETE: Injector = + Injector { inj, _, _, _ -> + inj.setval(UNDEF) + UNDEF + } /** $COPY: copy the value at the current key from dparent. */ - val transform_COPY: Injector = Injector { inj, _, _, _ -> - if (!checkPlacement(M_VAL, "COPY", T_ANY, inj)) return@Injector UNDEF - val out = getprop(inj.dparent, inj.key) - inj.setval(out) - out - } + val transform_COPY: Injector = + Injector { inj, _, _, _ -> + if (!checkPlacement(M_VAL, "COPY", T_ANY, inj)) return@Injector UNDEF + val out = getprop(inj.dparent, inj.key) + inj.setval(out) + out + } /** $KEY: emit the parent key, optionally renamed via `$KEY` or `$ANNO.KEY`. */ - val transform_KEY: Injector = Injector { inj, _, _, _ -> - if (inj.mode != M_VAL) return@Injector UNDEF - val keyspec = getprop(inj.parent, "`\$KEY`", UNDEF) - if (keyspec !== UNDEF) { - delprop(inj.parent, "`\$KEY`") - return@Injector getprop(inj.dparent, keyspec) - } - val anno = getprop(inj.parent, "`\$ANNO`", UNDEF) - getprop(anno, "KEY", getelem(inj.path, -2)) - } + val transform_KEY: Injector = + Injector { inj, _, _, _ -> + if (inj.mode != M_VAL) return@Injector UNDEF + val keyspec = getprop(inj.parent, "`\$KEY`", UNDEF) + if (keyspec !== UNDEF) { + delprop(inj.parent, "`\$KEY`") + return@Injector getprop(inj.dparent, keyspec) + } + val anno = getprop(inj.parent, "`\$ANNO`", UNDEF) + getprop(anno, "KEY", getelem(inj.path, -2)) + } /** $ANNO: drop the annotation marker. */ - val transform_ANNO: Injector = Injector { inj, _, _, _ -> - delprop(inj.parent, "`\$ANNO`") - UNDEF - } + val transform_ANNO: Injector = + Injector { inj, _, _, _ -> + delprop(inj.parent, "`\$ANNO`") + UNDEF + } /** $MERGE: deep-merge a list of objects over the current parent. */ - val transform_MERGE: Injector = Injector { inj, _, _, _ -> - if (inj.mode == M_KEYPRE) return@Injector inj.key - if (inj.mode != M_KEYPOST) return@Injector UNDEF - val args = getprop(inj.parent, inj.key) - val argList: MutableList = if (args is List<*>) { - @Suppress("UNCHECKED_CAST") (args.toMutableList() as MutableList) - } else mutableListOf(args) - inj.setval(UNDEF) - val mergelist = mutableListOf() - mergelist.add(inj.parent) - mergelist.addAll(argList) - mergelist.add(clone(inj.parent)) - merge(mergelist) - inj.key - } + val transform_MERGE: Injector = + Injector { inj, _, _, _ -> + if (inj.mode == M_KEYPRE) return@Injector inj.key + if (inj.mode != M_KEYPOST) return@Injector UNDEF + val args = getprop(inj.parent, inj.key) + val argList: MutableList = + if (args is List<*>) { + @Suppress("UNCHECKED_CAST") + (args.toMutableList() as MutableList) + } else { + mutableListOf(args) + } + inj.setval(UNDEF) + val mergelist = mutableListOf() + mergelist.add(inj.parent) + mergelist.addAll(argList) + mergelist.add(clone(inj.parent)) + merge(mergelist) + inj.key + } // FORMATTER: name → WalkApply for $FORMAT. private fun jsString(v: Any?): String { @@ -1076,402 +1294,455 @@ object Struct { return v.toString() } - val FORMATTER: Map = linkedMapOf( - "identity" to WalkApply { _, v, _, _ -> v }, - "upper" to WalkApply { _, v, _, _ -> if (isnode(v)) v else jsString(v).uppercase(Locale.ROOT) }, - "lower" to WalkApply { _, v, _, _ -> if (isnode(v)) v else jsString(v).lowercase(Locale.ROOT) }, - "string" to WalkApply { _, v, _, _ -> if (isnode(v)) v else jsString(v) }, - "number" to WalkApply { _, v, _, _ -> - if (isnode(v)) v else try { - val d = ("" + v).toDouble() - when { - d.isNaN() -> 0L - floor(d) == d -> d.toLong() - else -> d - } - } catch (_: Exception) { 0L } - }, - "integer" to WalkApply { _, v, _, _ -> - if (isnode(v)) v else try { ("" + v).toDouble().toLong() } catch (_: Exception) { 0L } - }, - "concat" to WalkApply { k, v, _, _ -> - if (k != null || v !is List<*>) v - else { - val sb = StringBuilder() - for (item in items(v)) { - val x = item[1] - if (!isnode(x)) sb.append(jsString(x)) - } - sb.toString() - } - }, - ) + val FORMATTER: Map = + linkedMapOf( + "identity" to WalkApply { _, v, _, _ -> v }, + "upper" to WalkApply { _, v, _, _ -> if (isnode(v)) v else jsString(v).uppercase(Locale.ROOT) }, + "lower" to WalkApply { _, v, _, _ -> if (isnode(v)) v else jsString(v).lowercase(Locale.ROOT) }, + "string" to WalkApply { _, v, _, _ -> if (isnode(v)) v else jsString(v) }, + "number" to + WalkApply { _, v, _, _ -> + if (isnode(v)) { + v + } else { + try { + val d = ("" + v).toDouble() + when { + d.isNaN() -> 0L + floor(d) == d -> d.toLong() + else -> d + } + } catch (_: Exception) { + 0L + } + } + }, + "integer" to + WalkApply { _, v, _, _ -> + if (isnode(v)) { + v + } else { + try { + ("" + v).toDouble().toLong() + } catch (_: Exception) { + 0L + } + } + }, + "concat" to + WalkApply { k, v, _, _ -> + if (k != null || v !is List<*>) { + v + } else { + val sb = StringBuilder() + for (item in items(v)) { + val x = item[1] + if (!isnode(x)) sb.append(jsString(x)) + } + sb.toString() + } + }, + ) /** $FORMAT: walk a sub-spec applying a named formatter. */ - val transform_FORMAT: Injector = Injector { inj, _, _, store -> - if (inj.keys.size > 1) { - val first = inj.keys[0] - inj.keys.clear(); inj.keys.add(first) - } - if (inj.mode != M_VAL) return@Injector UNDEF + val transform_FORMAT: Injector = + Injector { inj, _, _, store -> + if (inj.keys.size > 1) { + val first = inj.keys[0] + inj.keys.clear() + inj.keys.add(first) + } + if (inj.mode != M_VAL) return@Injector UNDEF - val name = getprop(inj.parent, 1) - val child = getprop(inj.parent, 2) + val name = getprop(inj.parent, 1) + val child = getprop(inj.parent, 2) - val tkey = getelem(inj.path, -2) - var target = getelem(inj.nodes, -2) - if (target === UNDEF) target = getelem(inj.nodes, -1) + val tkey = getelem(inj.path, -2) + var target = getelem(inj.nodes, -2) + if (target === UNDEF) target = getelem(inj.nodes, -1) - val cinj = injectChild(child, store, inj) - val resolved = cinj.`val` + val cinj = injectChild(child, store, inj) + val resolved = cinj.`val` - val formatter: WalkApply? = if (name is WalkApply) name else FORMATTER[name?.toString() ?: ""] - if (formatter == null) { - inj.errs.add("\$FORMAT: unknown format: $name.") - return@Injector UNDEF + val formatter: WalkApply? = if (name is WalkApply) name else FORMATTER[name?.toString() ?: ""] + if (formatter == null) { + inj.errs.add("\$FORMAT: unknown format: $name.") + return@Injector UNDEF + } + val out = walk(resolved, formatter) + setprop(target, tkey, out) + out } - val out = walk(resolved, formatter) - setprop(target, tkey, out) - out - } /** $APPLY: call a custom function on a resolved sub-spec value. */ - val transform_APPLY: Injector = Injector { inj, _, _, store -> - if (!checkPlacement(M_VAL, "APPLY", T_LIST, inj)) return@Injector UNDEF - - val args = mutableListOf() - val parent = inj.parent - if (parent is List<*> && parent.size > 1) { - for (i in 1 until parent.size) args.add(parent[i]) - } - val checked = injectorArgs(intArrayOf(T_FUNCTION, T_ANY), args) - if (checked[0] !== UNDEF) { - inj.errs.add("\$APPLY: ${checked[0]}") - return@Injector UNDEF - } - val applyFn = checked[1] - val child = checked[2] + val transform_APPLY: Injector = + Injector { inj, _, _, store -> + if (!checkPlacement(M_VAL, "APPLY", T_LIST, inj)) return@Injector UNDEF + + val args = mutableListOf() + val parent = inj.parent + if (parent is List<*> && parent.size > 1) { + for (i in 1 until parent.size) args.add(parent[i]) + } + val checked = injectorArgs(intArrayOf(T_FUNCTION, T_ANY), args) + if (checked[0] !== UNDEF) { + inj.errs.add("\$APPLY: ${checked[0]}") + return@Injector UNDEF + } + val applyFn = checked[1] + val child = checked[2] - val tkey = getelem(inj.path, -2) - var target = getelem(inj.nodes, -2) - if (target === UNDEF) target = getelem(inj.nodes, -1) + val tkey = getelem(inj.path, -2) + var target = getelem(inj.nodes, -2) + if (target === UNDEF) target = getelem(inj.nodes, -1) - val cinj = injectChild(child, store, inj) - val resolved = cinj.`val` + val cinj = injectChild(child, store, inj) + val resolved = cinj.`val` - val out: Any? = when (applyFn) { - is java.util.function.Function<*, *> -> { - @Suppress("UNCHECKED_CAST") - (applyFn as java.util.function.Function).apply(resolved) - } - is Function1<*, *> -> { - @Suppress("UNCHECKED_CAST") - (applyFn as (Any?) -> Any?).invoke(resolved) - } - else -> UNDEF + val out: Any? = + when (applyFn) { + is java.util.function.Function<*, *> -> { + @Suppress("UNCHECKED_CAST") + (applyFn as java.util.function.Function).apply(resolved) + } + is Function1<*, *> -> { + @Suppress("UNCHECKED_CAST") + (applyFn as (Any?) -> Any?).invoke(resolved) + } + else -> UNDEF + } + setprop(target, tkey, out) + out } - setprop(target, tkey, out) - out - } /** $EACH: convert a node into a list by cloning the child template per source entry. */ - val transform_EACH: Injector = Injector { inj, _, _, store -> - if (!checkPlacement(M_VAL, "EACH", T_LIST, inj)) return@Injector UNDEF - if (inj.keys.size > 1) inj.keys.subList(1, inj.keys.size).clear() - - val args = mutableListOf() - val parent = inj.parent - if (parent is List<*>) for (i in 1 until parent.size) args.add(parent[i]) - val checked = injectorArgs(intArrayOf(T_STRING, T_ANY), args) - if (checked[0] !== UNDEF) { - inj.errs.add("\$EACH: ${checked[0]}") - return@Injector UNDEF - } - val srcpath = checked[1] as String - val child = checked[2] - - val srcstore = getprop(store, inj.base, store) - val src = getpath(srcstore, srcpath, inj) - val srctype = typify(src) - - val tkey = getelem(inj.path, -2) - var target = getelem(inj.nodes, -2) - if (target === UNDEF) target = getelem(inj.nodes, -1) - - val tval = mutableListOf() - if ((T_LIST and srctype) != 0 && src is List<*>) { - for (i in src.indices) tval.add(clone(child)) - } else if ((T_MAP and srctype) != 0 && src is Map<*, *>) { - for ((kAny, _) in src) { - val cloned = clone(child) - val keyMap = linkedMapOf("KEY" to kAny.toString()) - val annoMap = linkedMapOf("`\$ANNO`" to keyMap) - val mergeArgs = mutableListOf(cloned, annoMap) - tval.add(merge(mergeArgs, 1)) - } - } - - var rval: Any? = mutableListOf() - - if (size(tval) > 0) { - val tcur: Any? = when (src) { - is List<*> -> src.toMutableList() - is Map<*, *> -> src.values.toMutableList() - else -> UNDEF + val transform_EACH: Injector = + Injector { inj, _, _, store -> + if (!checkPlacement(M_VAL, "EACH", T_LIST, inj)) return@Injector UNDEF + if (inj.keys.size > 1) inj.keys.subList(1, inj.keys.size).clear() + + val args = mutableListOf() + val parent = inj.parent + if (parent is List<*>) for (i in 1 until parent.size) args.add(parent[i]) + val checked = injectorArgs(intArrayOf(T_STRING, T_ANY), args) + if (checked[0] !== UNDEF) { + inj.errs.add("\$EACH: ${checked[0]}") + return@Injector UNDEF + } + val srcpath = checked[1] as String + val child = checked[2] + + val srcstore = getprop(store, inj.base, store) + val src = getpath(srcstore, srcpath, inj) + val srctype = typify(src) + + val tkey = getelem(inj.path, -2) + var target = getelem(inj.nodes, -2) + if (target === UNDEF) target = getelem(inj.nodes, -1) + + val tval = mutableListOf() + if ((T_LIST and srctype) != 0 && src is List<*>) { + for (i in src.indices) tval.add(clone(child)) + } else if ((T_MAP and srctype) != 0 && src is Map<*, *>) { + for ((kAny, _) in src) { + val cloned = clone(child) + val keyMap = linkedMapOf("KEY" to kAny.toString()) + val annoMap = linkedMapOf("`\$ANNO`" to keyMap) + val mergeArgs = mutableListOf(cloned, annoMap) + tval.add(merge(mergeArgs, 1)) + } } - val ckey = getelem(inj.path, -2) - val ckeyStr = strkey(ckey) + var rval: Any? = mutableListOf() - val tpathRaw = slice(inj.path, -1, null) - @Suppress("UNCHECKED_CAST") - val tpath: MutableList = if (tpathRaw is List<*>) (tpathRaw as List).toMutableList() else mutableListOf() + if (size(tval) > 0) { + val tcur: Any? = + when (src) { + is List<*> -> src.toMutableList() + is Map<*, *> -> src.values.toMutableList() + else -> UNDEF + } - val dpath = mutableListOf(S_DTOP) - if (srcpath.isNotEmpty()) for (part in srcpath.split(".")) dpath.add(part) - dpath.add("\$:$ckeyStr") + val ckey = getelem(inj.path, -2) + val ckeyStr = strkey(ckey) - val tcurMap = linkedMapOf(ckeyStr to tcur) - var tcurOut: Any? = tcurMap + val tpathRaw = slice(inj.path, -1, null) - if (size(tpath) > 1) { - val pkey = getelem(inj.path, -3, S_DTOP) - val pkeyStr = strkey(pkey) - val wrap = linkedMapOf(pkeyStr to tcurOut) - tcurOut = wrap - dpath.add("\$:$pkeyStr") - } + @Suppress("UNCHECKED_CAST") + val tpath: MutableList = if (tpathRaw is List<*>) (tpathRaw as List).toMutableList() else mutableListOf() - val singleKey = mutableListOf(ckeyStr) - val tinj = inj.child(0, singleKey) - tinj.path = tpath - val slicedNodes = slice(inj.nodes, -1, null) - @Suppress("UNCHECKED_CAST") - tinj.nodes = if (slicedNodes is List<*>) (slicedNodes as List).toMutableList() else mutableListOf() + val dpath = mutableListOf(S_DTOP) + if (srcpath.isNotEmpty()) for (part in srcpath.split(".")) dpath.add(part) + dpath.add("\$:$ckeyStr") - tinj.parent = getelem(tinj.nodes, -1) - setprop(tinj.parent, ckey, tval) + val tcurMap = linkedMapOf(ckeyStr to tcur) + var tcurOut: Any? = tcurMap - tinj.`val` = tval - tinj.dpath = dpath - tinj.dparent = tcurOut + if (size(tpath) > 1) { + val pkey = getelem(inj.path, -3, S_DTOP) + val pkeyStr = strkey(pkey) + val wrap = linkedMapOf(pkeyStr to tcurOut) + tcurOut = wrap + dpath.add("\$:$pkeyStr") + } - inject(tval, store, tinj) - rval = tinj.`val` - } + val singleKey = mutableListOf(ckeyStr) + val tinj = inj.child(0, singleKey) + tinj.path = tpath + val slicedNodes = slice(inj.nodes, -1, null) + @Suppress("UNCHECKED_CAST") + tinj.nodes = if (slicedNodes is List<*>) (slicedNodes as List).toMutableList() else mutableListOf() - setprop(target, tkey, rval) - if (rval is List<*> && rval.isNotEmpty()) rval[0] else UNDEF - } + tinj.parent = getelem(tinj.nodes, -1) + setprop(tinj.parent, ckey, tval) - /** $PACK: convert a list/map into a keyed map. */ - val transform_PACK: Injector = Injector { inj, _, _, store -> - if (!checkPlacement(M_KEYPRE, "PACK", T_MAP, inj)) return@Injector UNDEF - - val argsRaw = getprop(inj.parent, inj.key) - val argList = mutableListOf() - if (argsRaw is List<*>) argList.addAll(argsRaw) - val checked = injectorArgs(intArrayOf(T_STRING, T_ANY), argList) - if (checked[0] !== UNDEF) { - inj.errs.add("\$PACK: ${checked[0]}") - return@Injector UNDEF - } - val srcpath = checked[1] as String - val origchildspec = checked[2] - - val tkey = getelem(inj.path, -2) - val pathsize = size(inj.path) - var target = getelem(inj.nodes, pathsize - 2) - if (target === UNDEF) target = getelem(inj.nodes, pathsize - 1) - - val srcstore = getprop(store, inj.base, store) - val src = getpath(srcstore, srcpath, inj) - - var srcList: MutableList? = null - if (src is List<*>) { - srcList = src.toMutableList() - } else if (src is Map<*, *>) { - srcList = mutableListOf() - for ((kAny, node) in src) { - if (isnode(node)) { - val annoMap = linkedMapOf("KEY" to kAny.toString()) - setprop(node, "`\$ANNO`", annoMap) - srcList.add(node) - } + tinj.`val` = tval + tinj.dpath = dpath + tinj.dparent = tcurOut + + inject(tval, store, tinj) + rval = tinj.`val` } - } - if (srcList == null) return@Injector UNDEF - var keypath: Any? = UNDEF - var child: Any? = origchildspec - if (origchildspec is Map<*, *>) { - keypath = getprop(origchildspec, "`\$KEY`", UNDEF) - delprop(origchildspec, "`\$KEY`") - child = getprop(origchildspec, "`\$VAL`", origchildspec) + setprop(target, tkey, rval) + if (rval is List<*> && rval.isNotEmpty()) rval[0] else UNDEF } - val keypathFinal = keypath - val childFinal = child - val tval = linkedMapOf() - for (i in srcList.indices) { - val item = srcList[i] - val outKey: String = when { - keypathFinal === UNDEF -> { - if (item is Map<*, *> && item.containsKey(S_DKEY)) item[S_DKEY]?.toString() ?: "" - else i.toString() - } - keypathFinal is String && keypathFinal.startsWith("`") -> { - val mergeList = mutableListOf(linkedMapOf(), store, linkedMapOf(S_DTOP to item)) - val merged = merge(mergeList, 1) - inject(keypathFinal, merged)?.toString() ?: "" + /** $PACK: convert a list/map into a keyed map. */ + val transform_PACK: Injector = + Injector { inj, _, _, store -> + if (!checkPlacement(M_KEYPRE, "PACK", T_MAP, inj)) return@Injector UNDEF + + val argsRaw = getprop(inj.parent, inj.key) + val argList = mutableListOf() + if (argsRaw is List<*>) argList.addAll(argsRaw) + val checked = injectorArgs(intArrayOf(T_STRING, T_ANY), argList) + if (checked[0] !== UNDEF) { + inj.errs.add("\$PACK: ${checked[0]}") + return@Injector UNDEF + } + val srcpath = checked[1] as String + val origchildspec = checked[2] + + val tkey = getelem(inj.path, -2) + val pathsize = size(inj.path) + var target = getelem(inj.nodes, pathsize - 2) + if (target === UNDEF) target = getelem(inj.nodes, pathsize - 1) + + val srcstore = getprop(store, inj.base, store) + val src = getpath(srcstore, srcpath, inj) + + var srcList: MutableList? = null + if (src is List<*>) { + srcList = src.toMutableList() + } else if (src is Map<*, *>) { + srcList = mutableListOf() + for ((kAny, node) in src) { + if (isnode(node)) { + val annoMap = linkedMapOf("KEY" to kAny.toString()) + setprop(node, "`\$ANNO`", annoMap) + srcList.add(node) + } } - else -> getpath(item, keypathFinal, inj)?.toString() ?: "" } + if (srcList == null) return@Injector UNDEF - val tchild = clone(childFinal) - setprop(tval, outKey, tchild) - - val anno = getprop(item, "`\$ANNO`", UNDEF) - if (anno === UNDEF) delprop(tchild, "`\$ANNO`") - else setprop(tchild, "`\$ANNO`", anno) - } - - var rval: Map = linkedMapOf() + var keypath: Any? = UNDEF + var child: Any? = origchildspec + if (origchildspec is Map<*, *>) { + keypath = getprop(origchildspec, "`\$KEY`", UNDEF) + delprop(origchildspec, "`\$KEY`") + child = getprop(origchildspec, "`\$VAL`", origchildspec) + } + val keypathFinal = keypath + val childFinal = child - if (!isempty(tval)) { - val tsrc = linkedMapOf() + val tval = linkedMapOf() for (i in srcList.indices) { val item = srcList[i] - val kn: String = when { - keypathFinal === UNDEF -> i.toString() - keypathFinal is String && keypathFinal.startsWith("`") -> { - val mergeList = mutableListOf(linkedMapOf(), store, linkedMapOf(S_DTOP to item)) - val merged = merge(mergeList, 1) - inject(keypathFinal, merged)?.toString() ?: "" + val outKey: String = + when { + keypathFinal === UNDEF -> { + if (item is Map<*, *> && item.containsKey(S_DKEY)) { + item[S_DKEY]?.toString() ?: "" + } else { + i.toString() + } + } + keypathFinal is String && keypathFinal.startsWith("`") -> { + val mergeList = + mutableListOf(linkedMapOf(), store, linkedMapOf(S_DTOP to item)) + val merged = merge(mergeList, 1) + inject(keypathFinal, merged)?.toString() ?: "" + } + else -> getpath(item, keypathFinal, inj)?.toString() ?: "" } - else -> getpath(item, keypathFinal, inj)?.toString() ?: "" + + val tchild = clone(childFinal) + setprop(tval, outKey, tchild) + + val anno = getprop(item, "`\$ANNO`", UNDEF) + if (anno === UNDEF) { + delprop(tchild, "`\$ANNO`") + } else { + setprop(tchild, "`\$ANNO`", anno) } - setprop(tsrc, kn, item) } - val tpathRaw = slice(inj.path, -1, null) - @Suppress("UNCHECKED_CAST") - val tpath: MutableList = if (tpathRaw is List<*>) (tpathRaw as List).toMutableList() else mutableListOf() + var rval: Map = linkedMapOf() + + if (!isempty(tval)) { + val tsrc = linkedMapOf() + for (i in srcList.indices) { + val item = srcList[i] + val kn: String = + when { + keypathFinal === UNDEF -> i.toString() + keypathFinal is String && keypathFinal.startsWith("`") -> { + val mergeList = + mutableListOf(linkedMapOf(), store, linkedMapOf(S_DTOP to item)) + val merged = merge(mergeList, 1) + inject(keypathFinal, merged)?.toString() ?: "" + } + else -> getpath(item, keypathFinal, inj)?.toString() ?: "" + } + setprop(tsrc, kn, item) + } - val ckey = getelem(inj.path, -2) - val ckeyStr = strkey(ckey) + val tpathRaw = slice(inj.path, -1, null) - val dpath = mutableListOf(S_DTOP) - if (srcpath.isNotEmpty()) for (part in srcpath.split(".")) dpath.add(part) - dpath.add("\$:$ckeyStr") + @Suppress("UNCHECKED_CAST") + val tpath: MutableList = if (tpathRaw is List<*>) (tpathRaw as List).toMutableList() else mutableListOf() - var tcurOut: Any? = linkedMapOf(ckeyStr to tsrc) - if (size(tpath) > 1) { - val pkey = getelem(inj.path, -3, S_DTOP) - val pkeyStr = strkey(pkey) - tcurOut = linkedMapOf(pkeyStr to tcurOut) - dpath.add("\$:$pkeyStr") - } + val ckey = getelem(inj.path, -2) + val ckeyStr = strkey(ckey) - val singleKey = mutableListOf(ckeyStr) - val tinj = inj.child(0, singleKey) - tinj.path = tpath - val slicedNodes = slice(inj.nodes, -1, null) - @Suppress("UNCHECKED_CAST") - tinj.nodes = if (slicedNodes is List<*>) (slicedNodes as List).toMutableList() else mutableListOf() + val dpath = mutableListOf(S_DTOP) + if (srcpath.isNotEmpty()) for (part in srcpath.split(".")) dpath.add(part) + dpath.add("\$:$ckeyStr") - tinj.parent = getelem(tinj.nodes, -1) - tinj.`val` = tval - tinj.dpath = dpath - tinj.dparent = tcurOut + var tcurOut: Any? = linkedMapOf(ckeyStr to tsrc) + if (size(tpath) > 1) { + val pkey = getelem(inj.path, -3, S_DTOP) + val pkeyStr = strkey(pkey) + tcurOut = linkedMapOf(pkeyStr to tcurOut) + dpath.add("\$:$pkeyStr") + } - inject(tval, store, tinj) - val tv = tinj.`val` - if (tv is Map<*, *>) { - val out = linkedMapOf() - for ((k, vv) in tv) out[k.toString()] = vv - rval = out + val singleKey = mutableListOf(ckeyStr) + val tinj = inj.child(0, singleKey) + tinj.path = tpath + val slicedNodes = slice(inj.nodes, -1, null) + @Suppress("UNCHECKED_CAST") + tinj.nodes = if (slicedNodes is List<*>) (slicedNodes as List).toMutableList() else mutableListOf() + + tinj.parent = getelem(tinj.nodes, -1) + tinj.`val` = tval + tinj.dpath = dpath + tinj.dparent = tcurOut + + inject(tval, store, tinj) + val tv = tinj.`val` + if (tv is Map<*, *>) { + val out = linkedMapOf() + for ((k, vv) in tv) out[k.toString()] = vv + rval = out + } } - } - setprop(target, tkey, rval) - UNDEF - } + setprop(target, tkey, rval) + UNDEF + } /** $REF: resolve a named reference within the original spec, enabling recursive transformations. */ - val transform_REF: Injector = Injector { inj, value, _, store -> - if (inj.mode != M_VAL) return@Injector UNDEF + val transform_REF: Injector = + Injector { inj, value, _, store -> + if (inj.mode != M_VAL) return@Injector UNDEF - val refpath = getprop(inj.parent, 1) - inj.keyI = size(inj.keys) + val refpath = getprop(inj.parent, 1) + inj.keyI = size(inj.keys) - val specHolder = getprop(store, S_DSPEC) - val spec: Any? = when (specHolder) { - is java.util.function.Supplier<*> -> specHolder.get() - is Function0<*> -> specHolder.invoke() - else -> specHolder - } + val specHolder = getprop(store, S_DSPEC) + val spec: Any? = + when (specHolder) { + is java.util.function.Supplier<*> -> specHolder.get() + is Function0<*> -> specHolder.invoke() + else -> specHolder + } - val dpathRaw = slice(inj.path, 1, null) - @Suppress("UNCHECKED_CAST") - val dpath: MutableList = if (dpathRaw is List<*>) (dpathRaw as List).toMutableList() else mutableListOf() - val refInj = Injection(null, null) - refInj.dpath = dpath - refInj.dparent = getpath(spec, dpath) - refInj.handler = _injecthandler - val refResolved = getpath(spec, refpath, refInj) - - val tref = clone(refResolved) - - val hasSubRef = booleanArrayOf(false) - if (isnode(tref)) { - walk(tref, WalkApply { _, v, _, _ -> - if ("`\$REF`" == v) hasSubRef[0] = true - v - }) - } + val dpathRaw = slice(inj.path, 1, null) - val cpathRaw = slice(inj.path, -3, null) - @Suppress("UNCHECKED_CAST") - val cpath: MutableList = if (cpathRaw is List<*>) (cpathRaw as List).toMutableList() else mutableListOf() - val tpathRaw = slice(inj.path, -1, null) - @Suppress("UNCHECKED_CAST") - val tpath: MutableList = if (tpathRaw is List<*>) (tpathRaw as List).toMutableList() else mutableListOf() - val tval = getpath(store, tpath) - var rval: Any? = UNDEF - - if (!hasSubRef[0] || tval !== UNDEF) { - val lastKey = getelem(tpath, -1) - val singleKey = mutableListOf(strkey(lastKey)) - val tinj = inj.child(0, singleKey) - tinj.path = tpath - val slicedNodes = slice(inj.nodes, -1, null) @Suppress("UNCHECKED_CAST") - tinj.nodes = if (slicedNodes is List<*>) (slicedNodes as List).toMutableList() else mutableListOf() - tinj.parent = getelem(inj.nodes, -2) - tinj.`val` = tref - tinj.dpath = cpath.toMutableList() - tinj.dparent = getpath(store, cpath) + val dpath: MutableList = if (dpathRaw is List<*>) (dpathRaw as List).toMutableList() else mutableListOf() + val refInj = Injection(null, null) + refInj.dpath = dpath + refInj.dparent = getpath(spec, dpath) + refInj.handler = _injecthandler + val refResolved = getpath(spec, refpath, refInj) - inject(tref, store, tinj) - rval = tinj.`val` - } + val tref = clone(refResolved) + + val hasSubRef = booleanArrayOf(false) + if (isnode(tref)) { + walk( + tref, + WalkApply { _, v, _, _ -> + if ("`\$REF`" == v) hasSubRef[0] = true + v + }, + ) + } + + val cpathRaw = slice(inj.path, -3, null) - val grandparent = inj.setval(rval, 2) - if (islist(grandparent) && inj.prior != null) { - inj.prior!!.keyI-- + @Suppress("UNCHECKED_CAST") + val cpath: MutableList = if (cpathRaw is List<*>) (cpathRaw as List).toMutableList() else mutableListOf() + val tpathRaw = slice(inj.path, -1, null) + + @Suppress("UNCHECKED_CAST") + val tpath: MutableList = if (tpathRaw is List<*>) (tpathRaw as List).toMutableList() else mutableListOf() + val tval = getpath(store, tpath) + var rval: Any? = UNDEF + + if (!hasSubRef[0] || tval !== UNDEF) { + val lastKey = getelem(tpath, -1) + val singleKey = mutableListOf(strkey(lastKey)) + val tinj = inj.child(0, singleKey) + tinj.path = tpath + val slicedNodes = slice(inj.nodes, -1, null) + @Suppress("UNCHECKED_CAST") + tinj.nodes = if (slicedNodes is List<*>) (slicedNodes as List).toMutableList() else mutableListOf() + tinj.parent = getelem(inj.nodes, -2) + tinj.`val` = tref + tinj.dpath = cpath.toMutableList() + tinj.dparent = getpath(store, cpath) + + inject(tref, store, tinj) + rval = tinj.`val` + } + + val grandparent = inj.setval(rval, 2) + if (islist(grandparent) && inj.prior != null) { + inj.prior!!.keyI-- + } + value } - value - } - fun transform(data: Any?, spec: Any?): Any? = transform(data, spec, null) + fun transform( + data: Any?, + spec: Any?, + ): Any? = transform(data, spec, null) /** * Canonical TS-faithful transform: clone the spec, build a store with * transform_* injectors registered, then call inject(workspec, store, injdef). * Mirrors Java transform (Struct.java:2269) and TS transform (StructUtility.ts:1902). */ - fun transform(data: Any?, spec: Any?, options: Map?): Any? { + fun transform( + data: Any?, + spec: Any?, + options: Map?, + ): Any? { val origspec = spec val workspec = clone(origspec) @@ -1482,6 +1753,7 @@ object Struct { val errsRaw = options?.get("errs") val collect = errsRaw is MutableList<*> + @Suppress("UNCHECKED_CAST") val errs: MutableList = if (collect) errsRaw as MutableList else mutableListOf() @@ -1540,7 +1812,6 @@ object Struct { return if (out === SKIP || out === UNDEF) null else out } - private fun pathifyForHandler(path: Any?): String { return when (path) { null -> pathify(UNDEF) @@ -1552,27 +1823,34 @@ object Struct { private fun pathParts(path: Any?): MutableList? { return when (path) { is String -> if (path.isEmpty()) mutableListOf("") else path.split(".").toMutableList() - is List<*> -> path.map { - when (it) { - is String -> it - is Number -> strkey(it) - else -> strkey(it) - } - }.toMutableList() + is List<*> -> + path.map { + when (it) { + is String -> it + is Number -> strkey(it) + else -> strkey(it) + } + }.toMutableList() else -> null } } - private fun getpathInner(store: Any?, pathOrig: Any?, parts: MutableList?, inj: MutableMap?): Any? { + private fun getpathInner( + store: Any?, + pathOrig: Any?, + parts: MutableList?, + inj: MutableMap?, + ): Any? { if (parts == null) return null val base = inj?.get("base") val src = getprop(store, base, store) val dparent = inj?.get("dparent") - val dpath = when (val dp = inj?.get("dpath")) { - is List<*> -> dp.map { it.toString() }.toMutableList() - is String -> dp.split(".").toMutableList() - else -> null - } + val dpath = + when (val dp = inj?.get("dpath")) { + is List<*> -> dp.map { it.toString() }.toMutableList() + is String -> dp.split(".").toMutableList() + else -> null + } val numparts = parts.size var value: Any? = store if (pathOrig == null || store == null || (numparts == 1 && parts[0].isEmpty())) { @@ -1603,12 +1881,14 @@ object Struct { if (part.isEmpty()) { var ascends = 0 while (pI + 1 < numparts && parts[pI + 1].isEmpty()) { - ascends++; pI++ + ascends++ + pI++ } if (inj != null && ascends > 0) { if (pI == numparts - 1) ascends-- - if (ascends == 0) value = dparent - else if (dpath != null) { + if (ascends == 0) { + value = dparent + } else if (dpath != null) { val cutLen = (dpath.size - ascends).coerceAtLeast(0) val fullpath = mutableListOf() for (i in 0 until cutLen) fullpath.add(dpath[i]) @@ -1616,7 +1896,9 @@ object Struct { value = if (ascends <= size(dpath)) getpath(store, fullpath) else null break } - } else value = dparent + } else { + value = dparent + } } else { value = getprop(value, part) } @@ -1666,7 +1948,13 @@ object Struct { // =========================================================================== /** Build a type-mismatch error message. Mirrors Java _invalidTypeMsg (Struct.java:1439). */ - private fun _invalidTypeMsg(path: Any?, needtype: String, vt: Int, v: Any?, @Suppress("UNUSED_PARAMETER") whence: String): String { + private fun _invalidTypeMsg( + path: Any?, + needtype: String, + vt: Int, + v: Any?, + @Suppress("UNUSED_PARAMETER") whence: String, + ): String { val vs = if (v == null || v === UNDEF) "no value" else stringify(v) val sb = StringBuilder("Expected ") if (path is List<*> && path.size > 1) sb.append("field ").append(pathify(path, 1)).append(" to be ") @@ -1677,185 +1965,150 @@ object Struct { } /** $STRING: require a non-empty string. */ - val validate_STRING: Injector = Injector { inj, _, _, _ -> - val out = getprop(inj.dparent, inj.key) - val t = typify(out) - when { - (T_STRING and t) == 0 -> { - inj.errs.add(_invalidTypeMsg(inj.path, "string", t, out, "V1010")) - UNDEF - } - "" == out -> { - inj.errs.add("Empty string at " + pathify(inj.path, 1)) - UNDEF + val validate_STRING: Injector = + Injector { inj, _, _, _ -> + val out = getprop(inj.dparent, inj.key) + val t = typify(out) + when { + (T_STRING and t) == 0 -> { + inj.errs.add(_invalidTypeMsg(inj.path, "string", t, out, "V1010")) + UNDEF + } + "" == out -> { + inj.errs.add("Empty string at " + pathify(inj.path, 1)) + UNDEF + } + else -> out } - else -> out } - } /** Generic $TYPE handler. */ - val validate_TYPE: Injector = Injector { inj, _, ref, _ -> - val tname = if (ref == null || ref.length < 2) "" else ref.substring(1).lowercase(Locale.ROOT) - var idx = -1 - for (i in TYPE_NAMES.indices) { - if (tname == TYPE_NAMES[i]) { idx = i; break } - } - if (idx < 0) UNDEF - else { - val typev = 1 shl (31 - idx) - val out = getprop(inj.dparent, inj.key) - val t = typify(out) - if ((t and typev) == 0) { - inj.errs.add(_invalidTypeMsg(inj.path, tname, t, out, "V1001")) + val validate_TYPE: Injector = + Injector { inj, _, ref, _ -> + val tname = if (ref == null || ref.length < 2) "" else ref.substring(1).lowercase(Locale.ROOT) + var idx = -1 + for (i in TYPE_NAMES.indices) { + if (tname == TYPE_NAMES[i]) { + idx = i + break + } + } + if (idx < 0) { UNDEF - } else out + } else { + val typev = 1 shl (31 - idx) + val out = getprop(inj.dparent, inj.key) + val t = typify(out) + if ((t and typev) == 0) { + inj.errs.add(_invalidTypeMsg(inj.path, tname, t, out, "V1001")) + UNDEF + } else { + out + } + } } - } /** $ANY: accept any value. */ val validate_ANY: Injector = Injector { inj, _, _, _ -> getprop(inj.dparent, inj.key) } /** $CHILD: validate every direct child of the current node against a template. */ - val validate_CHILD: Injector = Injector { inj, _, _, _ -> - when (inj.mode) { - M_KEYPRE -> { - val childtm = getprop(inj.parent, inj.key) - val pkey = getelem(inj.path, -2) - var tval: Any? = getprop(inj.dparent, pkey) - if (tval === UNDEF || tval == null) tval = linkedMapOf() - else if (!ismap(tval)) { - val sp = slice(inj.path, -1, null) - inj.errs.add(_invalidTypeMsg(sp, "object", typify(tval), tval, "V0220")) - return@Injector UNDEF - } - val ckeys = keysof(tval) - for (ck in ckeys) { - setprop(inj.parent, ck, clone(childtm)) - inj.keys.add(ck) - } - inj.setval(UNDEF) - UNDEF - } - M_VAL -> { - if (!islist(inj.parent)) { - inj.errs.add("Invalid \$CHILD as value") - return@Injector UNDEF + val validate_CHILD: Injector = + Injector { inj, _, _, _ -> + when (inj.mode) { + M_KEYPRE -> { + val childtm = getprop(inj.parent, inj.key) + val pkey = getelem(inj.path, -2) + var tval: Any? = getprop(inj.dparent, pkey) + if (tval === UNDEF || tval == null) { + tval = linkedMapOf() + } else if (!ismap(tval)) { + val sp = slice(inj.path, -1, null) + inj.errs.add(_invalidTypeMsg(sp, "object", typify(tval), tval, "V0220")) + return@Injector UNDEF + } + val ckeys = keysof(tval) + for (ck in ckeys) { + setprop(inj.parent, ck, clone(childtm)) + inj.keys.add(ck) + } + inj.setval(UNDEF) + UNDEF } - val childtm = getprop(inj.parent, 1) - if (inj.dparent === UNDEF || inj.dparent == null) { + M_VAL -> { + if (!islist(inj.parent)) { + inj.errs.add("Invalid \$CHILD as value") + return@Injector UNDEF + } + val childtm = getprop(inj.parent, 1) + if (inj.dparent === UNDEF || inj.dparent == null) { + @Suppress("UNCHECKED_CAST") + (inj.parent as MutableList).clear() + return@Injector UNDEF + } + if (!islist(inj.dparent)) { + val sp = slice(inj.path, -1, null) + inj.errs.add(_invalidTypeMsg(sp, "list", typify(inj.dparent), inj.dparent, "V0230")) + inj.keyI = size(inj.parent) + return@Injector inj.dparent + } @Suppress("UNCHECKED_CAST") - (inj.parent as MutableList).clear() - return@Injector UNDEF - } - if (!islist(inj.dparent)) { - val sp = slice(inj.path, -1, null) - inj.errs.add(_invalidTypeMsg(sp, "list", typify(inj.dparent), inj.dparent, "V0230")) - inj.keyI = size(inj.parent) - return@Injector inj.dparent - } - @Suppress("UNCHECKED_CAST") - val dpl = inj.dparent as List - @Suppress("UNCHECKED_CAST") - val pl = inj.parent as MutableList - for (i in dpl.indices) setprop(pl, i, clone(childtm)) - while (pl.size > dpl.size) pl.removeAt(pl.size - 1) - inj.keyI = 0 - getprop(inj.dparent, 0) - } - else -> UNDEF - } - } - - /** $ONE: validate against any one of a list of alternative shapes. */ - val validate_ONE: Injector = Injector { inj, _, _, store -> - if (inj.mode != M_VAL) return@Injector UNDEF - if (!islist(inj.parent) || inj.keyI != 0) { - inj.errs.add("The \$ONE validator at field " + pathify(inj.path, 1, 1) + " must be the first element of an array.") - return@Injector UNDEF - } - inj.keyI = size(inj.keys) - inj.setval(inj.dparent, 2) - val sp = slice(inj.path, -1, null) - @Suppress("UNCHECKED_CAST") - inj.path = if (sp is List<*>) (sp as List).toMutableList() else mutableListOf() - inj.key = strkey(getelem(inj.path, -1)) - - val tvalsRaw = slice(inj.parent, 1, null) - @Suppress("UNCHECKED_CAST") - val tvals: List = if (tvalsRaw is List<*>) tvalsRaw as List else listOf() - if (tvals.isEmpty()) { - inj.errs.add("The \$ONE validator at field " + pathify(inj.path, 1, 1) + " must have at least one argument.") - return@Injector UNDEF - } - - for (tval in tvals) { - val terrs = mutableListOf() - val vstore = linkedMapOf() - if (store is Map<*, *>) for ((k, v) in store) vstore[k.toString()] = v - vstore[S_DTOP] = inj.dparent - val opts = linkedMapOf( - "extra" to vstore, - "errs" to terrs, - "meta" to inj.meta, - ) - val vcurrent: Any? = try { - validate(inj.dparent, tval, opts) - } catch (e: Exception) { - terrs.add(e.message) - inj.dparent - } - inj.setval(vcurrent, -2) - if (terrs.isEmpty()) return@Injector UNDEF - } + val dpl = inj.dparent as List - val valdesc = StringBuilder() - for (i in tvals.indices) { - if (i > 0) valdesc.append(", ") - val tv = tvals[i] - if (tv is String) { - val mm = R_TRANSFORM_NAME.matcher(tv) - if (mm.matches()) { - valdesc.append(mm.group(1).substring(1).lowercase(Locale.ROOT)) - continue + @Suppress("UNCHECKED_CAST") + val pl = inj.parent as MutableList + for (i in dpl.indices) setprop(pl, i, clone(childtm)) + while (pl.size > dpl.size) pl.removeAt(pl.size - 1) + inj.keyI = 0 + getprop(inj.dparent, 0) } + else -> UNDEF } - valdesc.append(stringify(tv)) } - inj.errs.add(_invalidTypeMsg(inj.path, (if (size(tvals) > 1) "one of " else "") + valdesc, typify(inj.dparent), inj.dparent, "V0210")) - UNDEF - } - /** $EXACT: validate against any one of a list of literal alternatives. */ - val validate_EXACT: Injector = Injector { inj, _, _, _ -> - if (inj.mode == M_VAL) { + /** $ONE: validate against any one of a list of alternative shapes. */ + val validate_ONE: Injector = + Injector { inj, _, _, store -> + if (inj.mode != M_VAL) return@Injector UNDEF if (!islist(inj.parent) || inj.keyI != 0) { - inj.errs.add("The \$EXACT validator at field " + pathify(inj.path, 1, 1) + " must be the first element of an array.") + inj.errs.add("The \$ONE validator at field " + pathify(inj.path, 1, 1) + " must be the first element of an array.") return@Injector UNDEF } inj.keyI = size(inj.keys) inj.setval(inj.dparent, 2) - val sp = slice(inj.path, 0, -1) + val sp = slice(inj.path, -1, null) @Suppress("UNCHECKED_CAST") inj.path = if (sp is List<*>) (sp as List).toMutableList() else mutableListOf() inj.key = strkey(getelem(inj.path, -1)) val tvalsRaw = slice(inj.parent, 1, null) + @Suppress("UNCHECKED_CAST") val tvals: List = if (tvalsRaw is List<*>) tvalsRaw as List else listOf() if (tvals.isEmpty()) { - inj.errs.add("The \$EXACT validator at field " + pathify(inj.path, 1, 1) + " must have at least one argument.") + inj.errs.add("The \$ONE validator at field " + pathify(inj.path, 1, 1) + " must have at least one argument.") return@Injector UNDEF } - var currentstr: String? = null for (tval in tvals) { - var exactmatch = tval == inj.dparent - if (!exactmatch && isnode(tval)) { - if (currentstr == null) currentstr = stringify(inj.dparent) - val tvalstr = stringify(tval) - exactmatch = tvalstr == currentstr - } - if (exactmatch) return@Injector UNDEF + val terrs = mutableListOf() + val vstore = linkedMapOf() + if (store is Map<*, *>) for ((k, v) in store) vstore[k.toString()] = v + vstore[S_DTOP] = inj.dparent + val opts = + linkedMapOf( + "extra" to vstore, + "errs" to terrs, + "meta" to inj.meta, + ) + val vcurrent: Any? = + try { + validate(inj.dparent, tval, opts) + } catch (e: Exception) { + terrs.add(e.message) + inj.dparent + } + inj.setval(vcurrent, -2) + if (terrs.isEmpty()) return@Injector UNDEF } val valdesc = StringBuilder() @@ -1871,85 +2124,150 @@ object Struct { } valdesc.append(stringify(tv)) } - inj.errs.add(_invalidTypeMsg(inj.path, - (if (size(inj.path) > 1) "" else "value ") + "exactly equal to " + (if (size(tvals) == 1) "" else "one of ") + valdesc, - typify(inj.dparent), inj.dparent, "V0110")) - UNDEF - } else { - delprop(inj.parent, inj.key) + inj.errs.add( + _invalidTypeMsg(inj.path, (if (size(tvals) > 1) "one of " else "") + valdesc, typify(inj.dparent), inj.dparent, "V0210"), + ) UNDEF } - } - /** Modify hook used by validate(): runs after each inject step. */ - val _validation: Modify = Modify { pval, key, parent, inj, _ -> - if (inj == null) return@Modify - if (pval === SKIP) return@Modify - val exact = (getprop(inj.meta, "`\$EXACT`", false) == true) - val cval = getprop(inj.dparent, key) - if (!exact && (cval === UNDEF || cval == null)) return@Modify + /** $EXACT: validate against any one of a list of literal alternatives. */ + val validate_EXACT: Injector = + Injector { inj, _, _, _ -> + if (inj.mode == M_VAL) { + if (!islist(inj.parent) || inj.keyI != 0) { + inj.errs.add("The \$EXACT validator at field " + pathify(inj.path, 1, 1) + " must be the first element of an array.") + return@Injector UNDEF + } + inj.keyI = size(inj.keys) + inj.setval(inj.dparent, 2) + val sp = slice(inj.path, 0, -1) + @Suppress("UNCHECKED_CAST") + inj.path = if (sp is List<*>) (sp as List).toMutableList() else mutableListOf() + inj.key = strkey(getelem(inj.path, -1)) + + val tvalsRaw = slice(inj.parent, 1, null) + + @Suppress("UNCHECKED_CAST") + val tvals: List = if (tvalsRaw is List<*>) tvalsRaw as List else listOf() + if (tvals.isEmpty()) { + inj.errs.add("The \$EXACT validator at field " + pathify(inj.path, 1, 1) + " must have at least one argument.") + return@Injector UNDEF + } - val ptype = typify(pval) - if ((T_STRING and ptype) != 0 && pval is String && pval.contains("$")) return@Modify + var currentstr: String? = null + for (tval in tvals) { + var exactmatch = tval == inj.dparent + if (!exactmatch && isnode(tval)) { + if (currentstr == null) currentstr = stringify(inj.dparent) + val tvalstr = stringify(tval) + exactmatch = tvalstr == currentstr + } + if (exactmatch) return@Injector UNDEF + } - val ctype = typify(cval) - if (ptype != ctype && pval !== UNDEF) { - inj.errs.add(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0010")) - return@Modify + val valdesc = StringBuilder() + for (i in tvals.indices) { + if (i > 0) valdesc.append(", ") + val tv = tvals[i] + if (tv is String) { + val mm = R_TRANSFORM_NAME.matcher(tv) + if (mm.matches()) { + valdesc.append(mm.group(1).substring(1).lowercase(Locale.ROOT)) + continue + } + } + valdesc.append(stringify(tv)) + } + inj.errs.add( + _invalidTypeMsg( + inj.path, + (if (size(inj.path) > 1) "" else "value ") + "exactly equal to " + (if (size(tvals) == 1) "" else "one of ") + valdesc, + typify(inj.dparent), + inj.dparent, + "V0110", + ), + ) + UNDEF + } else { + delprop(inj.parent, inj.key) + UNDEF + } } - if (ismap(cval)) { - if (!ismap(pval)) { - inj.errs.add(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0020")) + /** Modify hook used by validate(): runs after each inject step. */ + val _validation: Modify = + Modify { pval, key, parent, inj, _ -> + if (inj == null) return@Modify + if (pval === SKIP) return@Modify + val exact = (getprop(inj.meta, "`\$EXACT`", false) == true) + val cval = getprop(inj.dparent, key) + if (!exact && (cval === UNDEF || cval == null)) return@Modify + + val ptype = typify(pval) + if ((T_STRING and ptype) != 0 && pval is String && pval.contains("$")) return@Modify + + val ctype = typify(cval) + if (ptype != ctype && pval !== UNDEF) { + inj.errs.add(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0010")) return@Modify } - val ckeys = keysof(cval) - val pkeys = keysof(pval) - if (size(pkeys) > 0 && getprop(pval, "`\$OPEN`") != true) { - val badkeys = mutableListOf() - for (ck in ckeys) if (!haskey(pval, ck)) badkeys.add(ck) - if (badkeys.isNotEmpty()) { - inj.errs.add("Unexpected keys at field " + pathify(inj.path, 1) + ": " + badkeys.joinToString(", ")) + + if (ismap(cval)) { + if (!ismap(pval)) { + inj.errs.add(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0020")) + return@Modify + } + val ckeys = keysof(cval) + val pkeys = keysof(pval) + if (size(pkeys) > 0 && getprop(pval, "`\$OPEN`") != true) { + val badkeys = mutableListOf() + for (ck in ckeys) if (!haskey(pval, ck)) badkeys.add(ck) + if (badkeys.isNotEmpty()) { + inj.errs.add("Unexpected keys at field " + pathify(inj.path, 1) + ": " + badkeys.joinToString(", ")) + } + } else { + val mergeArgs = mutableListOf(pval, cval) + merge(mergeArgs) + if (isnode(pval)) delprop(pval, "`\$OPEN`") + } + } else if (islist(cval)) { + if (!islist(pval)) { + inj.errs.add(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0030")) + } + } else if (exact) { + if (cval != pval) { + val pathmsg = if (size(inj.path) > 1) "at field " + pathify(inj.path, 1) + ": " else "" + inj.errs.add("Value " + pathmsg + jsString(cval) + " should equal " + jsString(pval) + ".") } } else { - val mergeArgs = mutableListOf(pval, cval) - merge(mergeArgs) - if (isnode(pval)) delprop(pval, "`\$OPEN`") + setprop(parent, key, cval) } - } else if (islist(cval)) { - if (!islist(pval)) { - inj.errs.add(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0030")) - } - } else if (exact) { - if (cval != pval) { - val pathmsg = if (size(inj.path) > 1) "at field " + pathify(inj.path, 1) + ": " else "" - inj.errs.add("Value " + pathmsg + jsString(cval) + " should equal " + jsString(pval) + ".") - } - } else { - setprop(parent, key, cval) } - } /** Custom Injector used by validate(): handles meta-path syntax `$=value` / `$~spec`. */ - val _validatehandler: Injector = Injector { inj, value, ref, store -> - if (ref != null) { - val m = R_META_PATH.matcher(ref) - if (m.matches()) { - val op = m.group(2) - if ("=" == op) { - val wrap = mutableListOf("`\$EXACT`", value) - inj.setval(wrap) - } else { - inj.setval(value) + val _validatehandler: Injector = + Injector { inj, value, ref, store -> + if (ref != null) { + val m = R_META_PATH.matcher(ref) + if (m.matches()) { + val op = m.group(2) + if ("=" == op) { + val wrap = mutableListOf("`\$EXACT`", value) + inj.setval(wrap) + } else { + inj.setval(value) + } + inj.keyI = -1 + return@Injector SKIP } - inj.keyI = -1 - return@Injector SKIP } + _injecthandler.apply(inj, value, ref, store) } - _injecthandler.apply(inj, value, ref, store) - } - fun validate(data: Any?, spec: Any?): Any? = validate(data, spec, null) + fun validate( + data: Any?, + spec: Any?, + ): Any? = validate(data, spec, null) /** * Canonical TS-faithful validate. Build a store with validate_* injectors plus @@ -1957,12 +2275,17 @@ object Struct { * modify and _validatehandler as handler. Mirrors Java validate (Struct.java:2903) * and TS validate (StructUtility.ts:2347). */ - fun validate(data: Any?, spec: Any?, options: Map?): Any? { + fun validate( + data: Any?, + spec: Any?, + options: Map?, + ): Any? { val extraRaw = options?.get("extra") val errsRaw = options?.get("errs") val metaRaw = options?.get("meta") val collect = errsRaw is MutableList<*> + @Suppress("UNCHECKED_CAST") val errs: MutableList = if (collect) errsRaw as MutableList else mutableListOf() @@ -2001,13 +2324,14 @@ object Struct { if (metaRaw is Map<*, *>) for ((k, v) in metaRaw) meta[k.toString()] = v if (!meta.containsKey("`\$EXACT`")) meta["`\$EXACT`"] = false - val opts = linkedMapOf( - "meta" to meta, - "extra" to store, - "modify" to _validation, - "handler" to _validatehandler, - "errs" to errs, - ) + val opts = + linkedMapOf( + "meta" to meta, + "extra" to store, + "modify" to _validation, + "handler" to _validatehandler, + "errs" to errs, + ) val out = transform(data, spec, opts) if (errs.isNotEmpty() && !collect) { throw IllegalArgumentException(errs.joinToString(" | ") { it?.toString() ?: "" }) @@ -2015,15 +2339,17 @@ object Struct { return out } - /** Legacy bespoke validate kept private for fallback / debugging if needed. */ - @Suppress("unused") - // =========================================================================== // Select Injectors // =========================================================================== /** Build the recursive validate options used by select_*. */ - private fun selectRecOpts(store: Any?, point: Any?, meta: MutableMap, errs: MutableList): MutableMap { + private fun selectRecOpts( + store: Any?, + point: Any?, + meta: MutableMap, + errs: MutableList, + ): MutableMap { val vstore = linkedMapOf() if (store is Map<*, *>) for ((k, v) in store) vstore[k.toString()] = v vstore[S_DTOP] = point @@ -2031,94 +2357,117 @@ object Struct { } /** $AND: require every sub-term to validate against the current point. */ - val select_AND: Injector = Injector { inj, _, _, store -> - if (inj.mode != M_KEYPRE) return@Injector UNDEF - val terms = getprop(inj.parent, inj.key) - if (terms !is List<*>) return@Injector UNDEF - val ppath = slice(inj.path, -1, null) - val point = getpath(store, ppath) - for (term in terms) { - val terrs = mutableListOf() - val opts = selectRecOpts(store, point, inj.meta, terrs) - try { validate(point, term, opts) } catch (e: Exception) { terrs.add(e.message) } - if (terrs.isNotEmpty()) { - inj.errs.add("AND:${pathify(ppath)}: ${stringify(point)} fail:${stringify(terms)}") + val select_AND: Injector = + Injector { inj, _, _, store -> + if (inj.mode != M_KEYPRE) return@Injector UNDEF + val terms = getprop(inj.parent, inj.key) + if (terms !is List<*>) return@Injector UNDEF + val ppath = slice(inj.path, -1, null) + val point = getpath(store, ppath) + for (term in terms) { + val terrs = mutableListOf() + val opts = selectRecOpts(store, point, inj.meta, terrs) + try { + validate(point, term, opts) + } catch (e: Exception) { + terrs.add(e.message) + } + if (terrs.isNotEmpty()) { + inj.errs.add("AND:${pathify(ppath)}: ${stringify(point)} fail:${stringify(terms)}") + } } + val gkey = getelem(inj.path, -2) + val gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + UNDEF } - val gkey = getelem(inj.path, -2) - val gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - UNDEF - } /** $OR: require at least one sub-term to validate. */ - val select_OR: Injector = Injector { inj, _, _, store -> - if (inj.mode != M_KEYPRE) return@Injector UNDEF - val terms = getprop(inj.parent, inj.key) - if (terms !is List<*>) return@Injector UNDEF - val ppath = slice(inj.path, -1, null) - val point = getpath(store, ppath) - for (term in terms) { + val select_OR: Injector = + Injector { inj, _, _, store -> + if (inj.mode != M_KEYPRE) return@Injector UNDEF + val terms = getprop(inj.parent, inj.key) + if (terms !is List<*>) return@Injector UNDEF + val ppath = slice(inj.path, -1, null) + val point = getpath(store, ppath) + for (term in terms) { + val terrs = mutableListOf() + val opts = selectRecOpts(store, point, inj.meta, terrs) + try { + validate(point, term, opts) + } catch (e: Exception) { + terrs.add(e.message) + } + if (terrs.isEmpty()) { + val gkey = getelem(inj.path, -2) + val gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + return@Injector UNDEF + } + } + inj.errs.add("OR:${pathify(ppath)}: ${stringify(point)} fail:${stringify(terms)}") + UNDEF + } + + /** $NOT: require the sub-term to fail validation. */ + val select_NOT: Injector = + Injector { inj, _, _, store -> + if (inj.mode != M_KEYPRE) return@Injector UNDEF + val term = getprop(inj.parent, inj.key) + val ppath = slice(inj.path, -1, null) + val point = getpath(store, ppath) val terrs = mutableListOf() val opts = selectRecOpts(store, point, inj.meta, terrs) - try { validate(point, term, opts) } catch (e: Exception) { terrs.add(e.message) } + try { + validate(point, term, opts) + } catch (e: Exception) { + terrs.add(e.message) + } if (terrs.isEmpty()) { - val gkey = getelem(inj.path, -2) - val gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - return@Injector UNDEF + inj.errs.add("NOT:${pathify(ppath)}: ${stringify(point)} fail:${stringify(term)}") } + val gkey = getelem(inj.path, -2) + val gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + UNDEF } - inj.errs.add("OR:${pathify(ppath)}: ${stringify(point)} fail:${stringify(terms)}") - UNDEF - } - - /** $NOT: require the sub-term to fail validation. */ - val select_NOT: Injector = Injector { inj, _, _, store -> - if (inj.mode != M_KEYPRE) return@Injector UNDEF - val term = getprop(inj.parent, inj.key) - val ppath = slice(inj.path, -1, null) - val point = getpath(store, ppath) - val terrs = mutableListOf() - val opts = selectRecOpts(store, point, inj.meta, terrs) - try { validate(point, term, opts) } catch (e: Exception) { terrs.add(e.message) } - if (terrs.isEmpty()) { - inj.errs.add("NOT:${pathify(ppath)}: ${stringify(point)} fail:${stringify(term)}") - } - val gkey = getelem(inj.path, -2) - val gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - UNDEF - } /** $GT/$LT/$GTE/$LTE/$LIKE comparators dispatched by ref. */ - val select_CMP: Injector = Injector { inj, _, ref, store -> - if (inj.mode != M_KEYPRE) return@Injector UNDEF - val term = getprop(inj.parent, inj.key) - val gkey = getelem(inj.path, -2) - val ppath = slice(inj.path, -1, null) - val point = getpath(store, ppath) - var pass = false - if (point is Number && term is Number) { - val a = point.toDouble(); val b = term.toDouble() - pass = when (ref) { - "\$GT" -> a > b - "\$LT" -> a < b - "\$GTE" -> a >= b - "\$LTE" -> a <= b - else -> false - } - } else if ("\$LIKE" == ref && term is String) { - pass = try { Pattern.compile(term).matcher(stringify(point)).find() } catch (_: Exception) { false } - } - if (pass) { - val gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - } else { - inj.errs.add("CMP: ${pathify(ppath)}: ${stringify(point)} fail:$ref ${stringify(term)}") + val select_CMP: Injector = + Injector { inj, _, ref, store -> + if (inj.mode != M_KEYPRE) return@Injector UNDEF + val term = getprop(inj.parent, inj.key) + val gkey = getelem(inj.path, -2) + val ppath = slice(inj.path, -1, null) + val point = getpath(store, ppath) + var pass = false + if (point is Number && term is Number) { + val a = point.toDouble() + val b = term.toDouble() + pass = + when (ref) { + "\$GT" -> a > b + "\$LT" -> a < b + "\$GTE" -> a >= b + "\$LTE" -> a <= b + else -> false + } + } else if ("\$LIKE" == ref && term is String) { + pass = + try { + Pattern.compile(term).matcher(stringify(point)).find() + } catch (_: Exception) { + false + } + } + if (pass) { + val gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + } else { + inj.errs.add("CMP: ${pathify(ppath)}: ${stringify(point)} fail:$ref ${stringify(term)}") + } + UNDEF } - UNDEF - } /** * Canonical TS-faithful select. Tag each child node with $KEY, walk the @@ -2126,7 +2475,10 @@ object Struct { * against each child with the select_* injectors as extras and * meta.`$EXACT` = true. Mirrors Java select (Struct.java:2981). */ - fun select(children: Any?, query: Any?): MutableList { + fun select( + children: Any?, + query: Any?, + ): MutableList { if (!isnode(children)) return mutableListOf() val childList = mutableListOf() @@ -2146,28 +2498,36 @@ object Struct { val meta = linkedMapOf("`\$EXACT`" to true) - val extra = linkedMapOf( - "\$AND" to select_AND, - "\$OR" to select_OR, - "\$NOT" to select_NOT, - "\$GT" to select_CMP, - "\$LT" to select_CMP, - "\$GTE" to select_CMP, - "\$LTE" to select_CMP, - "\$LIKE" to select_CMP, - ) + val extra = + linkedMapOf( + "\$AND" to select_AND, + "\$OR" to select_OR, + "\$NOT" to select_NOT, + "\$GT" to select_CMP, + "\$LT" to select_CMP, + "\$GTE" to select_CMP, + "\$LTE" to select_CMP, + "\$LIKE" to select_CMP, + ) val q = clone(query) - walk(q, WalkApply { _, v, _, _ -> - if (ismap(v)) setprop(v, "`\$OPEN`", getprop(v, "`\$OPEN`", true)) - v - }) + walk( + q, + WalkApply { _, v, _, _ -> + if (ismap(v)) setprop(v, "`\$OPEN`", getprop(v, "`\$OPEN`", true)) + v + }, + ) val results = mutableListOf() for (child in childList) { val errs = mutableListOf() val opts = linkedMapOf("errs" to errs, "meta" to meta, "extra" to extra) - try { validate(child, clone(q), opts) } catch (e: Exception) { errs.add(e.message) } + try { + validate(child, clone(q), opts) + } catch (e: Exception) { + errs.add(e.message) + } if (errs.isEmpty()) results.add(child) } return results diff --git a/kt/src/test/kotlin/voxgig/struct/CorpusRunner.kt b/kt/src/test/kotlin/voxgig/struct/CorpusRunner.kt index 88b3cd4e..b180093a 100644 --- a/kt/src/test/kotlin/voxgig/struct/CorpusRunner.kt +++ b/kt/src/test/kotlin/voxgig/struct/CorpusRunner.kt @@ -23,12 +23,14 @@ object CorpusRunner { synchronized(this) { c = CORPUS if (c != null) return c!! - val candidates = listOf( - Paths.get("..", "build", "test", "test.json"), - Paths.get("build", "test", "test.json"), - ) - val path = candidates.firstOrNull { Files.exists(it) } - ?: throw IllegalStateException("corpus not found: tried ${candidates.joinToString()}") + val candidates = + listOf( + Paths.get("..", "build", "test", "test.json"), + Paths.get("build", "test", "test.json"), + ) + val path = + candidates.firstOrNull { Files.exists(it) } + ?: throw IllegalStateException("corpus not found: tried ${candidates.joinToString()}") val json = Files.readString(path) val type = object : TypeToken>() {}.type val parsed: Map = GSON.fromJson(json, type) @@ -38,11 +40,15 @@ object CorpusRunner { } @Suppress("UNCHECKED_CAST") - fun getSpec(category: String, name: String): Map { + fun getSpec( + category: String, + name: String, + ): Map { val all = loadCorpus() val struct = all["struct"] as Map - val cat = struct[category] as? Map - ?: throw IllegalArgumentException("Unknown category: $category") + val cat = + struct[category] as? Map + ?: throw IllegalArgumentException("Unknown category: $category") return (cat[name] as? Map) ?: throw IllegalArgumentException("Unknown spec: $category.$name") } @@ -55,11 +61,15 @@ object CorpusRunner { var passed: Int = 0 var total: Int = 0 val failures: MutableList = mutableListOf() + override fun toString(): String = "$name: $passed/$total" } - fun runset(fullName: String, testspec: Map, subject: Subject): Result = - runsetflags(fullName, testspec, true, subject) + fun runset( + fullName: String, + testspec: Map, + subject: Subject, + ): Result = runsetflags(fullName, testspec, true, subject) @Suppress("UNCHECKED_CAST") fun runsetflags( @@ -74,7 +84,14 @@ object CorpusRunner { if (eo !is Map<*, *>) continue val entry = eo as Map val input = if (entry.containsKey("in")) Struct.clone(entry["in"]) else Struct.UNDEF - val expected: Any? = if (entry.containsKey("out")) entry["out"] else if (nullFlag) null else Struct.UNDEF + val expected: Any? = + if (entry.containsKey("out")) { + entry["out"] + } else if (nullFlag) { + null + } else { + Struct.UNDEF + } res.total++ try { val got = subject.apply(input) @@ -82,16 +99,23 @@ object CorpusRunner { res.failures.add("[$i] expected err='${brief(entry["err"])}' but call returned ${brief(got)}") continue } - if (deepEqual(got, expected)) res.passed++ - else res.failures.add("[$i] in=${brief(entry["in"])} expected=${brief(expected)} got=${brief(got)}") + if (deepEqual(got, expected)) { + res.passed++ + } else { + res.failures.add("[$i] in=${brief(entry["in"])} expected=${brief(expected)} got=${brief(got)}") + } } catch (ex: Exception) { if (entry.containsKey("err")) { val expErr = entry["err"] val msg = ex.message ?: "" - val ok = expErr == true || - (expErr is String && (expErr.isEmpty() || msg.contains(expErr) || msg.lowercase().contains(expErr.lowercase()))) - if (ok) res.passed++ - else res.failures.add("[$i] err mismatch: expected '${brief(expErr)}' got '$msg'") + val ok = + expErr == true || + (expErr is String && (expErr.isEmpty() || msg.contains(expErr) || msg.lowercase().contains(expErr.lowercase()))) + if (ok) { + res.passed++ + } else { + res.failures.add("[$i] err mismatch: expected '${brief(expErr)}' got '$msg'") + } } else { res.failures.add("[$i] in=${brief(entry["in"])} threw=${ex.message}") } @@ -100,7 +124,10 @@ object CorpusRunner { return res } - fun deepEqual(a: Any?, b: Any?): Boolean = normalize(a) == normalize(b) + fun deepEqual( + a: Any?, + b: Any?, + ): Boolean = normalize(a) == normalize(b) fun normalize(v: Any?): Any? { if (v === Struct.UNDEF || v == null) return null diff --git a/kt/src/test/kotlin/voxgig/struct/StructCorpusTest.kt b/kt/src/test/kotlin/voxgig/struct/StructCorpusTest.kt index e2a3bea2..ead80b71 100644 --- a/kt/src/test/kotlin/voxgig/struct/StructCorpusTest.kt +++ b/kt/src/test/kotlin/voxgig/struct/StructCorpusTest.kt @@ -20,19 +20,19 @@ import java.util.function.Function */ @Suppress("UNCHECKED_CAST") class StructCorpusTest { - companion object { private val SCOREBOARD: MutableMap = TreeMap() - private val CATEGORY_TO_FILE: Map = linkedMapOf( - "minor" to "minor.jsonic", - "walk" to "walk.jsonic", - "merge" to "merge.jsonic", - "getpath" to "getpath.jsonic", - "inject" to "inject.jsonic", - "transform" to "transform.jsonic", - "validate" to "validate.jsonic", - "select" to "select.jsonic", - ) + private val CATEGORY_TO_FILE: Map = + linkedMapOf( + "minor" to "minor.jsonic", + "walk" to "walk.jsonic", + "merge" to "merge.jsonic", + "getpath" to "getpath.jsonic", + "inject" to "inject.jsonic", + "transform" to "transform.jsonic", + "validate" to "validate.jsonic", + "select" to "select.jsonic", + ) @JvmStatic @AfterAll @@ -88,9 +88,16 @@ class StructCorpusTest { } } - private fun getp(input: Any?, key: String): Any? = if (input is Map<*, *>) (input as Map)[key] else null - private fun getpDef(input: Any?, key: String, def: Any?): Any? = - if (input is Map<*, *> && (input as Map).containsKey(key)) input[key] else def + private fun getp( + input: Any?, + key: String, + ): Any? = if (input is Map<*, *>) (input as Map)[key] else null + + private fun getpDef( + input: Any?, + key: String, + def: Any?, + ): Any? = if (input is Map<*, *> && (input as Map).containsKey(key)) input[key] else def @TestFactory fun corpus(): Iterable { @@ -105,11 +112,15 @@ class StructCorpusTest { add(tests, "minor", "isempty", false) { Struct.isempty(it) } add(tests, "minor", "isfunc", true) { Struct.isfunc(it) } add(tests, "minor", "getprop", true) { - val v = getp(it, "val"); val k = getp(it, "key"); val a = getpDef(it, "alt", Struct.UNDEF) + val v = getp(it, "val") + val k = getp(it, "key") + val a = getpDef(it, "alt", Struct.UNDEF) if (a === Struct.UNDEF) Struct.getprop(v, k) else Struct.getprop(v, k, a) } add(tests, "minor", "getelem", true) { - val v = getp(it, "val"); val k = getp(it, "key"); val a = getpDef(it, "alt", Struct.UNDEF) + val v = getp(it, "val") + val k = getp(it, "key") + val a = getpDef(it, "alt", Struct.UNDEF) if (a === Struct.UNDEF) Struct.getelem(v, k) else Struct.getelem(v, k, a) } add(tests, "minor", "clone", false) { Struct.clone(it) } @@ -172,16 +183,22 @@ class StructCorpusTest { val maxdepth = (getp(it, "maxdepth") as? Number)?.toInt() val top = arrayOfNulls(1) val cur = arrayOfNulls(1) - val copy = Struct.WalkApply { key, value, _, _ -> - if (Struct.isnode(value)) { - val child: Any? = if (Struct.islist(value)) mutableListOf() else linkedMapOf() - if (key == null) { top[0] = child; cur[0] = child } - else { cur[0] = Struct.setprop(cur[0], key, child); cur[0] = child } - } else if (key != null) { - cur[0] = Struct.setprop(cur[0], key, value) + val copy = + Struct.WalkApply { key, value, _, _ -> + if (Struct.isnode(value)) { + val child: Any? = if (Struct.islist(value)) mutableListOf() else linkedMapOf() + if (key == null) { + top[0] = child + cur[0] = child + } else { + cur[0] = Struct.setprop(cur[0], key, child) + cur[0] = child + } + } else if (key != null) { + cur[0] = Struct.setprop(cur[0], key, value) + } + value } - value - } if (maxdepth == null) Struct.walk(src, copy) else Struct.walk(src, copy, null, maxdepth) top[0] } @@ -191,46 +208,52 @@ class StructCorpusTest { add(tests, "merge", "array", true) { Struct.merge(it) } add(tests, "merge", "integrity", true) { Struct.merge(it) } add(tests, "merge", "depth", true) { - val v = getp(it, "val"); val d = (getp(it, "depth") as? Number)?.toInt() ?: 32 + val v = getp(it, "val") + val d = (getp(it, "depth") as? Number)?.toInt() ?: 32 Struct.merge(v, d) } // ===== getpath ===== add(tests, "getpath", "basic", true) { Struct.getpath(getp(it, "store"), getp(it, "path")) } add(tests, "getpath", "relative", true) { - val inj: Struct.Injection? = (it as? Map)?.let { m -> - if (!m.containsKey("dparent") && !m.containsKey("dpath") && !m.containsKey("base")) null - else Struct.Injection(null, null).apply { - if (m.containsKey("dparent")) dparent = m["dparent"] - if (m.containsKey("dpath")) { - when (val dp = m["dpath"]) { - is List<*> -> dpath = dp.map { e -> e?.toString() ?: "" }.toMutableList() - is String -> if (dp.isNotEmpty()) dpath = dp.split(".").toMutableList() + val inj: Struct.Injection? = + (it as? Map)?.let { m -> + if (!m.containsKey("dparent") && !m.containsKey("dpath") && !m.containsKey("base")) { + null + } else { + Struct.Injection(null, null).apply { + if (m.containsKey("dparent")) dparent = m["dparent"] + if (m.containsKey("dpath")) { + when (val dp = m["dpath"]) { + is List<*> -> dpath = dp.map { e -> e?.toString() ?: "" }.toMutableList() + is String -> if (dp.isNotEmpty()) dpath = dp.split(".").toMutableList() + } + } + if (m.containsKey("base") && m["base"] is String) base = m["base"] as String } } - if (m.containsKey("base") && m["base"] is String) base = m["base"] as String } - } Struct.getpath(getp(it, "store"), getp(it, "path"), inj) } add(tests, "getpath", "special", true) { val injMap = getp(it, "inj") as? Map - val inj = injMap?.let { im -> - Struct.Injection(null, null).apply { - if (im.containsKey("key")) key = im["key"]?.toString() ?: "" - if (im.containsKey("dparent")) dparent = im["dparent"] - if (im.containsKey("dpath")) { - val dp = im["dpath"] - if (dp is List<*>) dpath = dp.map { e -> e?.toString() ?: "" }.toMutableList() - } - if (im.containsKey("meta")) { - val mm = im["meta"] - if (mm is Map<*, *>) { - meta = linkedMapOf().also { for ((k, v) in mm) it[k.toString()] = v } + val inj = + injMap?.let { im -> + Struct.Injection(null, null).apply { + if (im.containsKey("key")) key = im["key"]?.toString() ?: "" + if (im.containsKey("dparent")) dparent = im["dparent"] + if (im.containsKey("dpath")) { + val dp = im["dpath"] + if (dp is List<*>) dpath = dp.map { e -> e?.toString() ?: "" }.toMutableList() + } + if (im.containsKey("meta")) { + val mm = im["meta"] + if (mm is Map<*, *>) { + meta = linkedMapOf().also { for ((k, v) in mm) it[k.toString()] = v } + } } } } - } Struct.getpath(getp(it, "store"), getp(it, "path"), inj) } @@ -246,20 +269,26 @@ class StructCorpusTest { add(tests, "transform", "ref", true) { Struct.transform(getp(it, "data"), getp(it, "spec")) } add(tests, "transform", "format", false) { Struct.transform(getp(it, "data"), getp(it, "spec")) } add(tests, "transform", "modify", true) { - val opts = linkedMapOf( - "modify" to Struct.Modify { v, k, parent, _, _ -> - if (k != null && parent is MutableMap<*, *> && v is String) { - @Suppress("UNCHECKED_CAST") - (parent as MutableMap)[k.toString()] = "@$v" - } - } - ) + val opts = + linkedMapOf( + "modify" to + Struct.Modify { v, k, parent, _, _ -> + if (k != null && parent is MutableMap<*, *> && v is String) { + @Suppress("UNCHECKED_CAST") + (parent as MutableMap)[k.toString()] = "@$v" + } + }, + ) Struct.transform(getp(it, "data"), getp(it, "spec"), opts) } add(tests, "transform", "apply", true) { - val opts = linkedMapOf("extra" to linkedMapOf( - "apply" to Function { v -> if (v is String) v.uppercase() else v } - )) + val opts = + linkedMapOf( + "extra" to + linkedMapOf( + "apply" to Function { v -> if (v is String) v.uppercase() else v }, + ), + ) Struct.transform(getp(it, "data"), getp(it, "spec"), opts) } @@ -290,10 +319,17 @@ class StructCorpusTest { nullFlag: Boolean, subject: CorpusRunner.Subject, ) { - tests.add(DynamicTest.dynamicTest("$category-$name") { - val spec = try { CorpusRunner.getSpec(category, name) } catch (_: Exception) { return@dynamicTest } - val r = CorpusRunner.runsetflags("$category.$name", spec, nullFlag, subject) - SCOREBOARD["$category.$name"] = r - }) + tests.add( + DynamicTest.dynamicTest("$category-$name") { + val spec = + try { + CorpusRunner.getSpec(category, name) + } catch (_: Exception) { + return@dynamicTest + } + val r = CorpusRunner.runsetflags("$category.$name", spec, nullFlag, subject) + SCOREBOARD["$category.$name"] = r + }, + ) } } diff --git a/kt/src/test/kotlin/voxgig/struct/StructMinorTest.kt b/kt/src/test/kotlin/voxgig/struct/StructMinorTest.kt index 05e474df..275a50ea 100644 --- a/kt/src/test/kotlin/voxgig/struct/StructMinorTest.kt +++ b/kt/src/test/kotlin/voxgig/struct/StructMinorTest.kt @@ -2,14 +2,13 @@ package voxgig.struct import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import java.nio.file.Files +import java.nio.file.Path import kotlin.math.floor import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -import java.util.function.Function -import java.nio.file.Files -import java.nio.file.Path @Suppress("UNCHECKED_CAST") class StructMinorTest { @@ -56,20 +55,22 @@ class StructMinorTest { fun minorClone() = runSet("clone") { Struct.clone(it) } @Test - fun minorGetprop() = runSet("getprop") { - val m = it as? Map<*, *> ?: return@runSet Struct.getprop(Struct.UNDEF, Struct.UNDEF) - val v = if (m.containsKey("val")) m["val"] else Struct.UNDEF - val k = if (m.containsKey("key")) m["key"] else Struct.UNDEF - if (m.containsKey("alt")) Struct.getprop(v, k, m["alt"]) else Struct.getprop(v, k) - } + fun minorGetprop() = + runSet("getprop") { + val m = it as? Map<*, *> ?: return@runSet Struct.getprop(Struct.UNDEF, Struct.UNDEF) + val v = if (m.containsKey("val")) m["val"] else Struct.UNDEF + val k = if (m.containsKey("key")) m["key"] else Struct.UNDEF + if (m.containsKey("alt")) Struct.getprop(v, k, m["alt"]) else Struct.getprop(v, k) + } @Test - fun minorGetelem() = runSet("getelem") { - val m = it as? Map<*, *> ?: return@runSet Struct.getelem(Struct.UNDEF, Struct.UNDEF) - val v = if (m.containsKey("val")) m["val"] else Struct.UNDEF - val k = if (m.containsKey("key")) m["key"] else Struct.UNDEF - if (m.containsKey("alt")) Struct.getelem(v, k, m["alt"]) else Struct.getelem(v, k) - } + fun minorGetelem() = + runSet("getelem") { + val m = it as? Map<*, *> ?: return@runSet Struct.getelem(Struct.UNDEF, Struct.UNDEF) + val v = if (m.containsKey("val")) m["val"] else Struct.UNDEF + val k = if (m.containsKey("key")) m["key"] else Struct.UNDEF + if (m.containsKey("alt")) Struct.getelem(v, k, m["alt"]) else Struct.getelem(v, k) + } @Test fun minorItems() = runSet("items") { Struct.items(it) } @@ -78,29 +79,32 @@ class StructMinorTest { fun minorKeysof() = runSet("keysof") { Struct.keysof(it) } @Test - fun minorHaskey() = runSet("haskey") { - val m = it as? Map<*, *> ?: return@runSet Struct.haskey(Struct.UNDEF, Struct.UNDEF) - val src = if (m.containsKey("src")) m["src"] else Struct.UNDEF - val key = if (m.containsKey("key")) m["key"] else Struct.UNDEF - Struct.haskey(src, key) - } + fun minorHaskey() = + runSet("haskey") { + val m = it as? Map<*, *> ?: return@runSet Struct.haskey(Struct.UNDEF, Struct.UNDEF) + val src = if (m.containsKey("src")) m["src"] else Struct.UNDEF + val key = if (m.containsKey("key")) m["key"] else Struct.UNDEF + Struct.haskey(src, key) + } @Test - fun minorSetprop() = runSet("setprop") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - val parent = if (m.containsKey("parent")) Struct.clone(m["parent"]) else Struct.UNDEF - val key = if (m.containsKey("key")) m["key"] else Struct.UNDEF - val value = if (m.containsKey("val")) m["val"] else Struct.UNDEF - Struct.setprop(parent, key, value) - } + fun minorSetprop() = + runSet("setprop") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + val parent = if (m.containsKey("parent")) Struct.clone(m["parent"]) else Struct.UNDEF + val key = if (m.containsKey("key")) m["key"] else Struct.UNDEF + val value = if (m.containsKey("val")) m["val"] else Struct.UNDEF + Struct.setprop(parent, key, value) + } @Test - fun minorDelprop() = runSet("delprop") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - val parent = if (m.containsKey("parent")) Struct.clone(m["parent"]) else Struct.UNDEF - val key = if (m.containsKey("key")) m["key"] else Struct.UNDEF - Struct.delprop(parent, key) - } + fun minorDelprop() = + runSet("delprop") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + val parent = if (m.containsKey("parent")) Struct.clone(m["parent"]) else Struct.UNDEF + val key = if (m.containsKey("key")) m["key"] else Struct.UNDEF + Struct.delprop(parent, key) + } @Test fun minorSize() = runSet("size") { Struct.size(it) } @@ -112,33 +116,38 @@ class StructMinorTest { fun minorTypename() = runSet("typename") { Struct.typename(it) } @Test - fun minorSlice() = runSet("slice") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - Struct.slice(if (m.containsKey("val")) m["val"] else Struct.UNDEF, m["start"], m["end"]) - } + fun minorSlice() = + runSet("slice") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + Struct.slice(if (m.containsKey("val")) m["val"] else Struct.UNDEF, m["start"], m["end"]) + } @Test - fun minorPad() = runSet("pad") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - Struct.pad(m["val"], m["pad"], m["char"]) - } + fun minorPad() = + runSet("pad") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + Struct.pad(m["val"], m["pad"], m["char"]) + } @Test - fun minorFlatten() = runSet("flatten") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - Struct.flatten(m["val"], (m["depth"] as? Number)?.toInt()) - } + fun minorFlatten() = + runSet("flatten") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + Struct.flatten(m["val"], (m["depth"] as? Number)?.toInt()) + } @Test - fun minorFilter() = runSet("filter") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - val check = m["check"]?.toString() ?: "" - val checkMap = mapOf) -> Boolean>( - "gt3" to { n -> ((n[1] as Number).toDouble() > 3) }, - "lt3" to { n -> ((n[1] as Number).toDouble() < 3) } - ) - Struct.filter(m["val"]) { item -> checkMap[check]!!.invoke(item) } - } + fun minorFilter() = + runSet("filter") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + val check = m["check"]?.toString() ?: "" + val checkMap = + mapOf) -> Boolean>( + "gt3" to { n -> ((n[1] as Number).toDouble() > 3) }, + "lt3" to { n -> ((n[1] as Number).toDouble() < 3) }, + ) + Struct.filter(m["val"]) { item -> checkMap[check]!!.invoke(item) } + } @Test fun minorEscre() = runSet("escre") { Struct.escre(it) } @@ -147,64 +156,87 @@ class StructMinorTest { fun minorEscurl() = runSet("escurl") { Struct.escurl(it) } @Test - fun minorJoin() = runSet("join") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - Struct.join(m["val"], m["sep"], m["url"]) - } + fun minorJoin() = + runSet("join") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + Struct.join(m["val"], m["sep"], m["url"]) + } @Test - fun minorStringify() = runSet("stringify") { - val m = it as? Map<*, *> ?: return@runSet Struct.stringify(Struct.UNDEF) - if (!m.containsKey("val")) return@runSet Struct.stringify(Struct.UNDEF) - var value: Any? = m["val"] - if ("__NULL__" == value) value = "null" - if (m.containsKey("max")) Struct.stringify(value, (m["max"] as Number).toInt()) else Struct.stringify(value) - } + fun minorStringify() = + runSet("stringify") { + val m = it as? Map<*, *> ?: return@runSet Struct.stringify(Struct.UNDEF) + if (!m.containsKey("val")) return@runSet Struct.stringify(Struct.UNDEF) + var value: Any? = m["val"] + if ("__NULL__" == value) value = "null" + if (m.containsKey("max")) Struct.stringify(value, (m["max"] as Number).toInt()) else Struct.stringify(value) + } @Test - fun minorJsonify() = runSet("jsonify") { - val m = it as? Map<*, *> ?: return@runSet Struct.jsonify(Struct.UNDEF) - Struct.jsonify(if (m.containsKey("val")) m["val"] else Struct.UNDEF, m["flags"]) - } + fun minorJsonify() = + runSet("jsonify") { + val m = it as? Map<*, *> ?: return@runSet Struct.jsonify(Struct.UNDEF) + Struct.jsonify(if (m.containsKey("val")) m["val"] else Struct.UNDEF, m["flags"]) + } @Test - fun minorPathify() = runSet("pathify") { - val m = it as? Map<*, *> ?: return@runSet Struct.pathify(Struct.UNDEF) - Struct.pathify(if (m.containsKey("path")) m["path"] else Struct.UNDEF, m["from"]) - } + fun minorPathify() = + runSet("pathify") { + val m = it as? Map<*, *> ?: return@runSet Struct.pathify(Struct.UNDEF) + Struct.pathify(if (m.containsKey("path")) m["path"] else Struct.UNDEF, m["from"]) + } @Test - fun minorSetpath() = runSet("setpath") { - val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF - val store = if (m.containsKey("store")) Struct.clone(m["store"]) else Struct.UNDEF - val path = m["path"] - val value = if (m.containsKey("val")) m["val"] else Struct.UNDEF - Struct.setpath(store, path, value) - } + fun minorSetpath() = + runSet("setpath") { + val m = it as? Map<*, *> ?: return@runSet Struct.UNDEF + val store = if (m.containsKey("store")) Struct.clone(m["store"]) else Struct.UNDEF + val path = m["path"] + val value = if (m.containsKey("val")) m["val"] else Struct.UNDEF + Struct.setpath(store, path, value) + } - private fun runSet(name: String, fn: (Any?) -> Any?) { + private fun runSet( + name: String, + fn: (Any?) -> Any?, + ) { runSet(name, fn, false) } - private fun runSet(name: String, fn: (Any?) -> Any?, nullFlag: Boolean) { + private fun runSet( + name: String, + fn: (Any?) -> Any?, + nullFlag: Boolean, + ) { val testspec = minorSpec[name] as Map val set = testspec["set"] as List for (entryObj in set) { if (entryObj !is Map<*, *>) continue val inVal = if (entryObj.containsKey("in")) Struct.clone(entryObj["in"]) else Struct.UNDEF - val outVal = if (entryObj.containsKey("out")) entryObj["out"] else if (nullFlag) "__NULL__" else Struct.UNDEF + val outVal = + if (entryObj.containsKey("out")) { + entryObj["out"] + } else if (nullFlag) { + "__NULL__" + } else { + Struct.UNDEF + } val got = fn(inVal) assertTrue(equalNorm(outVal, got), "Mismatch in $name expected=${json(outVal)} got=${json(got)}") } } - private fun json(v: Any?): String = try { - gson.toJson(if (v === Struct.UNDEF) "__UNDEF__" else v) - } catch (_: Exception) { - v.toString() - } + private fun json(v: Any?): String = + try { + gson.toJson(if (v === Struct.UNDEF) "__UNDEF__" else v) + } catch (_: Exception) { + v.toString() + } - private fun equalNorm(a: Any?, b: Any?): Boolean { + private fun equalNorm( + a: Any?, + b: Any?, + ): Boolean { return normalize(a) == normalize(b) } diff --git a/kt/src/test/kotlin/voxgig/struct/StructTests.kt b/kt/src/test/kotlin/voxgig/struct/StructTests.kt index a705e50d..6b116467 100644 --- a/kt/src/test/kotlin/voxgig/struct/StructTests.kt +++ b/kt/src/test/kotlin/voxgig/struct/StructTests.kt @@ -2,17 +2,17 @@ package voxgig.struct import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import java.nio.file.Files +import java.nio.file.Path +import java.util.function.Function +import java.util.function.Supplier +import java.util.regex.Pattern import kotlin.math.floor import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue -import java.nio.file.Files -import java.nio.file.Path -import java.util.function.Function -import java.util.function.Supplier -import java.util.regex.Pattern @Suppress("UNCHECKED_CAST") class StructTests { @@ -64,9 +64,10 @@ class StructTests { @Test fun walkBasic() { - val walkPath = Struct.WalkApply { _, v, _, p -> - if (v is String) v + "~" + p.joinToString(".") else v - } + val walkPath = + Struct.WalkApply { _, v, _, p -> + if (v is String) v + "~" + p.joinToString(".") else v + } runSet(walkSpec["basic"] as Map) { input -> Struct.walk(input, walkPath) } } @@ -76,32 +77,35 @@ class StructTests { val outMap = test["out"] as Map<*, *> val logAfter = mutableListOf() - val walklogAfter = Struct.WalkApply { key, value, parent, path -> - val ks = key ?: "" - val entry = "k=${Struct.stringify(ks)}, v=${Struct.stringify(value)}, p=${slog(parent)}, t=${Struct.pathify(path)}" - logAfter.add(entry) - value - } + val walklogAfter = + Struct.WalkApply { key, value, parent, path -> + val ks = key ?: "" + val entry = "k=${Struct.stringify(ks)}, v=${Struct.stringify(value)}, p=${slog(parent)}, t=${Struct.pathify(path)}" + logAfter.add(entry) + value + } Struct.walk(test["in"], null, walklogAfter) assertEquals(outMap["after"], logAfter) val logBefore = mutableListOf() - val walklogBefore = Struct.WalkApply { key, value, parent, path -> - val ks = key ?: "" - val entry = "k=${Struct.stringify(ks)}, v=${Struct.stringify(value)}, p=${slog(parent)}, t=${Struct.pathify(path)}" - logBefore.add(entry) - value - } + val walklogBefore = + Struct.WalkApply { key, value, parent, path -> + val ks = key ?: "" + val entry = "k=${Struct.stringify(ks)}, v=${Struct.stringify(value)}, p=${slog(parent)}, t=${Struct.pathify(path)}" + logBefore.add(entry) + value + } Struct.walk(test["in"], walklogBefore) assertEquals(outMap["before"], logBefore) val logBoth = mutableListOf() - val walklogBoth = Struct.WalkApply { key, value, parent, path -> - val ks = key ?: "" - val entry = "k=${Struct.stringify(ks)}, v=${Struct.stringify(value)}, p=${slog(parent)}, t=${Struct.pathify(path)}" - logBoth.add(entry) - value - } + val walklogBoth = + Struct.WalkApply { key, value, parent, path -> + val ks = key ?: "" + val entry = "k=${Struct.stringify(ks)}, v=${Struct.stringify(value)}, p=${slog(parent)}, t=${Struct.pathify(path)}" + logBoth.add(entry) + value + } Struct.walk(test["in"], walklogBoth, walklogBoth) assertEquals(outMap["both"], logBoth) } @@ -114,21 +118,22 @@ class StructTests { val maxdepth = m["maxdepth"] val top = arrayOfNulls(1) val cur = arrayOfNulls(1) - val copy = Struct.WalkApply { key, value, _, _ -> - if (Struct.isnode(value)) { - val child: Any? = if (Struct.islist(value)) mutableListOf() else linkedMapOf() - if (key == null) { - top[0] = child - cur[0] = child - } else { - cur[0] = Struct.setprop(cur[0], key, child) - cur[0] = child + val copy = + Struct.WalkApply { key, value, _, _ -> + if (Struct.isnode(value)) { + val child: Any? = if (Struct.islist(value)) mutableListOf() else linkedMapOf() + if (key == null) { + top[0] = child + cur[0] = child + } else { + cur[0] = Struct.setprop(cur[0], key, child) + cur[0] = child + } + } else if (key != null) { + cur[0] = Struct.setprop(cur[0], key, value) } - } else if (key != null) { - cur[0] = Struct.setprop(cur[0], key, value) + value } - value - } if (maxdepth == null) Struct.walk(src, copy) else Struct.walk(src, copy, null, intish(maxdepth)) top[0] } @@ -139,30 +144,32 @@ class StructTests { runSet(walkSpec["copy"] as Map) { v -> val cur = arrayOfNulls(33) val keys = arrayOfNulls(33) - val walkcopy = Struct.WalkApply { key, value, _, path -> - if (key == null) { - java.util.Arrays.fill(cur, null) - java.util.Arrays.fill(keys, null) - cur[0] = when { - Struct.ismap(value) -> linkedMapOf() - Struct.islist(value) -> mutableListOf() - else -> value + val walkcopy = + Struct.WalkApply { key, value, _, path -> + if (key == null) { + java.util.Arrays.fill(cur, null) + java.util.Arrays.fill(keys, null) + cur[0] = + when { + Struct.ismap(value) -> linkedMapOf() + Struct.islist(value) -> mutableListOf() + else -> value + } + return@WalkApply value } - return@WalkApply value - } - var node: Any? = value - val i = path.size - keys[i] = key - if (Struct.isnode(node)) { - cur[i] = if (Struct.ismap(node)) linkedMapOf() else mutableListOf() - node = cur[i] - } - cur[i - 1] = Struct.setprop(cur[i - 1], key, node) - for (j in i - 1 downTo 1) { - cur[j - 1] = Struct.setprop(cur[j - 1], keys[j], cur[j]) + var node: Any? = value + val i = path.size + keys[i] = key + if (Struct.isnode(node)) { + cur[i] = if (Struct.ismap(node)) linkedMapOf() else mutableListOf() + node = cur[i] + } + cur[i - 1] = Struct.setprop(cur[i - 1], key, node) + for (j in i - 1 downTo 1) { + cur[j - 1] = Struct.setprop(cur[j - 1], keys[j], cur[j]) + } + value } - value - } Struct.walk(v, walkcopy) cur[0] } @@ -229,11 +236,12 @@ class StructTests { fun getpathRelative() { runSet(getpathSpec["relative"] as Map) { v -> val m = v as Map<*, *> - val inj = Struct.Injection(null, null).apply { - dparent = m["dparent"] - val dp = m["dpath"] - if (dp is String && dp.isNotEmpty()) dpath = dp.split(".").toMutableList() - } + val inj = + Struct.Injection(null, null).apply { + dparent = m["dparent"] + val dp = m["dpath"] + if (dp is String && dp.isNotEmpty()) dpath = dp.split(".").toMutableList() + } Struct.getpath(m["store"], m["path"], inj) } } @@ -244,22 +252,25 @@ class StructTests { val m = v as Map<*, *> val injObj = m["inj"] if (injObj is Map<*, *>) { - val inj = Struct.Injection(null, null).apply { - if (injObj.containsKey("key")) key = injObj["key"]?.toString() ?: "" - if (injObj.containsKey("dparent")) dparent = injObj["dparent"] - if (injObj.containsKey("dpath")) { - val dp = injObj["dpath"] - if (dp is List<*>) dpath = dp.map { it?.toString() ?: "" }.toMutableList() - } - if (injObj.containsKey("meta")) { - val mm = injObj["meta"] - if (mm is Map<*, *>) { - meta = linkedMapOf().also { for ((k, vv) in mm) it[k.toString()] = vv } + val inj = + Struct.Injection(null, null).apply { + if (injObj.containsKey("key")) key = injObj["key"]?.toString() ?: "" + if (injObj.containsKey("dparent")) dparent = injObj["dparent"] + if (injObj.containsKey("dpath")) { + val dp = injObj["dpath"] + if (dp is List<*>) dpath = dp.map { it?.toString() ?: "" }.toMutableList() + } + if (injObj.containsKey("meta")) { + val mm = injObj["meta"] + if (mm is Map<*, *>) { + meta = linkedMapOf().also { for ((k, vv) in mm) it[k.toString()] = vv } + } } } - } Struct.getpath(m["store"], m["path"], inj) - } else Struct.getpath(m["store"], m["path"]) + } else { + Struct.getpath(m["store"], m["path"]) + } } } @@ -271,9 +282,10 @@ class StructTests { store[Struct.S_DTOP] = m["store"] store["\$FOO"] = Supplier { "foo" } val inj = Struct.Injection(null, null) - inj.handler = Struct.Injector { _, value, _, _ -> - if (value is Supplier<*>) value.get() else value - } + inj.handler = + Struct.Injector { _, value, _, _ -> + if (value is Supplier<*>) value.get() else value + } Struct.getpath(store, m["path"], inj) } } @@ -442,13 +454,14 @@ class StructTests { val extra = linkedMapOf() // Custom $INTEGER validator using the canonical Injector signature. // Returns the data value either way so the output keeps the original key. - extra["\$INTEGER"] = Struct.Injector { inj, _, _, _ -> - val v = Struct.getprop(inj.dparent, inj.key) - if (v !is Number || floor(v.toDouble()) != v.toDouble()) { - inj.errs.add("Not an integer at ${Struct.pathify(inj.path, 1)}: $v") + extra["\$INTEGER"] = + Struct.Injector { inj, _, _, _ -> + val v = Struct.getprop(inj.dparent, inj.key) + if (v !is Number || floor(v.toDouble()) != v.toDouble()) { + inj.errs.add("Not an integer at ${Struct.pathify(inj.path, 1)}: $v") + } + v } - v - } val shape = mapOf("a" to "`\$INTEGER`") val opts = linkedMapOf("extra" to extra, "errs" to errs) @@ -497,11 +510,12 @@ class StructTests { @Test fun transformEdgeApply() { - val spec = mutableListOf( - "`\$APPLY`", - Function { v -> 1 + (v as Number).toInt() }, - 1 - ) + val spec = + mutableListOf( + "`\$APPLY`", + Function { v -> 1 + (v as Number).toInt() }, + 1, + ) assertEquals(2L, normalize(Struct.transform(linkedMapOf(), spec))) } @@ -509,14 +523,16 @@ class StructTests { fun transformModify() { val data = linkedMapOf("x" to "X") val spec = linkedMapOf("z" to "`x`") - val opts = linkedMapOf( - "modify" to Struct.Modify { value, key, parent, _, _ -> - if (key != null && parent is MutableMap<*, *> && value is String) { - @Suppress("UNCHECKED_CAST") - (parent as MutableMap)[key.toString()] = "@$value" - } - } - ) + val opts = + linkedMapOf( + "modify" to + Struct.Modify { value, key, parent, _, _ -> + if (key != null && parent is MutableMap<*, *> && value is String) { + @Suppress("UNCHECKED_CAST") + (parent as MutableMap)[key.toString()] = "@$value" + } + }, + ) val got = Struct.transform(data, spec, opts) assertTrue(normalize(mapOf("z" to "@X")) == normalize(got)) } @@ -524,17 +540,20 @@ class StructTests { @Test fun transformExtra() { val data = linkedMapOf("a" to 1) - val spec = linkedMapOf( - "x" to "`a`", - "b" to "`\$COPY`", - "c" to "`\$UPPER`" - ) - val extra = linkedMapOf( - "b" to 2, - "\$UPPER" to Struct.Injector { inj, _, _, _ -> - if (inj.path.isNotEmpty()) inj.path.last().uppercase() else "" - } - ) + val spec = + linkedMapOf( + "x" to "`a`", + "b" to "`\$COPY`", + "c" to "`\$UPPER`", + ) + val extra = + linkedMapOf( + "b" to 2, + "\$UPPER" to + Struct.Injector { inj, _, _, _ -> + if (inj.path.isNotEmpty()) inj.path.last().uppercase() else "" + }, + ) val opts = linkedMapOf("extra" to extra) val got = Struct.transform(data, spec, opts) assertTrue(normalize(mapOf("x" to 1, "b" to 2, "c" to "C")) == normalize(got)) @@ -550,14 +569,17 @@ class StructTests { assertEquals(99, ((got["x"] as Supplier<*>).get() as Number).toInt()) } - private fun runSet(testspec: Map, fn: (Any?) -> Any?) { + private fun runSet( + testspec: Map, + fn: (Any?) -> Any?, + ) { runSet(testspec, fn, null) } private fun runSet( testspec: Map, fn: (Any?) -> Any?, - filter: ((Map<*, *>) -> Boolean)? + filter: ((Map<*, *>) -> Boolean)?, ) { val set = testspec["set"] as List<*> for (eo in set) { @@ -574,7 +596,10 @@ class StructTests { } } - private fun runValidateSet(testspec: Map, useInj: Boolean) { + private fun runValidateSet( + testspec: Map, + useInj: Boolean, + ) { val set = testspec["set"] as List<*> for (eo in set) { if (eo !is Map<*, *>) continue @@ -593,7 +618,7 @@ class StructTests { val got = Struct.validate(data, spec, options) assertTrue( normalize(entry["out"]) == normalize(got), - "Mismatch in=${json(input)} expected=${json(entry["out"])} got=${json(got)}" + "Mismatch in=${json(input)} expected=${json(entry["out"])} got=${json(got)}", ) } } @@ -649,25 +674,30 @@ class StructTests { return true } - private fun collectCommands(node: Any?, out: MutableSet) { + private fun collectCommands( + node: Any?, + out: MutableSet, + ) { when (node) { is String -> { val m = cmdRef.matcher(node) while (m.find()) out.add(m.group(1)) } - is Map<*, *> -> node.forEach { (k, v) -> - collectCommands(k?.toString(), out) - collectCommands(v, out) - } + is Map<*, *> -> + node.forEach { (k, v) -> + collectCommands(k?.toString(), out) + collectCommands(v, out) + } is List<*> -> node.forEach { collectCommands(it, out) } } } - private fun json(v: Any?): String = try { - gson.toJson(if (v === Struct.UNDEF) "__UNDEF__" else v) - } catch (_: Exception) { - v.toString() - } + private fun json(v: Any?): String = + try { + gson.toJson(if (v === Struct.UNDEF) "__UNDEF__" else v) + } catch (_: Exception) { + v.toString() + } private fun normalize(v: Any?): Any? { if (v === Struct.UNDEF) return "__UNDEF__" diff --git a/lua/.luacheckrc b/lua/.luacheckrc new file mode 100644 index 00000000..31e7942a --- /dev/null +++ b/lua/.luacheckrc @@ -0,0 +1,18 @@ +-- Luacheck configuration for the Lua port. +-- https://luacheck.readthedocs.io/ +std = "lua54+luajit" +max_line_length = 120 + +-- The busted test framework injects these globals into spec files. +files["test/"] = { + std = "+busted", +} + +exclude_files = { + ".luarocks/", + "lua_modules/", +} + +-- Mirroring the canonical TypeScript source produces some unused arguments +-- (kept for signature parity); don't flag those. +unused_args = false diff --git a/lua/.stylua.toml b/lua/.stylua.toml new file mode 100644 index 00000000..f44ec0ed --- /dev/null +++ b/lua/.stylua.toml @@ -0,0 +1,7 @@ +# StyLua configuration for the Lua port. +# https://github.com/JohnnyMorganz/StyLua +column_width = 100 +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" diff --git a/lua/makefile b/lua/makefile index 0d098a72..b8f7e151 100644 --- a/lua/makefile +++ b/lua/makefile @@ -1,6 +1,6 @@ # Makefile - Common commands for Lua project -.PHONY: setup test bench clean +.PHONY: setup test lint format-check bench clean # Setup the environment setup: @@ -11,6 +11,16 @@ setup: test: PATH="$(HOME)/.luarocks/bin:$$PATH" find ./test/ -name "*test.lua" | xargs $$(command -v busted || echo $(HOME)/.luarocks/bin/busted) +# Code quality: luacheck (lint) + StyLua (format check). +# Install with: luarocks install luacheck +# cargo install stylua --features lua54 (5.3+ bitwise ops) +lint: + luacheck src test + stylua --check src test + +format-check: + stylua --check src test + # Run walk() benchmark. Gated on WALK_BENCH=1 inside the script. bench: WALK_BENCH=1 lua test/walk_bench.lua diff --git a/lua/setup.sh b/lua/setup.sh index 19921ef3..e847e19f 100755 --- a/lua/setup.sh +++ b/lua/setup.sh @@ -24,18 +24,18 @@ install_lua() { fi # Download and install latest Lua from source - cd /tmp + cd /tmp || exit 1 curl -L -R -O "https://www.lua.org/ftp/lua-5.4.7.tar.gz" tar zxf "lua-5.4.7.tar.gz" - cd "lua-5.4.7" + cd "lua-5.4.7" || exit 1 make macosx test sudo make install # Download and install latest LuaRocks from source - cd /tmp + cd /tmp || exit 1 curl -L -R -O "https://luarocks.org/releases/luarocks-3.11.1.tar.gz" tar zxpf "luarocks-3.11.1.tar.gz" - cd "luarocks-3.11.1" + cd "luarocks-3.11.1" || exit 1 ./configure && make && sudo make install fi elif [[ "$OSTYPE" == "linux-gnu"* ]]; then @@ -57,18 +57,18 @@ install_lua() { fi # Download and install latest Lua from source - cd /tmp + cd /tmp || exit 1 curl -L -R -O "https://www.lua.org/ftp/lua-5.4.7.tar.gz" # Current latest stable tar zxf "lua-5.4.7.tar.gz" - cd "lua-5.4.7" + cd "lua-5.4.7" || exit 1 make all test sudo make install # Download and install latest LuaRocks from source - cd /tmp + cd /tmp || exit 1 curl -L -R -O "https://luarocks.org/releases/luarocks-3.11.1.tar.gz" tar zxpf "luarocks-3.11.1.tar.gz" - cd "luarocks-3.11.1" + cd "luarocks-3.11.1" || exit 1 ./configure --with-lua-include=/usr/local/include && make && sudo make install else echo "Unsupported OS: $OSTYPE" @@ -100,10 +100,10 @@ else echo "LuaRocks not found, installing only LuaRocks..." # Download and install only LuaRocks - cd /tmp + cd /tmp || exit 1 curl -L -R -O "https://luarocks.org/releases/luarocks-3.11.1.tar.gz" tar zxpf "luarocks-3.11.1.tar.gz" - cd "luarocks-3.11.1" + cd "luarocks-3.11.1" || exit 1 ./configure && make && sudo make install else echo "LuaRocks found: $(luarocks --version)" diff --git a/lua/src/struct.lua b/lua/src/struct.lua index 827aa776..cdd1d529 100644 --- a/lua/src/struct.lua +++ b/lua/src/struct.lua @@ -51,7 +51,8 @@ to represent JSON null, if this ambiguity creates issues (thankfully in most APIs, JSON nulls are not used). For example, the unit tests use the string "__NULL__" where necessary. -]] ---------------------------------------------------------- +]] +---------------------------------------------------------- -- String constants are explicitly defined. ---------------------------------------------------------- @@ -61,61 +62,60 @@ local M_KEYPOST = 2 local M_VAL = 4 local MODENAME = { - [M_VAL] = 'val', - [M_KEYPRE] = 'key:pre', - [M_KEYPOST] = 'key:post', + [M_VAL] = "val", + [M_KEYPRE] = "key:pre", + [M_KEYPOST] = "key:post", } -- Special strings. -local S_BKEY = '`$KEY`' -local S_BANNO = '`$ANNO`' -local S_BEXACT = '`$EXACT`' -local S_BVAL = '`$VAL`' +local S_BKEY = "`$KEY`" +local S_BANNO = "`$ANNO`" +local S_BEXACT = "`$EXACT`" +local S_BVAL = "`$VAL`" -local S_DKEY = '$KEY' -local S_DTOP = '$TOP' -local S_DERRS = '$ERRS' -local S_DSPEC = '$SPEC' +local S_DKEY = "$KEY" +local S_DTOP = "$TOP" +local S_DERRS = "$ERRS" +local S_DSPEC = "$SPEC" -- General strings. -local S_list = 'list' -local S_base = 'base' -local S_boolean = 'boolean' -local S_function = 'function' -local S_symbol = 'symbol' -local S_instance = 'instance' -local S_key = 'key' -local S_any = 'any' -local S_nil = 'nil' -local S_null = 'null' -local S_number = 'number' -local S_object = 'object' -local S_string = 'string' -local S_decimal = 'decimal' -local S_integer = 'integer' -local S_map = 'map' -local S_scalar = 'scalar' -local S_node = 'node' +local S_list = "list" +local S_base = "base" +local S_boolean = "boolean" +local S_function = "function" +local S_symbol = "symbol" +local S_instance = "instance" +local S_key = "key" +local S_any = "any" +local S_nil = "nil" +local S_null = "null" +local S_number = "number" +local S_object = "object" +local S_string = "string" +local S_decimal = "decimal" +local S_integer = "integer" +local S_map = "map" +local S_scalar = "scalar" +local S_node = "node" -- Character strings. -local S_BT = '`' -local S_CN = ':' -local S_CS = ']' -local S_DS = '$' -local S_DT = '.' -local S_FS = '/' -local S_KEY = 'KEY' -local S_MT = '' -local S_OS = '[' -local S_SP = ' ' -local S_CM = ',' -local S_VIZ = ': ' - +local S_BT = "`" +local S_CN = ":" +local S_CS = "]" +local S_DS = "$" +local S_DT = "." +local S_FS = "/" +local S_KEY = "KEY" +local S_MT = "" +local S_OS = "[" +local S_SP = " " +local S_CM = "," +local S_VIZ = ": " -- Types (bit flags) -- Using explicit bit positions to match TS implementation -local T_any = (1 << 31) - 1 -- All bits set -local T_noval = 1 << 30 -- Property absent, undefined +local T_any = (1 << 31) - 1 -- All bits set +local T_noval = 1 << 30 -- Property absent, undefined local T_boolean = 1 << 29 local T_decimal = 1 << 28 local T_integer = 1 << 27 @@ -123,7 +123,7 @@ local T_number = 1 << 26 local T_string = 1 << 25 local T_function = 1 << 24 local T_symbol = 1 << 23 -local T_null = 1 << 22 -- Actual JSON null value +local T_null = 1 << 22 -- Actual JSON null value -- gap of 7 local T_list = 1 << 14 local T_map = 1 << 13 @@ -143,23 +143,30 @@ local TYPENAME = { S_function, S_symbol, S_null, - '', '', '', - '', '', '', '', + "", + "", + "", + "", + "", + "", + "", S_list, S_map, S_instance, - '', '', '', '', + "", + "", + "", + "", S_scalar, S_node, } - -- The standard undefined value for this language. local NONE = nil -- Private markers -local SKIP = { ['`$SKIP`'] = true } -local DELETE = { ['`$DELETE`'] = true } +local SKIP = { ["`$SKIP`"] = true } +local DELETE = { ["`$DELETE`"] = true } local MAXDEPTH = 32 @@ -171,6 +178,7 @@ local _injecthandler local _validatehandler local _invalidTypeMsg local _validation +local stringify local ismap local islist local getpath @@ -179,25 +187,40 @@ local delprop local checkPlacement local injectorArgs - -- Return type string for narrowest type. local function typename(t) -- Math.clz32 equivalent: count leading zeros in a 32-bit integer local function clz32(x) - if x == 0 then return 32 end + if x == 0 then + return 32 + end local n = 0 - if (x & 0xFFFF0000) == 0 then n = n + 16; x = x << 16 end - if (x & 0xFF000000) == 0 then n = n + 8; x = x << 8 end - if (x & 0xF0000000) == 0 then n = n + 4; x = x << 4 end - if (x & 0xC0000000) == 0 then n = n + 2; x = x << 2 end - if (x & 0x80000000) == 0 then n = n + 1 end + if (x & 0xFFFF0000) == 0 then + n = n + 16 + x = x << 16 + end + if (x & 0xFF000000) == 0 then + n = n + 8 + x = x << 8 + end + if (x & 0xF0000000) == 0 then + n = n + 4 + x = x << 4 + end + if (x & 0xC0000000) == 0 then + n = n + 2 + x = x << 2 + end + if (x & 0x80000000) == 0 then + n = n + 1 + end return n end - local idx = clz32(t) + 1 -- 1-based index + local idx = clz32(t) + 1 -- 1-based index if idx >= 1 and idx <= #TYPENAME then return TYPENAME[idx] end - return TYPENAME[1] -- S_any + return TYPENAME[1] -- S_any end -- Value is a node - defined, and a map (hash) or list (array). @@ -211,14 +234,12 @@ local function isnode(val) return ismap(val) or islist(val) end - -- Value is a defined map (hash) with string keys. -- @param val (any) The value to check -- @return (boolean) True if value is a map ismap = function(val) -- Check if the value is a table - if type(val) ~= "table" or - (getmetatable(val) and getmetatable(val).__jsontype == "array") then + if type(val) ~= "table" or (getmetatable(val) and getmetatable(val).__jsontype == "array") then return false end @@ -237,21 +258,23 @@ ismap = function(val) return true end - -- Value is a defined list (array) with integer keys (indexes). -- @param val (any) The value to check -- @return (boolean) True if value is a list islist = function(val) -- First check metatable indicators (preferred approach) - if getmetatable(val) and ((getmetatable(val).__jsontype == "array") or - (getmetatable(val).__jsontype and getmetatable(val).__jsontype.type == - "array")) then + if + getmetatable(val) + and ( + (getmetatable(val).__jsontype == "array") + or (getmetatable(val).__jsontype and getmetatable(val).__jsontype.type == "array") + ) + then return true end -- Check if it's a table - if type(val) ~= "table" or - (getmetatable(val) and getmetatable(val).__jsontype == "object") then + if type(val) ~= "table" or (getmetatable(val) and getmetatable(val).__jsontype == "object") then return false end @@ -271,17 +294,14 @@ islist = function(val) return count > 0 and max == count end - -- Value is a defined string (non-empty) or integer key. -- @param key (any) The key to check -- @return (boolean) True if key is valid local function iskey(key) local keytype = type(key) - return (keytype == S_string and key ~= S_MT and key ~= S_null) or keytype == - S_number + return (keytype == S_string and key ~= S_MT and key ~= S_null) or keytype == S_number end - -- Get a defined value. Returns alt if val is nil. local function getdef(val, alt) if nil == val then @@ -290,14 +310,15 @@ local function getdef(val, alt) return val end - -- The integer size of the value. local function size(val) if islist(val) then return #val elseif ismap(val) then local count = 0 - for _ in pairs(val) do count = count + 1 end + for _ in pairs(val) do + count = count + 1 + end return count end @@ -314,7 +335,6 @@ local function size(val) end end - -- Check for an "empty" value - nil, empty string, array, object. -- @param val (any) The value to check -- @return (boolean) True if value is empty @@ -338,15 +358,13 @@ local function isempty(val) return false end - -- Value is a function. -- @param val (any) The value to check -- @return (boolean) True if value is a function local function isfunc(val) - return type(val) == 'function' + return type(val) == "function" end - -- Determine the type of a value as a bit code. -- @param value (any) The value to check -- @return (number) The type as a bit flag @@ -358,9 +376,9 @@ local function typify(value) local luatype = type(value) if luatype == S_number then - if value ~= value then -- NaN check + if value ~= value then -- NaN check return T_noval - elseif math.type(value) == 'integer' or (value % 1 == 0) then + elseif math.type(value) == "integer" or (value % 1 == 0) then return T_scalar | T_number | T_integer else return T_scalar | T_number | T_decimal @@ -371,7 +389,7 @@ local function typify(value) return T_scalar | T_boolean elseif luatype == S_function then return T_scalar | T_function - elseif luatype == 'table' then + elseif luatype == "table" then if islist(value) then return T_node | T_list elseif ismap(value) then @@ -384,7 +402,6 @@ local function typify(value) return T_any end - -- Safely get a property of a node. Nil arguments return nil. -- If the key is not found, return the alternative value, if any. -- @param val (any) The parent object/table @@ -432,7 +449,6 @@ local function getprop(val, key, alt) return out end - -- Get a list element. The key should be an integer, or a string -- that can parse to an integer only. Negative integers count from the end of the list. local function getelem(val, key, alt) @@ -463,7 +479,6 @@ local function getelem(val, key, alt) return out end - -- Convert different types of keys to string representation. -- String keys are returned as is. -- Number keys are converted to strings. @@ -491,7 +506,6 @@ local function strkey(key) return S_MT end - -- Sorted keys of a map, or indexes of a list. -- @param val (any) The object or array to get keys from -- @return (table) Array of keys as strings @@ -519,7 +533,6 @@ local function keysof(val) end end - -- Value of property with name key in node val is defined. -- @param val (any) The object to check -- @param key (any) The key to check @@ -528,7 +541,6 @@ local function haskey(val, key) return getprop(val, key) ~= NONE end - -- List the sorted keys of a map or list as an array of tuples of the form {key, value} -- @param val (any) The object or array to convert to key-value pairs -- @return (table) Array of {key, value} pairs @@ -559,7 +571,6 @@ local function items(val) return result end - -- Filter item values using check function. -- check receives {key, value} pairs (1-indexed: [1]=key, [2]=value). -- Returns array of values where check returns true. @@ -576,7 +587,6 @@ local function filter(val, check) return out end - -- Escape regular expression. -- @param s (string) The string to escape -- @return (string) The escaped string @@ -586,7 +596,6 @@ local function escre(s) return result end - -- Escape URLs. -- @param s (string) The string to escape -- @return (string) The URL-encoded string @@ -599,10 +608,9 @@ local function escurl(s) return result end - -- Replace a search string (all), or a pattern, in a source string. local function replace(s, from, to) - local rs = s + local rs local ts = typify(s) if 0 == (T_string & ts) then rs = stringify(s) @@ -611,7 +619,7 @@ local function replace(s, from, to) else rs = stringify(s) end - if type(from) == 'string' then + if type(from) == "string" then -- Plain string replacement (all occurrences) return (rs:gsub(escre(from), to)) else @@ -620,7 +628,6 @@ local function replace(s, from, to) end end - -- Return a sub-array. Start and end are 0-based, end is exclusive. -- For numbers: clamp between start and end-1. -- For strings: substring from start to end. @@ -643,12 +650,16 @@ local function slice(val, start, endidx, mutate) if start ~= nil then if start < 0 then endidx = vlen + start - if endidx < 0 then endidx = 0 end + if endidx < 0 then + endidx = 0 + end start = 0 elseif endidx ~= nil then if endidx < 0 then endidx = vlen + endidx - if endidx < 0 then endidx = 0 end + if endidx < 0 then + endidx = 0 + end elseif vlen < endidx then endidx = vlen end @@ -686,7 +697,9 @@ local function slice(val, start, endidx, mutate) else if islist(val) then if mutate then - for i = 1, #val do val[i] = nil end + for i = 1, #val do + val[i] = nil + end return val end return setmetatable({}, { __jsontype = "array" }) @@ -699,7 +712,6 @@ local function slice(val, start, endidx, mutate) return val end - -- Flatten nested lists to a given depth. local function flatten(val, depth) if not islist(val) then @@ -710,7 +722,7 @@ local function flatten(val, depth) setmetatable(result, { __jsontype = "array" }) for _, item in ipairs(val) do - if (islist(item) or (type(item) == 'table' and next(item) == nil)) and depth > 0 then + if (islist(item) or (type(item) == "table" and next(item) == nil)) and depth > 0 then local sub = flatten(item, depth - 1) for _, v in ipairs(sub) do table.insert(result, v) @@ -722,14 +734,15 @@ local function flatten(val, depth) return result end - -- Pad a string or number. -- Positive padlen = right-pad (padEnd), negative padlen = left-pad (padStart). local function pad(val, padlen, padchar) val = S_string == type(val) and val or stringify(val) padlen = padlen or 44 padchar = padchar or S_SP - if #padchar > 1 then padchar = padchar:sub(1, 1) end + if #padchar > 1 then + padchar = padchar:sub(1, 1) + end if padlen >= 0 then -- Right-pad (padEnd) @@ -746,7 +759,6 @@ local function pad(val, padlen, padchar) return val end - -- Delete a property from a node. delprop = function(parent, key) if not iskey(key) then @@ -771,7 +783,6 @@ delprop = function(parent, key) return parent end - -- Build a JSON map from alternating key, value arguments. local function jm(...) local kv = { ... } @@ -779,7 +790,7 @@ local function jm(...) local o = {} local i = 0 while i < kvsize do - local k = getprop(kv, i, '$KEY' .. i) + local k = getprop(kv, i, "$KEY" .. i) k = S_string == type(k) and k or stringify(k) o[k] = getprop(kv, i + 1, nil) i = i + 2 @@ -787,7 +798,6 @@ local function jm(...) return o end - -- Define a JSON Array using function arguments. local function jt(...) local v = { ... } @@ -800,7 +810,6 @@ local function jt(...) return a end - -- Concatenate strings, merging separator char as needed. -- Default separator is comma. When url=true, preserve protocol slashes. -- @param arr (table) Array of parts to join @@ -827,14 +836,14 @@ local function join(arr, sep, url) local v = arr[i] local ts = typify(v) if (0 < (T_string & ts)) and v ~= S_MT and v ~= S_null then - table.insert(string_items, { i - 1, v }) -- 0-based index, value + table.insert(string_items, { i - 1, v }) -- 0-based index, value end end -- Step 2: Process each element to clean separators local processed = {} for _, item in ipairs(string_items) do - local idx = item[1] -- 0-based original index + local idx = item[1] -- 0-based original index local s = item[2] if seppat ~= nil and seppat ~= S_MT then @@ -853,8 +862,10 @@ local function join(arr, sep, url) end -- Collapse multiple seps between non-sep chars - s = s:gsub("([^" .. seppat .. "])" .. seppat .. "+([^" .. seppat .. "])", - "%1" .. sepdef .. "%2") + s = s:gsub( + "([^" .. seppat .. "])" .. seppat .. "+([^" .. seppat .. "])", + "%1" .. sepdef .. "%2" + ) end end @@ -866,19 +877,18 @@ local function join(arr, sep, url) return table.concat(processed, sepdef) end - -- Safely stringify a value for humans (NOT JSON!) -- Strings are returned as-is (not quoted). -- @param val (any) The value to stringify -- @param maxlen (number) Optional maximum length for result -- @param pretty (boolean) Optional pretty mode with ANSI colors -- @return (string) String representation of the value -local function stringify(val, maxlen, pretty) +stringify = function(val, maxlen, pretty) local valstr = S_MT pretty = pretty and true or false if val == nil then - return pretty and '<>' or valstr + return pretty and "<>" or valstr end if type(val) == S_string then @@ -896,26 +906,28 @@ local function stringify(val, maxlen, pretty) local function serialize(obj, seen) seen = seen or {} - if type(obj) == 'table' and seen[obj] then - return '...' + if type(obj) == "table" and seen[obj] then + return "..." end local obj_type = type(obj) if obj == nil then - return 'null' + return "null" elseif obj_type == S_number then - if obj ~= obj then return 'null' end -- NaN + if obj ~= obj then + return "null" + end -- NaN -- Use integer representation for whole numbers if obj % 1 == 0 then - return string.format('%d', obj) + return string.format("%d", obj) end return tostring(obj) elseif obj_type == S_boolean then return tostring(obj) elseif obj_type == S_function then - return 'null' - elseif obj_type ~= 'table' then + return "null" + elseif obj_type ~= "table" then return tostring(obj) end @@ -938,9 +950,9 @@ local function stringify(val, maxlen, pretty) seen[obj] = nil if is_arr then - return S_OS .. table.concat(parts, ',') .. S_CS + return S_OS .. table.concat(parts, ",") .. S_CS else - return '{' .. table.concat(parts, ',') .. '}' + return "{" .. table.concat(parts, ",") .. "}" end end @@ -951,31 +963,33 @@ local function stringify(val, maxlen, pretty) if success then valstr = result else - valstr = '__STRINGIFY_FAILED__' + valstr = "__STRINGIFY_FAILED__" end end -- Handle maxlen if maxlen ~= nil and maxlen > -1 then if maxlen < #valstr then - valstr = string.sub(valstr, 1, maxlen - 3) .. '...' + valstr = string.sub(valstr, 1, maxlen - 3) .. "..." end end if pretty then local c = { 81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69 } - local r = '\x1b[0m' + local r = "\x1b[0m" local d = 0 - local function cc(n) return '\x1b[38;5;' .. n .. 'm' end + local function cc(n) + return "\x1b[38;5;" .. n .. "m" + end local o = cc(c[1]) local t = o for i = 1, #valstr do local ch = valstr:sub(i, i) - if ch == '{' or ch == S_OS then + if ch == "{" or ch == S_OS then d = d + 1 o = cc(c[(d % #c) + 1]) t = t .. o .. ch - elseif ch == '}' or ch == S_CS then + elseif ch == "}" or ch == S_CS then t = t .. o .. ch d = d - 1 o = cc(c[(d % #c) + 1]) @@ -989,15 +1003,14 @@ local function stringify(val, maxlen, pretty) return valstr end - -- Convert a value to JSON string representation (matching JSON.stringify behavior). local function jsonify(val, flags) local str = S_null if val ~= nil then local ok, result = pcall(function() - local indent_size = getprop(flags, 'indent', 2) - local offset = getprop(flags, 'offset', 0) + local indent_size = getprop(flags, "indent", 2) + local offset = getprop(flags, "offset", 0) -- Recursive JSON serializer matching JSON.stringify(val, null, indent) local function ser(v, depth) @@ -1006,22 +1019,27 @@ local function jsonify(val, flags) elseif type(v) == S_boolean then return tostring(v) elseif type(v) == S_number then - if v ~= v then return S_null end -- NaN + if v ~= v then + return S_null + end -- NaN if v % 1 == 0 then - return string.format('%d', v) + return string.format("%d", v) end return tostring(v) elseif type(v) == S_string then -- Escape string for JSON - local escaped = v:gsub('\\', '\\\\'):gsub('"', '\\"') - :gsub('\n', '\\n'):gsub('\r', '\\r'):gsub('\t', '\\t') + local escaped = v:gsub("\\", "\\\\") + :gsub('"', '\\"') + :gsub("\n", "\\n") + :gsub("\r", "\\r") + :gsub("\t", "\\t") return '"' .. escaped .. '"' elseif type(v) == S_function then - return nil -- Functions are omitted in JSON - elseif type(v) == 'table' then + return nil -- Functions are omitted in JSON + elseif type(v) == "table" then if islist(v) then if #v == 0 then - return '[]' + return "[]" end local parts = {} for i = 1, #v do @@ -1029,35 +1047,43 @@ local function jsonify(val, flags) table.insert(parts, sv or S_null) end if indent_size == 0 then - return '[' .. table.concat(parts, ',') .. ']' + return "[" .. table.concat(parts, ",") .. "]" end - local pad_str = string.rep(' ', indent_size * (depth + 1) + offset) - local close_pad = string.rep(' ', indent_size * depth + offset) - return '[\n' .. pad_str .. table.concat(parts, ',\n' .. pad_str) .. - '\n' .. close_pad .. ']' + local pad_str = string.rep(" ", indent_size * (depth + 1) + offset) + local close_pad = string.rep(" ", indent_size * depth + offset) + return "[\n" + .. pad_str + .. table.concat(parts, ",\n" .. pad_str) + .. "\n" + .. close_pad + .. "]" else -- Map/object local keys_list = keysof(v) if #keys_list == 0 then - return '{}' + return "{}" end local parts = {} for _, k in ipairs(keys_list) do local sv = ser(v[k], depth + 1) - if sv ~= nil then -- Skip undefined values + if sv ~= nil then -- Skip undefined values table.insert(parts, '"' .. k .. '": ' .. sv) end end if #parts == 0 then - return '{}' + return "{}" end if indent_size == 0 then - return '{' .. table.concat(parts, ',') .. '}' + return "{" .. table.concat(parts, ",") .. "}" end - local pad_str = string.rep(' ', indent_size * (depth + 1) + offset) - local close_pad = string.rep(' ', indent_size * depth + offset) - return '{\n' .. pad_str .. table.concat(parts, ',\n' .. pad_str) .. - '\n' .. close_pad .. '}' + local pad_str = string.rep(" ", indent_size * (depth + 1) + offset) + local close_pad = string.rep(" ", indent_size * depth + offset) + return "{\n" + .. pad_str + .. table.concat(parts, ",\n" .. pad_str) + .. "\n" + .. close_pad + .. "}" end end return S_null @@ -1073,14 +1099,13 @@ local function jsonify(val, flags) if ok and result ~= nil then str = result else - str = '__JSONIFY_FAILED__' + str = "__JSONIFY_FAILED__" end end return str end - -- Build a human friendly path string. -- @param val (any) The path as array or string -- @param startin (number) Optional start index @@ -1096,12 +1121,12 @@ local function pathify(val, startin, endin) elseif type(val) == S_string then path = { val } setmetatable(path, { - __jsontype = "array" + __jsontype = "array", }) elseif type(val) == S_number then path = { val } setmetatable(path, { - __jsontype = "array" + __jsontype = "array", }) end @@ -1118,7 +1143,7 @@ local function pathify(val, startin, endin) path = sliced if #path == 0 then - pathstr = '' + pathstr = "" else -- Filter valid path elements using iskey local filtered = {} @@ -1148,19 +1173,18 @@ local function pathify(val, startin, endin) -- Handle unknown paths if pathstr == NONE then - pathstr = '' + pathstr = pathstr .. ">" end return pathstr end - -- Set a value deep inside a node at a key path. local function setpath(store, path, val, injdef) local pathType = typify(path) @@ -1202,7 +1226,7 @@ local function setpath(store, path, val, injdef) local lastKey = getelem(parts, -1) - if type(val) == 'table' and val['`$DELETE`'] then + if type(val) == "table" and val["`$DELETE`"] then delprop(parent, lastKey) else setprop(parent, lastKey, val) @@ -1211,7 +1235,6 @@ local function setpath(store, path, val, injdef) return parent end - -- Clone a JSON-like data structure. -- NOTE: function value references are copied, *not* cloned. -- @param val (any) The value to clone @@ -1283,7 +1306,6 @@ local function clone(val, flags) return val end - -- Safely set a property. Undefined arguments and invalid keys are ignored. -- Returns the (possibly modified) parent. -- If the parent is a list, and the key is negative, prepend the value. @@ -1329,7 +1351,6 @@ setprop = function(parent, key, val) return parent end - -- Walk a data structure depth first, applying a function to each value. -- The `path` argument passed to the before/after callbacks is a single -- mutable array per depth, shared across all callback invocations for the @@ -1345,8 +1366,7 @@ end -- @param path (table) Current path (for recursive calls) -- @param pool (table) Per-depth reusable path arrays (for recursive calls) -- @return (any) The transformed value -local function walk(val, before, after, maxdepth, - key, parent, path, pool) +local function walk(val, before, after, maxdepth, key, parent, path, pool) if nil == pool then pool = {} local rootPath = {} @@ -1402,7 +1422,6 @@ local function walk(val, before, after, maxdepth, return out end - -- Merge a list of values into each other. Later values have -- precedence. Nodes override scalars. Node kinds (list or map) -- override each other, and do *not* merge. The first element is @@ -1412,7 +1431,7 @@ end -- @return (any) The merged result local function merge(val, maxdepth) local md = slice(getdef(maxdepth, MAXDEPTH), 0) - local out = NONE + local out -- Handle edge cases if not islist(val) then @@ -1471,8 +1490,7 @@ local function merge(val, maxdepth) -- Destination empty, so create node (unless override is class instance). if NONE == tval and 0 == (T_instance & typify(bval)) then - cur[pI + 1] = islist(bval) and - setmetatable({}, { __jsontype = "array" }) or {} + cur[pI + 1] = islist(bval) and setmetatable({}, { __jsontype = "array" }) or {} -- Matching override and destination so continue with their values. elseif typify(bval) == typify(tval) then @@ -1505,14 +1523,12 @@ local function merge(val, maxdepth) if 0 == md then out = getelem(list, -1) - out = islist(out) and setmetatable({}, { __jsontype = "array" }) - or ismap(out) and {} or out + out = islist(out) and setmetatable({}, { __jsontype = "array" }) or ismap(out) and {} or out end return out end - -- Get a value deep inside a node using a key path. -- @param store (table) The data store to search in -- @param path (string|table|number) The path to the value @@ -1529,7 +1545,7 @@ getpath = function(store, path, injdef) local pos = 1 local len = #path while pos <= len do - local dotpos = path:find('.', pos, true) + local dotpos = path:find(".", pos, true) if dotpos then table.insert(parts, path:sub(pos, dotpos - 1)) pos = dotpos + 1 @@ -1541,14 +1557,12 @@ getpath = function(store, path, injdef) if pos == 1 then -- Empty string parts = { S_MT } - elseif pos == len + 1 then - -- Normal end - else + elseif pos ~= len + 1 then -- Path ends with a dot table.insert(parts, S_MT) end -- Handle trailing dot: "a." -> ["a", ""] - if len > 0 and path:sub(len, len) == '.' then + if len > 0 and path:sub(len, len) == "." then table.insert(parts, S_MT) end elseif type(path) == S_number then @@ -1561,13 +1575,12 @@ getpath = function(store, path, injdef) local base = getprop(injdef, S_base) local src = getprop(store, base, store) local numparts = #parts - local dparent = getprop(injdef, 'dparent') + local dparent = getprop(injdef, "dparent") -- An empty path (incl empty string) just finds the store. if path == nil or store == nil or (1 == numparts and S_MT == parts[1]) then val = src elseif 0 < numparts then - -- Check for $ACTIONs if 1 == numparts then val = getprop(store, parts[1]) @@ -1577,34 +1590,34 @@ getpath = function(store, path, injdef) val = src -- Check for meta path syntax: field$=value or field$~value - local m1, m2, m3 = parts[1]:match("^([^$]+)%$([=~])(.+)$") + local m1, _, m3 = parts[1]:match("^([^$]+)%$([=~])(.+)$") if m1 and injdef and injdef.meta then val = getprop(injdef.meta, m1) parts[1] = m3 end - local dpath = getprop(injdef, 'dpath') + local dpath = getprop(injdef, "dpath") local pI = 0 while NONE ~= val and pI < numparts do - local part = parts[pI + 1] -- Lua 1-based + local part = parts[pI + 1] -- Lua 1-based if injdef and S_DKEY == part then part = getprop(injdef, S_key) - elseif injdef and part and #part > 5 and part:sub(1, 5) == '$GET:' then + elseif injdef and part and #part > 5 and part:sub(1, 5) == "$GET:" then -- $GET:path$ -> get store value, use as path part (strip trailing $) part = stringify(getpath(src, slice(part, 5, -1))) - elseif injdef and part and #part > 5 and part:sub(1, 5) == '$REF:' then + elseif injdef and part and #part > 5 and part:sub(1, 5) == "$REF:" then -- $REF:refpath$ -> get spec value, use as path part (strip trailing $) part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1))) - elseif injdef and part and #part > 6 and part:sub(1, 6) == '$META:' then + elseif injdef and part and #part > 6 and part:sub(1, 6) == "$META:" then -- $META:metapath$ -> get meta value, use as path part (strip trailing $) - part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1))) + part = stringify(getpath(getprop(injdef, "meta"), slice(part, 6, -1))) end -- $$ escapes $ if part and type(part) == S_string then - part = part:gsub('%$%$', '$') + part = part:gsub("%$%$", "$") end if S_MT == part then @@ -1649,7 +1662,7 @@ getpath = function(store, path, injdef) end -- Injdef may provide a custom handler to modify found value. - local handler = getprop(injdef, 'handler') + local handler = getprop(injdef, "handler") if nil ~= injdef and isfunc(handler) then local ref = pathify(path) val = handler(injdef, val, ref, store) @@ -1658,7 +1671,6 @@ getpath = function(store, path, injdef) return val end - -- Injection "class" for managing injection state. -- Methods: descend, child, setval @@ -1690,21 +1702,37 @@ function Injection:new(val, parent) return o end - function Injection:__tostring() - return 'INJ' .. S_CN .. - pad(pathify(self.path, 1)) .. - (MODENAME[self.mode] or '') .. (self.full and '/full' or '') .. S_CN .. - 'key=' .. self.keyI .. S_FS .. tostring(self.key) .. S_FS .. S_OS .. table.concat(self.keys, ',') .. S_CS .. - ' p=' .. stringify(self.parent, -1, 1) .. - ' m=' .. stringify(self.meta, -1, 1) .. - ' d/' .. pathify(self.dpath, 1) .. '=' .. stringify(self.dparent, -1, 1) .. - ' r=' .. stringify(getprop(getprop(self.nodes, 0), S_DTOP), -1, 1) + return "INJ" + .. S_CN + .. pad(pathify(self.path, 1)) + .. (MODENAME[self.mode] or "") + .. (self.full and "/full" or "") + .. S_CN + .. "key=" + .. self.keyI + .. S_FS + .. tostring(self.key) + .. S_FS + .. S_OS + .. table.concat(self.keys, ",") + .. S_CS + .. " p=" + .. stringify(self.parent, -1, 1) + .. " m=" + .. stringify(self.meta, -1, 1) + .. " d/" + .. pathify(self.dpath, 1) + .. "=" + .. stringify(self.dparent, -1, 1) + .. " r=" + .. stringify(getprop(getprop(self.nodes, 0), S_DTOP), -1, 1) end - function Injection:descend() - if self.meta.__d == nil then self.meta.__d = 0 end + if self.meta.__d == nil then + self.meta.__d = 0 + end self.meta.__d = self.meta.__d + 1 local parentkey = getelem(self.path, -2) @@ -1718,7 +1746,7 @@ function Injection:descend() self.dparent = getprop(self.dparent, parentkey) local lastpart = getelem(self.dpath, -1) - if lastpart == '$:' .. tostring(parentkey) then + if lastpart == "$:" .. tostring(parentkey) then self.dpath = slice(self.dpath, -1) else self.dpath = flatten({ self.dpath, parentkey }) @@ -1729,9 +1757,8 @@ function Injection:descend() return self.dparent end - function Injection:child(keyI, keys) - local key = strkey(keys[keyI + 1]) -- Lua 1-based + local key = strkey(keys[keyI + 1]) -- Lua 1-based local val = self.val local cinj = Injection:new(getprop(val, key), val) @@ -1756,9 +1783,8 @@ function Injection:child(keyI, keys) return cinj end - function Injection:setval(val, ancestor) - local parent = NONE + local parent if ancestor == nil or ancestor < 2 then if NONE == val then self.parent = delprop(self.parent, self.key) @@ -1778,7 +1804,6 @@ function Injection:setval(val, ancestor) return parent end - -- Inject values from a data store into a node recursively. -- @param val (any) The value to inject into -- @param store (table) The data store @@ -1826,7 +1851,7 @@ local function inject(val, store, injdef) else nodekeys = {} for i = 1, #val do - table.insert(nodekeys, i - 1) -- 0-based indices + table.insert(nodekeys, i - 1) -- 0-based indices end end @@ -1851,10 +1876,6 @@ local function inject(val, store, injdef) -- Perform val mode injection inject(childinj.val, store, childinj) - -- The injection may modify child processing. - nkI = childinj.keyI - nodekeys = childinj.keys - -- Perform key:post mode injection childinj.mode = M_KEYPOST _injectstr(nodekey, store, childinj) @@ -1865,7 +1886,6 @@ local function inject(val, store, injdef) nkI = nkI + 1 end - elseif S_string == valtype then inj.mode = M_VAL val = _injectstr(val, store, inj) @@ -1887,17 +1907,15 @@ local function inject(val, store, injdef) return getprop(inj.parent, S_DTOP) end - -- Delete a key from a map or list. local function transform_DELETE(inj) inj:setval(NONE) return NONE end - -- Copy value from source data. local function transform_COPY(inj, _val) - local ijname = 'COPY' + local ijname = "COPY" if not checkPlacement(M_VAL, ijname, T_any, inj) then return NONE @@ -1908,7 +1926,6 @@ local function transform_COPY(inj, _val) return out end - -- As a value, inject the key of the parent node. local function transform_KEY(inj) local mode, path, parent = inj.mode, inj.path, inj.parent @@ -1927,14 +1944,12 @@ local function transform_KEY(inj) return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2)) end - -- Store annotation data about a node. local function transform_ANNO(inj) delprop(inj.parent, S_BANNO) return NONE end - -- Merge a list of objects into the current object. local function transform_MERGE(inj) local mode, key, parent = inj.mode, inj.key, inj.parent @@ -1943,7 +1958,6 @@ local function transform_MERGE(inj) if M_KEYPRE == mode then out = key - elseif M_KEYPOST == mode then out = key @@ -1964,7 +1978,6 @@ local function transform_MERGE(inj) return out end - -- Helper: injectChild local function injectChild(child, store, inj) local cinj = inj @@ -1985,11 +1998,10 @@ local function injectChild(child, store, inj) return cinj end - -- Convert a node to a list. -- Format: ['`$EACH`', '`source-path-of-node`', child-template] local function transform_EACH(inj, _val, _ref, store) - local ijname = 'EACH' + local ijname = "EACH" if not checkPlacement(M_VAL, ijname, T_list, inj) then return NONE @@ -1998,14 +2010,18 @@ local function transform_EACH(inj, _val, _ref, store) -- Remove remaining keys to avoid spurious processing. local trimmed = slice(inj.keys, 0, 1) -- Replace keys in-place - for i = #inj.keys, 1, -1 do inj.keys[i] = nil end - for i, v in ipairs(trimmed) do inj.keys[i] = v end + for i = #inj.keys, 1, -1 do + inj.keys[i] = nil + end + for i, v in ipairs(trimmed) do + inj.keys[i] = v + end -- Get arguments: ['`$EACH`', 'source-path', child-template] local each_args = injectorArgs({ T_string, T_any }, slice(inj.parent, 1)) local err = each_args[1] if NONE ~= err then - table.insert(inj.errs, '$' .. ijname .. ': ' .. err) + table.insert(inj.errs, "$" .. ijname .. ": " .. err) return NONE end local srcpath = each_args[2] @@ -2016,12 +2032,14 @@ local function transform_EACH(inj, _val, _ref, store) local src = getpath(srcstore, srcpath, inj) local srctype = typify(src) - local tcur = {} + local tcur local tval = {} setmetatable(tval, { __jsontype = "array" }) local tkey = getelem(inj.path, -2) - local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end) + local target = getelem(inj.nodes, -2, function() + return getelem(inj.nodes, -1) + end) -- Create clones of the child template for each value of the source. if 0 < (T_list & srctype) then @@ -2043,7 +2061,9 @@ local function transform_EACH(inj, _val, _ref, store) local srcvals = {} setmetatable(srcvals, { __jsontype = "array" }) if islist(src) then - for i = 1, #src do table.insert(srcvals, src[i]) end + for i = 1, #src do + table.insert(srcvals, src[i]) + end elseif ismap(src) then for _, item in ipairs(items(src)) do table.insert(srcvals, item[2]) @@ -2060,14 +2080,14 @@ local function transform_EACH(inj, _val, _ref, store) table.insert(srcparts, p) end end - local dpath = flatten({ S_DTOP, srcparts, '$:' .. tostring(ckey) }) + local dpath = flatten({ S_DTOP, srcparts, "$:" .. tostring(ckey) }) tcur = { [ckey] = srcvals } if 1 < size(tpath) then local pkey = getelem(inj.path, -3, S_DTOP) tcur = { [pkey] = tcur } - table.insert(dpath, '$:' .. tostring(pkey)) + table.insert(dpath, "$:" .. tostring(pkey)) end local tinj = inj:child(0, { ckey }) @@ -2089,14 +2109,12 @@ local function transform_EACH(inj, _val, _ref, store) return getelem(rval, 0) end - -- Convert a node to a map. -- Format: { '`$PACK`':['`source-path`', child-template]} local function transform_PACK(inj, _val, _ref, store) - local mode, key, path, parent, nodes = inj.mode, inj.key, inj.path, - inj.parent, inj.nodes + local key, path, parent, nodes = inj.key, inj.path, inj.parent, inj.nodes - local ijname = 'EACH' + local ijname = "EACH" if not checkPlacement(M_KEYPRE, ijname, T_map, inj) then return NONE @@ -2107,7 +2125,7 @@ local function transform_PACK(inj, _val, _ref, store) local pack_args = injectorArgs({ T_string, T_any }, args) local err = pack_args[1] if NONE ~= err then - table.insert(inj.errs, '$' .. ijname .. ': ' .. err) + table.insert(inj.errs, "$" .. ijname .. ": " .. err) return NONE end local srcpath = pack_args[2] @@ -2185,7 +2203,7 @@ local function transform_PACK(inj, _val, _ref, store) local srcnode = item[2] local kn if keypath == nil then - kn = srcI - 1 -- 0-based + kn = srcI - 1 -- 0-based elseif type(keypath) == S_string and keypath:sub(1, 1) == S_BT then kn = inject(keypath, merge({ {}, store, { [S_DTOP] = srcnode } }, 1)) else @@ -2203,14 +2221,14 @@ local function transform_PACK(inj, _val, _ref, store) table.insert(srcparts, p) end end - local dpath = flatten({ S_DTOP, srcparts, '$:' .. tostring(ckey) }) + local dpath = flatten({ S_DTOP, srcparts, "$:" .. tostring(ckey) }) local tcur = { [ckey] = tsrc } if 1 < size(tpath) then local pkey = getelem(inj.path, -3, S_DTOP) tcur = { [pkey] = tcur } - table.insert(dpath, '$:' .. tostring(pkey)) + table.insert(dpath, "$:" .. tostring(pkey)) end local tinj = inj:child(0, { ckey }) @@ -2231,15 +2249,13 @@ local function transform_PACK(inj, _val, _ref, store) return NONE end - -- Placement labels for error messages. local PLACEMENT = { - [M_VAL] = 'value', + [M_VAL] = "value", [M_KEYPRE] = S_key, [M_KEYPOST] = S_key, } - -- Check that a transform is used in the correct mode and parent type. checkPlacement = function(modes, ijname, parentTypes, inj) if 0 == (modes & inj.mode) then @@ -2250,35 +2266,55 @@ checkPlacement = function(modes, ijname, parentTypes, inj) table.insert(expected, PLACEMENT[m]) end end - table.insert(inj.errs, '$' .. ijname .. ': invalid placement as ' .. - PLACEMENT[inj.mode] .. ', expected: ' .. - table.concat(expected, ',') .. '.') + table.insert( + inj.errs, + "$" + .. ijname + .. ": invalid placement as " + .. PLACEMENT[inj.mode] + .. ", expected: " + .. table.concat(expected, ",") + .. "." + ) return false end if not isempty(parentTypes) then local ptype = typify(inj.parent) if 0 == (parentTypes & ptype) then - table.insert(inj.errs, '$' .. ijname .. ': invalid placement in parent ' .. - typename(ptype) .. ', expected: ' .. typename(parentTypes) .. '.') + table.insert( + inj.errs, + "$" + .. ijname + .. ": invalid placement in parent " + .. typename(ptype) + .. ", expected: " + .. typename(parentTypes) + .. "." + ) return false end end return true end - -- Validate and extract typed arguments from a list. injectorArgs = function(argTypes, args) local numargs = size(argTypes) local found = {} - found[1] = NONE -- err slot (1-based) + found[1] = NONE -- err slot (1-based) for argI = 1, numargs do - local arg = getprop(args, argI - 1) -- 0-based access + local arg = getprop(args, argI - 1) -- 0-based access local argType = typify(arg) if 0 == (argTypes[argI] & argType) then - found[1] = 'invalid argument: ' .. stringify(arg, 22) .. - ' (' .. typename(argType) .. ' at position ' .. argI .. - ') is not of type: ' .. typename(argTypes[argI]) .. '.' + found[1] = "invalid argument: " + .. stringify(arg, 22) + .. " (" + .. typename(argType) + .. " at position " + .. argI + .. ") is not of type: " + .. typename(argTypes[argI]) + .. "." break end found[1 + argI] = arg @@ -2286,7 +2322,6 @@ injectorArgs = function(argTypes, args) return found end - -- Transform: resolve a reference to another part of the spec. -- Format: ['`$REF`', 'ref-path'] local function transform_REF(inj, val, _ref, store) @@ -2313,7 +2348,7 @@ local function transform_REF(inj, val, _ref, store) local hasSubRef = false if isnode(ref) then walk(ref, function(_k, v) - if '`$REF`' == v then + if "`$REF`" == v then hasSubRef = true end return v @@ -2326,7 +2361,7 @@ local function transform_REF(inj, val, _ref, store) local tpath = slice(inj.path, -1) local tcur = getpath(store, cpath) local tval = getpath(store, tpath) - local rval = NONE + local rval if not hasSubRef or NONE ~= tval then local tinj = inj:child(0, { getelem(tpath, -1) }) @@ -2355,10 +2390,11 @@ local function transform_REF(inj, val, _ref, store) return val end - -- Named formatters for transform_FORMAT. local FORMATTER = { - identity = function(_k, v) return v end, + identity = function(_k, v) + return v + end, upper = function(_k, v) return isnode(v) and v or string.upper(tostring(v)) end, @@ -2369,14 +2405,20 @@ local FORMATTER = { return isnode(v) and v or tostring(v) end, number = function(_k, v) - if isnode(v) then return v end + if isnode(v) then + return v + end local n = tonumber(v) return (n == nil or n ~= n) and 0 or n end, integer = function(_k, v) - if isnode(v) then return v end + if isnode(v) then + return v + end local n = tonumber(v) - if n == nil or n ~= n then n = 0 end + if n == nil or n ~= n then + n = 0 + end return math.floor(n) end, concat = function(k, v) @@ -2384,7 +2426,7 @@ local FORMATTER = { local parts = {} for _, item in ipairs(items(v)) do local val = item[2] - table.insert(parts, isnode(val) and '' or tostring(val)) + table.insert(parts, isnode(val) and "" or tostring(val)) end return table.concat(parts) end @@ -2392,7 +2434,6 @@ local FORMATTER = { end, } - -- Transform: format values using named formatters. -- Format: ['`$FORMAT`', 'name', child] local function transform_FORMAT(inj, _val, _ref, store) @@ -2409,7 +2450,9 @@ local function transform_FORMAT(inj, _val, _ref, store) -- Source data. local tkey = getelem(inj.path, -2) - local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end) + local target = getelem(inj.nodes, -2, function() + return getelem(inj.nodes, -1) + end) local cinj = injectChild(child, store, inj) local resolved = cinj.val @@ -2417,7 +2460,7 @@ local function transform_FORMAT(inj, _val, _ref, store) local formatter = (0 < (T_function & typify(name))) and name or getprop(FORMATTER, name) if NONE == formatter then - table.insert(inj.errs, '$FORMAT: unknown format: ' .. tostring(name) .. '.') + table.insert(inj.errs, "$FORMAT: unknown format: " .. tostring(name) .. ".") return NONE end @@ -2428,11 +2471,10 @@ local function transform_FORMAT(inj, _val, _ref, store) return out end - -- Apply a function to a value. -- Format: ['`$APPLY`', function, child] local function transform_APPLY(inj, _val, _ref, store) - local ijname = 'APPLY' + local ijname = "APPLY" if not checkPlacement(M_VAL, ijname, T_list, inj) then return NONE @@ -2441,12 +2483,14 @@ local function transform_APPLY(inj, _val, _ref, store) local found = injectorArgs({ T_function, T_any }, slice(inj.parent, 1)) local err, apply, child = found[1], found[2], found[3] if NONE ~= err then - table.insert(inj.errs, '$' .. ijname .. ': ' .. err) + table.insert(inj.errs, "$" .. ijname .. ": " .. err) return NONE end local tkey = getelem(inj.path, -2) - local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end) + local target = getelem(inj.nodes, -2, function() + return getelem(inj.nodes, -1) + end) local cinj = injectChild(child, store, inj) local resolved = cinj.val @@ -2457,7 +2501,6 @@ local function transform_APPLY(inj, _val, _ref, store) return out end - -- Transform data using spec. -- @param data (any) Source data to transform -- @param spec (any) Transform specification @@ -2498,51 +2541,58 @@ local function transform(data, spec, injdef) { [S_DTOP] = dataClone, - [S_DSPEC] = function() return origspec end, - - ['$BT'] = function() return S_BT end, - ['$DS'] = function() return S_DS end, - ['$WHEN'] = function() return os.date('!%Y-%m-%dT%H:%M:%S.000Z') end, - - ['$DELETE'] = transform_DELETE, - ['$COPY'] = transform_COPY, - ['$KEY'] = transform_KEY, - ['$ANNO'] = transform_ANNO, - ['$MERGE'] = transform_MERGE, - ['$EACH'] = transform_EACH, - ['$PACK'] = transform_PACK, - ['$REF'] = transform_REF, - ['$FORMAT'] = transform_FORMAT, - ['$APPLY'] = transform_APPLY, + [S_DSPEC] = function() + return origspec + end, + + ["$BT"] = function() + return S_BT + end, + ["$DS"] = function() + return S_DS + end, + ["$WHEN"] = function() + return os.date("!%Y-%m-%dT%H:%M:%S.000Z") + end, + + ["$DELETE"] = transform_DELETE, + ["$COPY"] = transform_COPY, + ["$KEY"] = transform_KEY, + ["$ANNO"] = transform_ANNO, + ["$MERGE"] = transform_MERGE, + ["$EACH"] = transform_EACH, + ["$PACK"] = transform_PACK, + ["$REF"] = transform_REF, + ["$FORMAT"] = transform_FORMAT, + ["$APPLY"] = transform_APPLY, }, extraTransforms, - { ['$ERRS'] = errs }, + { ["$ERRS"] = errs }, }, 1) local out = inject(spec, store, injdef) local generr = 0 < size(errs) and not collect if generr then - error(table.concat(errs, ' | ')) + error(table.concat(errs, " | ")) end return out end - -- A required string value. NOTE: Rejects empty strings. local function validate_STRING(inj) local out = getprop(inj.dparent, inj.key) local t = typify(out) if 0 == (T_string & t) then - local msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010') + local msg = _invalidTypeMsg(inj.path, S_string, t, out, "V1010") table.insert(inj.errs, msg) return NONE end if S_MT == out then - local msg = 'Empty string at ' .. pathify(inj.path, 1) + local msg = "Empty string at " .. pathify(inj.path, 1) table.insert(inj.errs, msg) return NONE end @@ -2550,7 +2600,6 @@ local function validate_STRING(inj) return out end - -- A generic type validator. Ref is used to determine which type to check. local function validate_TYPE(inj, _val, ref) local tname = slice(ref, 1):lower() @@ -2573,27 +2622,24 @@ local function validate_TYPE(inj, _val, ref) local t = typify(out) if 0 == (t & typev) then - table.insert(inj.errs, _invalidTypeMsg(inj.path, tname, t, out, 'V1001')) + table.insert(inj.errs, _invalidTypeMsg(inj.path, tname, t, out, "V1001")) return NONE end return out end - -- Allow any value. local function validate_ANY(inj) local out = getprop(inj.dparent, inj.key) return out end - -- Specify child values for map or list. -- Map syntax: {'`$CHILD`': child-template } -- List syntax: ['`$CHILD`', child-template ] local function validate_CHILD(inj) - local mode, key, parent, keys, path = inj.mode, inj.key, inj.parent, - inj.keys, inj.path + local mode, key, parent, keys, path = inj.mode, inj.key, inj.parent, inj.keys, inj.path -- Map syntax. if M_KEYPRE == mode then @@ -2606,8 +2652,10 @@ local function validate_CHILD(inj) if NONE == tval then tval = {} elseif not ismap(tval) then - table.insert(inj.errs, _invalidTypeMsg( - slice(inj.path, 0, -1), S_object, typify(tval), tval, 'V0220')) + table.insert( + inj.errs, + _invalidTypeMsg(slice(inj.path, 0, -1), S_object, typify(tval), tval, "V0220") + ) return NONE end @@ -2628,7 +2676,7 @@ local function validate_CHILD(inj) if M_VAL == mode then if not islist(parent) then -- $CHILD was not inside a list. - table.insert(inj.errs, 'Invalid $CHILD as value') + table.insert(inj.errs, "Invalid $CHILD as value") return NONE end @@ -2641,8 +2689,8 @@ local function validate_CHILD(inj) end if not islist(inj.dparent) then - local msg = _invalidTypeMsg( - slice(inj.path, 0, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230') + local msg = + _invalidTypeMsg(slice(inj.path, 0, -1), S_list, typify(inj.dparent), inj.dparent, "V0230") table.insert(inj.errs, msg) inj.keyI = size(parent) return inj.dparent @@ -2662,13 +2710,11 @@ local function validate_CHILD(inj) return NONE end - ---------------------------------------------------------- -- Forward declaration for validate to resolve lack of function hoisting ---------------------------------------------------------- local validate - -- Match at least one of the specified shapes. -- Syntax: ['`$ONE`', alt0, alt1, ...] local function validate_ONE(inj, _val, _ref, store) @@ -2677,9 +2723,12 @@ local function validate_ONE(inj, _val, _ref, store) -- Only operate in val mode, since parent is a list. if M_VAL == mode then if not islist(parent) or 0 ~= keyI then - table.insert(inj.errs, - 'The $ONE validator at field ' .. pathify(inj.path, 1, 1) .. - ' must be the first element of an array.') + table.insert( + inj.errs, + "The $ONE validator at field " + .. pathify(inj.path, 1, 1) + .. " must be the first element of an array." + ) return end @@ -2693,9 +2742,12 @@ local function validate_ONE(inj, _val, _ref, store) local tvals = slice(parent, 1) if 0 == size(tvals) then - table.insert(inj.errs, - 'The $ONE validator at field ' .. pathify(inj.path, 1, 1) .. - ' must have at least one argument.') + table.insert( + inj.errs, + "The $ONE validator at field " + .. pathify(inj.path, 1, 1) + .. " must have at least one argument." + ) return end @@ -2726,19 +2778,24 @@ local function validate_ONE(inj, _val, _ref, store) for _, v in ipairs(tvals) do table.insert(valdesc, stringify(v)) end - local valdesc_str = table.concat(valdesc, ', ') - valdesc_str = valdesc_str:gsub('`%$([A-Z]+)`', function(p1) + local valdesc_str = table.concat(valdesc, ", ") + valdesc_str = valdesc_str:gsub("`%$([A-Z]+)`", function(p1) return string.lower(p1) end) - table.insert(inj.errs, - _invalidTypeMsg(inj.path, - (1 < size(tvals) and 'one of ' or '') .. valdesc_str, typify(inj.dparent), - inj.dparent, 'V0210')) + table.insert( + inj.errs, + _invalidTypeMsg( + inj.path, + (1 < size(tvals) and "one of " or "") .. valdesc_str, + typify(inj.dparent), + inj.dparent, + "V0210" + ) + ) end end - -- Match exactly one of the specified values. -- Syntax: ['`$EXACT`', val1, val2, ...] local function validate_EXACT(inj) @@ -2747,9 +2804,12 @@ local function validate_EXACT(inj) -- Only operate in val mode, since parent is a list. if M_VAL == mode then if not islist(parent) or 0 ~= keyI then - table.insert(inj.errs, 'The $EXACT validator at field ' .. - pathify(inj.path, 1, 1) .. - ' must be the first element of an array.') + table.insert( + inj.errs, + "The $EXACT validator at field " + .. pathify(inj.path, 1, 1) + .. " must be the first element of an array." + ) return end @@ -2763,9 +2823,12 @@ local function validate_EXACT(inj) local tvals = slice(parent, 1) if 0 == size(tvals) then - table.insert(inj.errs, 'The $EXACT validator at field ' .. - pathify(inj.path, 1, 1) .. - ' must have at least one argument.') + table.insert( + inj.errs, + "The $EXACT validator at field " + .. pathify(inj.path, 1, 1) + .. " must have at least one argument." + ) return end @@ -2791,19 +2854,26 @@ local function validate_EXACT(inj) for _, v in ipairs(tvals) do table.insert(valdesc, stringify(v)) end - local valdesc_str = table.concat(valdesc, ', ') - - table.insert(inj.errs, _invalidTypeMsg( - inj.path, - (1 < size(inj.path) and '' or 'value ') .. - 'exactly equal to ' .. (1 == size(tvals) and '' or 'one of ') .. valdesc_str, - typify(inj.dparent), inj.dparent, 'V0110')) + local valdesc_str = table.concat(valdesc, ", ") + + table.insert( + inj.errs, + _invalidTypeMsg( + inj.path, + (1 < size(inj.path) and "" or "value ") + .. "exactly equal to " + .. (1 == size(tvals) and "" or "one of ") + .. valdesc_str, + typify(inj.dparent), + inj.dparent, + "V0110" + ) + ) else delprop(parent, key) end end - -- This is the "modify" argument to inject. Use this to perform -- generic validation. Runs *after* any special commands. _validation = function(pval, key, parent, inj) @@ -2836,13 +2906,13 @@ _validation = function(pval, key, parent, inj) -- Type mismatch. if ptype ~= ctype and NONE ~= pval then - table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010')) + table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0010")) return end if ismap(cval) then if not ismap(pval) then - table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020')) + table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0020")) return end @@ -2850,7 +2920,7 @@ _validation = function(pval, key, parent, inj) local pkeys = keysof(pval) -- Empty spec object {} means object can be open (any keys). - if 0 < size(pkeys) and true ~= getprop(pval, '`$OPEN`') then + if 0 < size(pkeys) and true ~= getprop(pval, "`$OPEN`") then local badkeys = {} for _, ckey in ipairs(ckeys) do @@ -2861,27 +2931,30 @@ _validation = function(pval, key, parent, inj) -- Closed object, so reject extra keys not in shape. if 0 < size(badkeys) then - local msg = - 'Unexpected keys at field ' .. pathify(inj.path, 1) .. S_VIZ .. table.concat(badkeys, ', ') + local msg = "Unexpected keys at field " + .. pathify(inj.path, 1) + .. S_VIZ + .. table.concat(badkeys, ", ") table.insert(inj.errs, msg) end else -- Object is open, so merge in extra keys. merge({ pval, cval }) if isnode(pval) then - delprop(pval, '`$OPEN`') + delprop(pval, "`$OPEN`") end end elseif islist(cval) then if not islist(pval) then - table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030')) + table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0030")) end elseif exact then if cval ~= pval then - local pathmsg = 1 < size(inj.path) - and ('at field ' .. pathify(inj.path, 1) .. S_VIZ) or S_MT - table.insert(inj.errs, 'Value ' .. pathmsg .. tostring(cval) .. - ' should equal ' .. tostring(pval) .. '.') + local pathmsg = 1 < size(inj.path) and ("at field " .. pathify(inj.path, 1) .. S_VIZ) or S_MT + table.insert( + inj.errs, + "Value " .. pathmsg .. tostring(cval) .. " should equal " .. tostring(pval) .. "." + ) end else -- Spec value was a default, copy over data @@ -2889,7 +2962,6 @@ _validation = function(pval, key, parent, inj) end end - -- Validate a data structure against a shape specification. The shape -- specification follows the "by example" principle. Plain data in -- the shape is treated as default values that also specify the @@ -2946,7 +3018,7 @@ validate = function(data, spec, injdef) -- A special top level value to collect errors. { ["$ERRS"] = errs, - } + }, }, 1) local meta = (injdef and injdef.meta) or {} @@ -2963,17 +3035,15 @@ validate = function(data, spec, injdef) local generr = (0 < size(errs) and not collect) if generr then - error(table.concat(errs, ' | ')) + error(table.concat(errs, " | ")) end return out end - -- Select query operators -- ====================== - local function select_AND(inj, _val, _ref, store) if M_KEYPRE == inj.mode then local terms = getprop(inj.parent, inj.key) @@ -2994,8 +3064,10 @@ local function select_AND(inj, _val, _ref, store) }) if 0 ~= size(terrs) then - table.insert(inj.errs, - 'AND:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(terms)) + table.insert( + inj.errs, + "AND:" .. pathify(ppath) .. S_VIZ .. stringify(point) .. " fail:" .. stringify(terms) + ) end end @@ -3005,7 +3077,6 @@ local function select_AND(inj, _val, _ref, store) end end - local function select_OR(inj, _val, _ref, store) if M_KEYPRE == inj.mode then local terms = getprop(inj.parent, inj.key) @@ -3034,12 +3105,13 @@ local function select_OR(inj, _val, _ref, store) end end - table.insert(inj.errs, - 'OR:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(terms)) + table.insert( + inj.errs, + "OR:" .. pathify(ppath) .. S_VIZ .. stringify(point) .. " fail:" .. stringify(terms) + ) end end - local function select_NOT(inj, _val, _ref, store) if M_KEYPRE == inj.mode then local term = getprop(inj.parent, inj.key) @@ -3059,8 +3131,10 @@ local function select_NOT(inj, _val, _ref, store) }) if 0 == size(terrs) then - table.insert(inj.errs, - 'NOT:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(term)) + table.insert( + inj.errs, + "NOT:" .. pathify(ppath) .. S_VIZ .. stringify(point) .. " fail:" .. stringify(term) + ) end local gkey = getelem(inj.path, -2) @@ -3069,7 +3143,6 @@ local function select_NOT(inj, _val, _ref, store) end end - local function select_CMP(inj, _val, ref, store) if M_KEYPRE == inj.mode then local term = getprop(inj.parent, inj.key) @@ -3080,15 +3153,15 @@ local function select_CMP(inj, _val, ref, store) local pass = false - if '$GT' == ref and point > term then + if "$GT" == ref and point > term then pass = true - elseif '$LT' == ref and point < term then + elseif "$LT" == ref and point < term then pass = true - elseif '$GTE' == ref and point >= term then + elseif "$GTE" == ref and point >= term then pass = true - elseif '$LTE' == ref and point <= term then + elseif "$LTE" == ref and point <= term then pass = true - elseif '$LIKE' == ref and stringify(point):match(term) then + elseif "$LIKE" == ref and stringify(point):match(term) then pass = true end @@ -3096,15 +3169,23 @@ local function select_CMP(inj, _val, ref, store) local gp = getelem(inj.nodes, -2) setprop(gp, gkey, point) else - table.insert(inj.errs, 'CMP: ' .. pathify(ppath) .. S_VIZ .. stringify(point) .. - ' fail:' .. ref .. ' ' .. stringify(term)) + table.insert( + inj.errs, + "CMP: " + .. pathify(ppath) + .. S_VIZ + .. stringify(point) + .. " fail:" + .. ref + .. " " + .. stringify(term) + ) end end return NONE end - -- Select children matching a query. local function select_fn(children, query) if not isnode(children) then @@ -3137,14 +3218,14 @@ local function select_fn(children, query) ["$GTE"] = select_CMP, ["$LTE"] = select_CMP, ["$LIKE"] = select_CMP, - } + }, } local q = clone(query) walk(q, function(_k, v) if ismap(v) then - setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true)) + setprop(v, "`$OPEN`", getprop(v, "`$OPEN`", true)) end return v end) @@ -3162,25 +3243,25 @@ local function select_fn(children, query) return results end - -- Internal utilities -- ================== - -- Build a type validation error message. _invalidTypeMsg = function(path, needtype, vt, v, _whence) - local vs = (v == nil or v == S_null) and 'no value' or stringify(v) + local vs = (v == nil or v == S_null) and "no value" or stringify(v) local vtname = type(vt) == S_number and typename(vt) or tostring(vt) - local msg = 'Expected ' .. (1 < #path and ('field ' .. pathify(path, 1) - .. ' to be ') or '') .. needtype .. ', but found ' .. ((v ~= nil and v ~= S_null) - and (vtname .. S_VIZ) or '') .. vs + local msg = "Expected " + .. (1 < #path and ("field " .. pathify(path, 1) .. " to be ") or "") + .. needtype + .. ", but found " + .. ((v ~= nil and v ~= S_null) and (vtname .. S_VIZ) or "") + .. vs - msg = msg .. '.' + msg = msg .. "." return msg end - -- Default inject handler for transforms. _injecthandler = function(inj, val, ref, store) local out = val @@ -3198,10 +3279,9 @@ _injecthandler = function(inj, val, ref, store) return out end - -- Validate handler - intercepts meta paths for validation. _validatehandler = function(inj, val, ref, store) - local out = val + local out -- Check for meta path syntax: field$=value or field$~value local m = ref:match("^([^$]+)%$([=~])(.+)$") @@ -3209,7 +3289,7 @@ _validatehandler = function(inj, val, ref, store) if ismetapath then local eq = ref:match("^[^$]+%$(.)") -- '=' or '~' - if '=' == eq then + if "=" == eq then inj:setval({ S_BEXACT, val }) else inj:setval(val) @@ -3224,7 +3304,6 @@ _validatehandler = function(inj, val, ref, store) return out end - -- Inject store values into a string. _injectstr = function(val, store, inj) -- Can't inject into non-strings @@ -3232,7 +3311,7 @@ _injectstr = function(val, store, inj) return S_MT end - local out = val + local out -- Full value wrapped in backticks -- R_INJECTION_FULL: /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ @@ -3275,11 +3354,13 @@ _injectstr = function(val, store, inj) return S_MT elseif type(found) == S_string then return found - elseif type(found) == 'table' then + elseif type(found) == "table" then local dkjson = require("dkjson") local ok, result = pcall(dkjson.encode, found) - if ok and result then return result end - return islist(found) and '[...]' or '{...}' + if ok and result then + return result + end + return islist(found) and "[...]" or "{...}" else return tostring(found) end @@ -3295,7 +3376,6 @@ _injectstr = function(val, store, inj) return out end - -- Define the StructUtility "class" local StructUtility = { clone = clone, diff --git a/lua/test/client_test.lua b/lua/test/client_test.lua index c11de09a..da5e45f9 100644 --- a/lua/test/client_test.lua +++ b/lua/test/client_test.lua @@ -7,14 +7,13 @@ local SDK = require("sdk").SDK local TEST_JSON_FILE = "../build/test/test.json" -describe('client', function() +describe("client", function() local runner = makeRunner(TEST_JSON_FILE, SDK:test()) - local runnerCheck = runner('check') - local spec, runset, subject = runnerCheck.spec, runnerCheck.runset, - runnerCheck.subject + local runnerCheck = runner("check") + local spec, runset, subject = runnerCheck.spec, runnerCheck.runset, runnerCheck.subject - test('client-check-basic', function() + test("client-check-basic", function() runset(spec.basic, subject) end) end) diff --git a/lua/test/runner.lua b/lua/test/runner.lua index fc1793f7..53a8f7bd 100644 --- a/lua/test/runner.lua +++ b/lua/test/runner.lua @@ -2,14 +2,16 @@ local json = require("dkjson") local lfs = require("lfs") local luassert = require("luassert") - -local NULLMARK = "__NULL__" -- Value is JSON null -local UNDEFMARK = "__UNDEF__" -- Value is not present (thus undefined) +local NULLMARK = "__NULL__" -- Value is JSON null +local UNDEFMARK = "__UNDEF__" -- Value is not present (thus undefined) local EXISTSMARK = "__EXISTS__" -- Value exists (not undefined) -- Unique sentinel for JSON null (distinguishes from the literal string "null") -local JSON_NULL = setmetatable({}, { __tostring = function() return "null" end }) - +local JSON_NULL = setmetatable({}, { + __tostring = function() + return "null" + end, +}) ---------------------------------------------------------- -- Utility Functions @@ -28,7 +30,6 @@ local function readFileSync(path) return content end - -- Join path segments with forward slashes -- @param ... (string) Path segments to join -- @return (string) Joined path @@ -36,14 +37,12 @@ local function join(...) return table.concat({ ... }, "/") end - -- Assert failure with message -- @param msg (string) Failure message local function fail(msg) luassert(false, msg) end - -- Deep equality check between two values -- @param actual (any) The actual value -- @param expected (any) The expected value @@ -67,8 +66,6 @@ local handleError local match local matchval - - -- Creates a runner function that can be used to run tests -- @param testfile (string) The path to the test file -- @param client (table) The client instance to use @@ -143,7 +140,7 @@ local function makeRunner(testfile, client) runset = runset, runsetflags = runsetflags, subject = subject, - client = client + client = client, } return runpack @@ -152,22 +149,16 @@ local function makeRunner(testfile, client) return runner end - - -- Resolve the test specification from a file -- @param name (string) The name of the test specification -- @param testfile (string) The path to the test file -- @return (table) The resolved test specification resolveSpec = function(name, testfile) - local alltests = json.decode(readFileSync(join(lfs.currentdir(), testfile)), - 1, JSON_NULL) - local spec = - (alltests.primary and alltests.primary[name]) or (alltests[name]) or - alltests + local alltests = json.decode(readFileSync(join(lfs.currentdir(), testfile)), 1, JSON_NULL) + local spec = (alltests.primary and alltests.primary[name]) or alltests[name] or alltests return spec end - -- Resolve client instances based on specification -- @param spec (table) The test specification -- @param store (table) Store with configuration values @@ -191,7 +182,6 @@ resolveClients = function(client, spec, store, structUtils) return clients end - -- Resolve the test subject function -- @param name (string) The name of the subject to resolve -- @param container (table) The container object (Utility) @@ -201,7 +191,6 @@ resolveSubject = function(name, container) return subject end - -- Resolve test flags with defaults -- @param flags (table) Input flags -- @return (table) Resolved flags with defaults applied @@ -217,7 +206,6 @@ resolveFlags = function(flags) return flags end - -- Prepare a test entry with the given flags -- @param entry (table) The test entry -- @param flags (table) Processing flags @@ -227,7 +215,6 @@ resolveEntry = function(entry, flags) return entry end - -- Check the result of a test against expectations -- @param entry (table) The test entry -- @param args (table) The test arguments @@ -238,7 +225,7 @@ checkResult = function(entry, args, res, structUtils) -- If expected error but none thrown, fail if entry.err then - fail('Expected error did not occur: ' .. structUtils.stringify(entry.err)) + fail("Expected error did not occur: " .. structUtils.stringify(entry.err)) return end @@ -248,7 +235,7 @@ checkResult = function(entry, args, res, structUtils) ["in"] = entry["in"], args = args, out = entry.res, - ctx = entry.ctx + ctx = entry.ctx, } match(entry.match, result, structUtils) matched = true @@ -277,7 +264,6 @@ checkResult = function(entry, args, res, structUtils) end end - -- Handle errors during test execution -- @param entry (table) The test entry -- @param err (any) The error that occurred @@ -293,35 +279,29 @@ handleError = function(entry, err, structUtils) if entry.match then -- Process the error with fixJSON before matching local processed_err = fixJSON(err, { null = true }) - match( - entry.match, - { - ["in"] = entry["in"], - out = entry.res, - ctx = entry.ctx, - err = processed_err - }, - structUtils - ) + match(entry.match, { + ["in"] = entry["in"], + out = entry.res, + ctx = entry.ctx, + err = processed_err, + }, structUtils) end return end - fail("ERROR MATCH: [" .. structUtils.stringify(entry_err) .. "] <=> [" .. - err_message .. "]") + fail("ERROR MATCH: [" .. structUtils.stringify(entry_err) .. "] <=> [" .. err_message .. "]") else -- fail((err.stack or err_message) .. "\n\nENTRY: " .. inspect(entry)) fail((err.stack or err_message)) end end - -- Prepare test arguments -- @param entry (table) The test entry -- @param testpack (table) The test pack with client and utility -- @return (table) Array of arguments for the test resolveArgs = function(entry, testpack, utility, structUtils) - local args = {} + local args if entry.ctx then args = { entry.ctx } @@ -347,7 +327,6 @@ resolveArgs = function(entry, testpack, utility, structUtils) return args end - -- Resolve the test pack with client and subject -- @param name (string) The name of the test -- @param entry (table) The test entry @@ -360,7 +339,7 @@ resolveTestPack = function(name, entry, subject, client, clients) name = name, client = client, subject = subject, - utility = client:utility() + utility = client:utility(), } if entry.client then @@ -372,7 +351,6 @@ resolveTestPack = function(name, entry, subject, client, clients) return testpack end - -- Match a check structure against a base structure -- @param check (table) The check structure with patterns -- @param base (table) The base structure to validate against @@ -401,9 +379,15 @@ match = function(check, base, structUtils) end if not matchval(val, baseval, structUtils) then - fail("MATCH: " .. table.concat(path, ".") .. ": [" .. - structUtils.stringify(val) .. "] <=> [" .. - structUtils.stringify(baseval) .. "]") + fail( + "MATCH: " + .. table.concat(path, ".") + .. ": [" + .. structUtils.stringify(val) + .. "] <=> [" + .. structUtils.stringify(baseval) + .. "]" + ) end end @@ -411,7 +395,6 @@ match = function(check, base, structUtils) end) end - -- Check if a test value matches a base value according to defined rules -- @param check (any) The test pattern or value to check -- @param base (any) The base value to check against @@ -429,15 +412,23 @@ matchval = function(check, base, structUtils) if rem then -- Convert JS RegExp to Lua pattern when possible -- This is a simplification and might need adjustments for complex patterns - local lua_pattern = rem:gsub("%%", "%%%%"):gsub("%.", "%%."):gsub("%+", - "%%+"):gsub("%-", "%%-"):gsub("%*", "%%*"):gsub("%?", "%%?"):gsub( - "%[", "%%["):gsub("%]", "%%]"):gsub("%^", "%%^"):gsub("%$", "%%$") - :gsub("%(", "%%("):gsub("%)", "%%)") + local lua_pattern = rem + :gsub("%%", "%%%%") + :gsub("%.", "%%.") + :gsub("%+", "%%+") + :gsub("%-", "%%-") + :gsub("%*", "%%*") + :gsub("%?", "%%?") + :gsub("%[", "%%[") + :gsub("%]", "%%]") + :gsub("%^", "%%^") + :gsub("%$", "%%$") + :gsub("%(", "%%(") + :gsub("%)", "%%)") pass = basestr:match(lua_pattern) ~= nil else -- Convert both strings to lowercase and check if one contains the other - pass = basestr:lower():find(structUtils.stringify(check):lower(), 1, - true) ~= nil + pass = basestr:lower():find(structUtils.stringify(check):lower(), 1, true) ~= nil end elseif type(check) == "function" then pass = true @@ -447,7 +438,6 @@ matchval = function(check, base, structUtils) return pass end - -- Transform null values in JSON data according to flags. -- dkjson decodes JSON null as the Lua string "null". -- When flags.null is true, convert "null" to NULLMARK ("__NULL__"). @@ -466,15 +456,25 @@ fixJSON = function(val, flags) end local function isarray(t) - if type(t) ~= "table" then return false end - if t == JSON_NULL then return false end + if type(t) ~= "table" then + return false + end + if t == JSON_NULL then + return false + end local mt = getmetatable(t) - if mt and mt.__jsontype == "array" then return true end + if mt and mt.__jsontype == "array" then + return true + end local count = 0 local max = 0 for k in pairs(t) do - if type(k) ~= "number" then return false end - if k > max then max = k end + if type(k) ~= "number" then + return false + end + if k > max then + max = k + end count = count + 1 end return count > 0 and max == count @@ -532,7 +532,6 @@ fixJSON = function(val, flags) return replacer(val) end - -- Process null marker values -- @param val (any) The value to check -- @param key (any) The key in the parent @@ -545,12 +544,11 @@ local function nullModifier(val, key, parent) end end - -- Module exports return { NULLMARK = NULLMARK, EXISTSMARK = EXISTSMARK, JSON_NULL = JSON_NULL, nullModifier = nullModifier, - makeRunner = makeRunner + makeRunner = makeRunner, } diff --git a/lua/test/sdk.lua b/lua/test/sdk.lua index 8fd40d34..806377cc 100644 --- a/lua/test/sdk.lua +++ b/lua/test/sdk.lua @@ -5,14 +5,14 @@ local SDK = {} SDK.__index = SDK -- Constructor -function SDK:new(opts) +function SDK.new(class, initOpts) local _opts local _utility local instance = {} - setmetatable(instance, self) + setmetatable(instance, class) - _opts = opts or {} + _opts = initOpts or {} _utility = { struct = StructUtility:new(), contextify = function(ctxmap) @@ -20,13 +20,12 @@ function SDK:new(opts) end, check = function(ctx) return { - zed = "ZED" .. - (_opts == nil and "" or - (_opts.foo == nil and "" or _opts.foo)) .. - "_" .. - (ctx.meta and ctx.meta.bar or "0") + zed = "ZED" + .. (_opts == nil and "" or (_opts.foo == nil and "" or _opts.foo)) + .. "_" + .. (ctx.meta and ctx.meta.bar or "0"), } - end + end, } function instance:tester(opts) @@ -46,5 +45,5 @@ function SDK:test(opts) end return { - SDK = SDK + SDK = SDK, } diff --git a/lua/test/struct_test.lua b/lua/test/struct_test.lua index 6d84408d..39b56ea7 100755 --- a/lua/test/struct_test.lua +++ b/lua/test/struct_test.lua @@ -3,8 +3,8 @@ package.path = package.path .. ";./test/?.lua" local assert = require("luassert") local runnerModule = require("runner") -local makeRunner, nullModifier, NULLMARK, JSON_NULL = runnerModule.makeRunner, - runnerModule.nullModifier, runnerModule.NULLMARK, runnerModule.JSON_NULL +local makeRunner, nullModifier, NULLMARK, JSON_NULL = + runnerModule.makeRunner, runnerModule.nullModifier, runnerModule.NULLMARK, runnerModule.JSON_NULL local SDK = require("sdk").SDK @@ -20,7 +20,7 @@ local TEST_JSON_FILE = "../build/test/test.json" local function array(...) local t = { ... } return setmetatable(t, { - __jsontype = "array" + __jsontype = "array", }) end @@ -30,7 +30,7 @@ end local function object(t) t = t or {} return setmetatable(t, { - __jsontype = "object" + __jsontype = "object", }) end @@ -41,9 +41,9 @@ end describe("struct", function() local runner = makeRunner(TEST_JSON_FILE, SDK:test()) - local runnerStruct = runner('struct') - local spec, runset, runsetflags, client = runnerStruct.spec, - runnerStruct.runset, runnerStruct.runsetflags, runnerStruct.client + local runnerStruct = runner("struct") + local spec, runset, runsetflags, client = + runnerStruct.spec, runnerStruct.runset, runnerStruct.runsetflags, runnerStruct.client local struct_util = client:utility().struct -- Extract test specifications for different function groups @@ -152,38 +152,32 @@ describe("struct", function() runset(minorSpec.isnode, isnode) end) - test("minor-ismap", function() runset(minorSpec.ismap, ismap) end) - test("minor-islist", function() runset(minorSpec.islist, islist) end) - test("minor-iskey", function() runsetflags(minorSpec.iskey, { - null = false + null = false, }, iskey) end) - test("minor-strkey", function() runsetflags(minorSpec.strkey, { - null = false + null = false, }, strkey) end) - test("minor-isempty", function() runsetflags(minorSpec.isempty, { - null = false + null = false, }, isempty) end) - test("minor-isfunc", function() runset(minorSpec.isfunc, isfunc) @@ -193,15 +187,17 @@ describe("struct", function() end assert.equal(isfunc(f0), true) - assert.equal(isfunc(function() - return nil - end), true) + assert.equal( + isfunc(function() + return nil + end), + true + ) end) - test("minor-clone", function() runsetflags(minorSpec.clone, { - null = false + null = false, }, clone) -- Additional function cloning test @@ -210,36 +206,36 @@ describe("struct", function() end local original = { - a = f0 + a = f0, } local copied = clone(original) assert.are.same(original, copied) end) - test("minor-filter", function() local checkmap = { - gt3 = function(n) return n[2] > 3 end, - lt3 = function(n) return n[2] < 3 end, + gt3 = function(n) + return n[2] > 3 + end, + lt3 = function(n) + return n[2] < 3 + end, } runset(minorSpec.filter, function(vin) return filter(vin.val, checkmap[vin.check]) end) end) - test("minor-flatten", function() runset(minorSpec.flatten, function(vin) return flatten(vin.val, vin.depth) end) end) - test("minor-escre", function() runset(minorSpec.escre, escre) end) - test("minor-escurl", function() runset(minorSpec.escurl, function(vin) -- Ensure spaces are properly replaced like in the Go implementation @@ -247,7 +243,6 @@ describe("struct", function() end) end) - test("minor-stringify", function() runset(minorSpec.stringify, function(vin) if NULLMARK == vin.val then @@ -258,10 +253,9 @@ describe("struct", function() end) end) - - test('minor-pathify', function() + test("minor-pathify", function() runsetflags(minorSpec.pathify, { - null = true + null = true, }, function(vin) local path if NULLMARK == vin.path then @@ -270,28 +264,25 @@ describe("struct", function() path = vin.path end - local pathstr = pathify(path, vin.from):gsub('__NULL__%.', '') - pathstr = NULLMARK == vin.path and pathstr:gsub('>', ':null>') or pathstr + local pathstr = pathify(path, vin.from):gsub("__NULL__%.", "") + pathstr = NULLMARK == vin.path and pathstr:gsub(">", ":null>") or pathstr return pathstr end) end) - test("minor-items", function() runset(minorSpec.items, items) end) - test("minor-edge-items", function() - local a0 = {11, 22, 33} + local a0 = { 11, 22, 33 } a0.x = 1 - assert.same(items(a0), {{'0', 11}, {'1', 22}, {'2', 33}}) + assert.same(items(a0), { { "0", 11 }, { "1", 22 }, { "2", 33 } }) end) - test("minor-getprop", function() runsetflags(minorSpec.getprop, { - null = false + null = false, }, function(vin) if vin.alt == nil then return getprop(vin.val, vin.key) @@ -301,7 +292,6 @@ describe("struct", function() end) end) - test("minor-edge-getprop", function() local strarr = { "a", "b", "c", "d", "e" } assert.same(getprop(strarr, 2), "c") @@ -312,14 +302,12 @@ describe("struct", function() assert.same(getprop(intarr, "2"), 5) end) - test("minor-setprop", function() runset(minorSpec.setprop, function(vin) return setprop(vin.parent, vin.key, vin.val) end) end) - test("minor-edge-setprop", function() local strarr0 = { "a", "b", "c", "d", "e" } local strarr1 = { "a", "b", "c", "d", "e" } @@ -332,41 +320,36 @@ describe("struct", function() assert.same({ 2, 3, 555, 7, 11 }, setprop(intarr1, "2", 555)) end) - test("minor-haskey", function() runsetflags(minorSpec.haskey, { - null = false + null = false, }, function(vin) return haskey(vin.src, vin.key) end) end) - test("minor-keysof", function() runset(minorSpec.keysof, keysof) end) test("minor-edge-keysof", function() - local a0 = {11, 22, 33} + local a0 = { 11, 22, 33 } a0.x = 1 - assert.same(keysof(a0), {'0', '1', '2'}) + assert.same(keysof(a0), { "0", "1", "2" }) end) - test("minor-join", function() runsetflags(minorSpec.join, { - null = false + null = false, }, function(vin) return join(vin.val, vin.sep, vin.url) end) end) - test("minor-typename", function() runset(minorSpec.typename, typename) end) - test("minor-typify", function() -- Filter out JSON null 'in' entries: Lua typify(nil) returns T_null, -- but TS typify(null) returns T_scalar|T_null. @@ -378,14 +361,13 @@ describe("struct", function() end end runsetflags(filtered, { - null = false + null = false, }, typify) end) - test("minor-getelem", function() runsetflags(minorSpec.getelem, { - null = false + null = false, }, function(vin) if vin.alt == nil then return getelem(vin.val, vin.key) @@ -395,48 +377,42 @@ describe("struct", function() end) end) - test("minor-size", function() runsetflags(minorSpec.size, { - null = false + null = false, }, size) end) - test("minor-slice", function() runsetflags(minorSpec.slice, { - null = false + null = false, }, function(vin) - return slice(vin.val, vin.start, vin['end']) + return slice(vin.val, vin.start, vin["end"]) end) end) - test("minor-pad", function() runsetflags(minorSpec.pad, { - null = false + null = false, }, function(vin) return pad(vin.val, vin.pad, vin.char) end) end) - test("minor-setpath", function() runsetflags(minorSpec.setpath, { - null = false + null = false, }, function(vin) return setpath(vin.store, vin.path, vin.val) end) end) - test("minor-delprop", function() runset(minorSpec.delprop, function(vin) return delprop(vin.parent, vin.key) end) end) - test("minor-edge-delprop", function() local strarr0 = { "a", "b", "c", "d", "e" } local strarr1 = { "a", "b", "c", "d", "e" } @@ -449,16 +425,14 @@ describe("struct", function() assert.same({ 2, 3, 7, 11 }, delprop(intarr1, "2")) end) - test("minor-jsonify", function() runsetflags(minorSpec.jsonify, { - null = false + null = false, }, function(vin) return jsonify(vin.val, vin.flags) end) end) - ---------------------------------------------------------- -- Walk Tests ---------------------------------------------------------- @@ -467,8 +441,14 @@ describe("struct", function() local test = clone(walkSpec.log) local function walklog(key, val, parent, path) - return "k=" .. stringify(key) .. ", v=" .. stringify(val) .. ", p=" .. - stringify(parent) .. ", t=" .. pathify(path) + return "k=" + .. stringify(key) + .. ", v=" + .. stringify(val) + .. ", p=" + .. stringify(parent) + .. ", t=" + .. pathify(path) end -- Test before callback @@ -499,7 +479,6 @@ describe("struct", function() assert.same(logba, test.out.both) end) - test("walk-basic", function() local function walkpath(_key, val, _parent, path) if type(val) == "string" then @@ -513,11 +492,10 @@ describe("struct", function() end) end) - test("walk-depth", function() runsetflags(walkSpec.depth, { null = false }, function(vin) local top = nil - local cur = nil + local cur local function copy(key, val, _parent, _path) if key == nil or isnode(val) then local child = islist(val) and array() or object() @@ -538,7 +516,6 @@ describe("struct", function() end) end) - test("walk-copy", function() local cur @@ -568,32 +545,27 @@ describe("struct", function() end) end) - ---------------------------------------------------------- -- Merge Tests ---------------------------------------------------------- test("merge-basic", function() local test = clone(mergeSpec.basic) - assert.same(test.out, merge(test['in'])) + assert.same(test.out, merge(test["in"])) end) - test("merge-cases", function() runset(mergeSpec.cases, merge) end) - test("merge-array", function() runset(mergeSpec.array, merge) end) - test("merge-integrity", function() runset(mergeSpec.integrity, merge) end) - test("merge-special", function() local f0 = function() return nil @@ -601,30 +573,34 @@ describe("struct", function() assert.same(f0, merge(array(f0))) assert.same(f0, merge(array(nil, f0))) - assert.same(object({ - a = f0 - }), merge(array(object({ - a = f0 - })))) - assert.same(object({ - a = object({ - b = f0 - }) - }), merge(array(object({ - a = object({ - b = f0 - }) - })))) + assert.same( + object({ + a = f0, + }), + merge(array(object({ + a = f0, + }))) + ) + assert.same( + object({ + a = object({ + b = f0, + }), + }), + merge(array(object({ + a = object({ + b = f0, + }), + }))) + ) end) - test("merge-depth", function() runset(mergeSpec.depth, function(vin) return merge(vin.val, vin.depth) end) end) - ---------------------------------------------------------- -- GetPath Tests ---------------------------------------------------------- @@ -635,14 +611,13 @@ describe("struct", function() end) end) - test("getpath-relative", function() runset(getpathSpec.relative, function(vin) local dpath = vin.dpath - if type(dpath) == 'string' then + if type(dpath) == "string" then -- Split dpath string into array local parts = {} - for part in dpath:gmatch('[^%.]+') do + for part in dpath:gmatch("[^%.]+") do table.insert(parts, part) end dpath = parts @@ -651,42 +626,40 @@ describe("struct", function() end) end) - test("getpath-special", function() runset(spec.getpath.special, function(vin) return getpath(vin.store, vin.path, vin.inj) end) end) - test("getpath-handler", function() runset(spec.getpath.handler, function(vin) return getpath( { ["$TOP"] = vin.store, - ["$FOO"] = function() return 'foo' end, + ["$FOO"] = function() + return "foo" + end, }, vin.path, { handler = function(_inj, val, _cur, _ref) return val() - end + end, } ) end) end) - ---------------------------------------------------------- -- Inject Tests ---------------------------------------------------------- test("inject-basic", function() local test = clone(injectSpec.basic) - assert.same(test.out, inject(test['in'].val, test['in'].store)) + assert.same(test.out, inject(test["in"].val, test["in"].store)) end) - test("inject-string", function() runset(injectSpec.string, function(vin) local result = inject(vin.val, vin.store, { modify = nullModifier }) @@ -694,74 +667,63 @@ describe("struct", function() end) end) - test("inject-deep", function() runset(injectSpec.deep, function(vin) return inject(vin.val, vin.store) end) end) - ---------------------------------------------------------- -- Transform Tests ---------------------------------------------------------- test("transform-basic", function() local test = clone(transformSpec.basic) - assert.same(transform(test['in'].data, test['in'].spec), - test.out) + assert.same(transform(test["in"].data, test["in"].spec), test.out) end) - test("transform-paths", function() runset(transformSpec.paths, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-cmds", function() runset(transformSpec.cmds, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-each", function() runset(transformSpec.each, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-pack", function() runset(transformSpec.pack, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-ref", function() runset(transformSpec.ref, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-format", function() runsetflags(transformSpec.format, { null = false }, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-apply", function() runset(transformSpec.apply, function(vin) return transform(vin.data, vin.spec) end) end) - test("transform-modify", function() runset(transformSpec.modify, function(vin) return transform(vin.data, vin.spec, { @@ -769,71 +731,82 @@ describe("struct", function() -- Modify string values by adding '@' prefix if key ~= nil and parent ~= nil and type(val) == "string" then parent[key] = "@" .. val - val = parent[key] end - end + end, }) end) end) - test("transform-extra", function() -- Test advanced transform functionality - assert.same(transform({ - a = 1 - }, { - x = '`a`', - b = '`$COPY`', - c = '`$UPPER`' - }, { - extra = { + assert.same( + transform({ + a = 1, + }, { + x = "`a`", + b = "`$COPY`", + c = "`$UPPER`", + }, { + extra = { + b = 2, + ["$UPPER"] = function(inj) + local path = inj.path + return ("" .. tostring(getprop(path, #path - 1))):upper() + end, + }, + }), + { + x = 1, b = 2, - ["$UPPER"] = function(inj) - local path = inj.path - return ('' .. tostring(getprop(path, #path - 1))):upper() - end + c = "C", } - }), { - x = 1, - b = 2, - c = 'C' - }) + ) end) - test("transform-funcval", function() -- Test function handling in transform local f0 = function() return 99 end - assert.same(transform({}, { - x = 1 - }), { - x = 1 - }) - assert.same(transform({}, { - x = f0 - }), { - x = f0 - }) - assert.same(transform({ - a = 1 - }, { - x = '`a`' - }), { - x = 1 - }) - assert.same(transform({ - f0 = f0 - }, { - x = '`f0`' - }), { - x = f0 - }) + assert.same( + transform({}, { + x = 1, + }), + { + x = 1, + } + ) + assert.same( + transform({}, { + x = f0, + }), + { + x = f0, + } + ) + assert.same( + transform({ + a = 1, + }, { + x = "`a`", + }), + { + x = 1, + } + ) + assert.same( + transform({ + f0 = f0, + }, { + x = "`f0`", + }), + { + x = f0, + } + ) end) - ---------------------------------------------------------- -- Validate Tests ---------------------------------------------------------- @@ -844,42 +817,36 @@ describe("struct", function() end) end) - test("validate-child", function() runset(validateSpec.child, function(vin) return validate(vin.data, vin.spec) end) end) - test("validate-one", function() runset(validateSpec.one, function(vin) return validate(vin.data, vin.spec) end) end) - test("validate-exact", function() runset(validateSpec.exact, function(vin) return validate(vin.data, vin.spec) end) end) - test("validate-invalid", function() runsetflags(validateSpec.invalid, { null = false }, function(vin) return validate(vin.data, vin.spec) end) end) - test("validate-special", function() runset(validateSpec.special, function(vin) return validate(vin.data, vin.spec, vin.inj) end) end) - test("validate-custom", function() -- Test custom validation functions local errs = array() @@ -897,23 +864,22 @@ describe("struct", function() table.insert(path_parts, tostring(inj.path[i])) end local path_str = table.concat(path_parts, ".") - table.insert(inj.errs, "Not an integer at " .. path_str .. ": " .. - tostring(out)) + table.insert(inj.errs, "Not an integer at " .. path_str .. ": " .. tostring(out)) return nil end return out - end + end, } local shape = { - a = "`$INTEGER`" + a = "`$INTEGER`", } local out = validate({ - a = 1 + a = 1, }, shape, { extra = extra, errs = errs }) assert.same({ - a = 1 + a = 1, }, out) assert.equal(0, #errs) @@ -922,7 +888,6 @@ describe("struct", function() assert.same(array("Not an integer at a: A"), errs) end) - ---------------------------------------------------------- -- Select Tests ---------------------------------------------------------- @@ -933,21 +898,18 @@ describe("struct", function() end) end) - test("select-operators", function() runset(selectSpec.operators, function(vin) return select_fn(vin.obj, vin.query) end) end) - test("select-edge", function() runset(selectSpec.edge, function(vin) return select_fn(vin.obj, vin.query) end) end) - test("select-alts", function() runset(selectSpec.alts, function(vin) return select_fn(vin.obj, vin.query) diff --git a/lua/test/walk_bench.lua b/lua/test/walk_bench.lua index 2637847f..791a6da7 100644 --- a/lua/test/walk_bench.lua +++ b/lua/test/walk_bench.lua @@ -30,7 +30,6 @@ local function buildTree(width, depth) return out end - local function countNodes(val) if type(val) ~= "table" then return 1 @@ -42,7 +41,6 @@ local function countNodes(val) return n end - local function measure(label, tree, runs) local sink = 0 local cb = function(_k, v, _p, path) @@ -67,33 +65,38 @@ local function measure(label, tree, runs) local min = times[1] local max = times[#times] local sum = 0 - for _, t in ipairs(times) do sum = sum + t end + for _, t in ipairs(times) do + sum = sum + t + end local mean = sum / #times local nodes = countNodes(tree) local nsPerNode = (median * 1e6) / nodes - print(string.format( - "[walk-bench] %s: nodes=%d runs=%d min=%.2fms median=%.2fms mean=%.2fms max=%.2fms ns/node=%.1f sink=%d", - label, nodes, runs, min, median, mean, max, nsPerNode, sink - )) + print( + string.format( + "[walk-bench] %s: nodes=%d runs=%d min=%.2fms median=%.2fms mean=%.2fms max=%.2fms ns/node=%.1f sink=%d", + label, + nodes, + runs, + min, + median, + mean, + max, + nsPerNode, + sink + ) + ) end - -- ~299k nodes: width=8, depth=6. -local wideDeep = buildTree(8, 6) -measure("wide+deep (w=8,d=6)", wideDeep, 7) -wideDeep = nil +measure("wide+deep (w=8,d=6)", buildTree(8, 6), 7) collectgarbage("collect") -- ~1M nodes, shallow. -local wide = buildTree(1000, 2) -measure("wide (w=1000,d=2)", wide, 7) -wide = nil +measure("wide (w=1000,d=2)", buildTree(1000, 2), 7) collectgarbage("collect") -- ~2M nodes, deep. Lua's MAXDEPTH in walk is 32, so w=2,d=20 is safe. -local deep = buildTree(2, 20) -measure("deep (w=2,d=20)", deep, 5) -deep = nil +measure("deep (w=2,d=20)", buildTree(2, 20), 5) collectgarbage("collect") diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 00000000..f7a25aed --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,14 @@ +# osv-scanner ignore list. +# https://google.github.io/osv-scanner/configuration/ +# +# Every entry must carry a reason and should be revisited periodically — this +# file should only ever shrink. + +[[IgnoredVulns]] +id = "GO-2025-3750" +# This advisory is against the Go *standard library*; osv-scanner reports it +# because `go/go.mod` declares `go 1.20` (the minimum supported toolchain). +# CI and releases build with current Go (1.24+), which carries the fix. +# Action item: when support for pre-1.23.10 Go toolchains is dropped, bump the +# `go` directive instead of relying on this ignore. +reason = "go.mod's `go 1.20` is the minimum-supported toolchain; CI/releases build with patched Go (1.24+)." diff --git a/php/Makefile b/php/Makefile index 6ed5babd..983cbe30 100644 --- a/php/Makefile +++ b/php/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test bench clean reset +.PHONY: inspect build test lint clean reset audit inspect: @echo "PHP version:" @@ -13,6 +13,11 @@ build: test: vendor/bin/phpunit +# Code quality: PHP_CodeSniffer (PSR-12) + PHPStan static analysis. +lint: + vendor/bin/phpcs + vendor/bin/phpstan analyse --no-progress + bench: WALK_BENCH=1 php -d memory_limit=512M tests/WalkBench.php @@ -21,3 +26,7 @@ clean: reset: clean rm -rf vendor + +# Supply-chain: scan Composer dependencies for known vulnerabilities. +audit: + composer audit diff --git a/php/composer.json b/php/composer.json index 897e1539..53ca65d7 100644 --- a/php/composer.json +++ b/php/composer.json @@ -10,6 +10,16 @@ } ], "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^10.5.62", + "squizlabs/php_codesniffer": "^3.10", + "phpstan/phpstan": "^1.11" + }, + "scripts": { + "lint": [ + "@php vendor/bin/phpcs", + "@php vendor/bin/phpstan analyse --no-progress" + ], + "lint:fix": "@php vendor/bin/phpcbf", + "test": "@php vendor/bin/phpunit" } } diff --git a/php/composer.lock b/php/composer.lock index a0276a0e..4daecd40 100644 --- a/php/composer.lock +++ b/php/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e3d665560bd2989a574d466df96727be", + "content-hash": "fab8b36788c638c9cee5d84ebcd41aa9", "packages": [], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -57,7 +57,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -65,20 +65,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -121,9 +121,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -243,6 +243,59 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.12.33", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1", + "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-02-28T20:30:03+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "10.1.16", @@ -566,16 +619,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.45", + "version": "10.5.63", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { @@ -585,7 +638,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -596,13 +649,13 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.5", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.2", + "sebastian/exporter": "^5.1.4", "sebastian/global-state": "^6.0.2", "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", "sebastian/type": "^4.0.0", "sebastian/version": "^4.0.1" }, @@ -647,7 +700,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" }, "funding": [ { @@ -658,12 +711,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-02-06T16:08:12+00:00" + "time": "2026-01-27T05:48:37+00:00" }, { "name": "sebastian/cli-parser", @@ -835,16 +896,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.3", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { @@ -900,15 +961,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2026-01-24T09:25:16+00:00" }, { "name": "sebastian/complexity", @@ -1101,16 +1174,16 @@ }, { "name": "sebastian/exporter", - "version": "5.1.2", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { @@ -1119,7 +1192,7 @@ "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -1167,15 +1240,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { "name": "sebastian/global-state", @@ -1411,23 +1496,23 @@ }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -1462,15 +1547,28 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { "name": "sebastian/type", @@ -1581,18 +1679,97 @@ ], "time": "2023-02-07T11:34:05+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -1621,7 +1798,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -1629,15 +1806,15 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/php/phpcs.xml.dist b/php/phpcs.xml.dist new file mode 100644 index 00000000..50b1e534 --- /dev/null +++ b/php/phpcs.xml.dist @@ -0,0 +1,73 @@ + + + PSR-12 coding standard for the PHP port. + + src + tests + + vendor/* + + + + + + + + + + + + + + + + + + + src/Struct.php + + + src/Struct.php + + + + + src/Struct.php + + + + + src/Struct.php + + + + + src/Struct.php + + + + + tests/* + + + tests/* + + diff --git a/php/phpstan.neon.dist b/php/phpstan.neon.dist new file mode 100644 index 00000000..e0400217 --- /dev/null +++ b/php/phpstan.neon.dist @@ -0,0 +1,7 @@ +# PHPStan configuration for the PHP port. +# https://phpstan.org/ +parameters: + level: 5 + paths: + - src + treatPhpDocTypesAsCertain: false diff --git a/php/src/Struct.php b/php/src/Struct.php index a65cfae8..93693fbe 100644 --- a/php/src/Struct.php +++ b/php/src/Struct.php @@ -1,4 +1,5 @@ list, $start, $end - $start)); + if ($mutate) { + $val->list = array_slice($val->list, $start, $end - $start); + } else { + $val = new ListRef(array_slice($val->list, $start, $end - $start)); + } } elseif (self::islist($val)) { $val = array_slice($val, $start, $end - $start); } elseif (is_string($val)) { @@ -755,7 +759,11 @@ public static function slice(mixed $val, ?int $start = null, ?int $end = null): } } else { if ($val instanceof ListRef) { - $val = new ListRef([]); + if ($mutate) { + $val->list = []; + } else { + $val = new ListRef([]); + } } elseif (self::islist($val)) { $val = []; } elseif (is_string($val)) { @@ -776,7 +784,7 @@ public static function pad(mixed $str, ?int $padding = null, ?string $padchar = $padding = $padding ?? 44; $padchar = $padchar ?? ' '; $padchar = ($padchar . ' ')[0]; // Get first character or space as fallback - + if ($padding >= 0) { return str_pad($str, $padding, $padchar, STR_PAD_RIGHT); } else { @@ -880,7 +888,7 @@ public static function pathify(mixed $val, ?int $startin = null, ?int $endin = n $pathstr = $UNDEF; - if ($path !== $UNDEF && $start >= 0) { + if ($path !== $UNDEF) { $len = count($path); $length = max(0, $len - $end - $start); $slice = array_slice($path, $start, $length); @@ -1238,7 +1246,14 @@ private static function _walk( foreach (self::items($out) as [$childKey, $childVal]) { $pool[$childDepth][$depth] = self::S_MT . $childKey; $result = self::_walk( - $childVal, $before, $after, $md, $childKey, $out, $pool[$childDepth], $pool + $childVal, + $before, + $after, + $md, + $childKey, + $out, + $pool[$childDepth], + $pool ); if (self::ismap($out)) { if (is_object($out)) { @@ -1353,7 +1368,7 @@ public static function getpath( // An empty path (incl empty string) just finds the src (base data) if ($path === null || $store === null || ($numparts === 1 && $parts[0] === '')) { $val = $src; - } else if ($numparts > 0) { + } elseif ($numparts > 0) { // Check for $ACTIONs if ($numparts === 1) { $val = self::_getprop($store, $parts[0]); @@ -1375,16 +1390,16 @@ public static function getpath( if ($injdef && $part === '$KEY') { $part = self::_getprop($injdef, 'key'); - } else if ($injdef && str_starts_with($part, '$GET:')) { + } elseif ($injdef && str_starts_with($part, '$GET:')) { // $GET:path$ -> get store value, use as path part (string) $getpath = substr($part, 5, -1); $getval = self::getpath($src, $getpath); $part = self::stringify($getval); - } else if ($injdef && str_starts_with($part, '$REF:')) { + } elseif ($injdef && str_starts_with($part, '$REF:')) { // $REF:refpath$ -> get spec value, use as path part (string) $refpath = substr($part, 5, -1); $part = self::stringify(self::getpath(self::_getprop($store, '$SPEC'), self::slice($part, 5, -1))); - } else if ($injdef && str_starts_with($part, '$META:')) { + } elseif ($injdef && str_starts_with($part, '$META:')) { // $META:metapath$ -> get meta value, use as path part (string) $part = self::stringify(self::getpath(self::_getprop($injdef, 'meta'), substr($part, 6, -1))); } @@ -1540,7 +1555,7 @@ public static function inject( } } // Inject paths into string scalars. - else if ($valtype === 'string') { + elseif ($valtype === 'string') { $inj->mode = self::M_VAL; $val = self::_injectstr($val, $store, $inj); if (self::SKIP !== $val) { @@ -1603,10 +1618,9 @@ private static function _injectstr( // Get the extracted path reference. $out = self::getpath($store, $pathref, $inj); - } - else { + } else { // Check for injections within the string. - $out = preg_replace_callback('/`([^`]+)`/', function($matches) use ($store, $inj) { + $out = preg_replace_callback('/`([^`]+)`/', function ($matches) use ($store, $inj) { $ref = $matches[1]; if (strlen($ref) > 3) { @@ -1650,7 +1664,11 @@ public static function _injecthandler( ): mixed { $out = $val; - // Check if val is a function (command transforms) + // Check if val is a function (command transforms). + // The "undef() === $ref" guard mirrors the reference source, where the + // ref argument may be the absent-value sentinel; with PHP's string type + // hint that branch is unreachable here, hence the analyser hint. + // @phpstan-ignore-next-line identical.alwaysFalse $iscmd = self::isfunc($val) && (self::undef() === $ref || str_starts_with($ref, self::S_DS)); // Only call val function if it is a special command ($NAME format). @@ -1664,15 +1682,6 @@ public static function _injecthandler( return $out; } - private static function _injecthandler_getpath( - object $state, - mixed $val, - string $ref, - mixed $store - ): mixed { - return self::_injecthandler($state, $val, $ref, $store); - } - /** * @internal * Delete a key from a map or list. @@ -1844,7 +1853,7 @@ public static function transform_EACH( // Create clones of the child template for each value of the current source. if (self::islist($src)) { $srcArr = ($src instanceof ListRef) ? $src->list : (array) $src; - $tval = array_map(function($_) use ($child) { + $tval = array_map(function ($_) use ($child) { return self::clone($child); }, $srcArr); } elseif (self::ismap($src)) { @@ -2247,8 +2256,8 @@ public static function transform_APPLY(object $inj, mixed $_val, string $_ref, m * * @param mixed $data Source data (not mutated) * @param mixed $spec Transform spec (JSON-like) - * @param array|object|null $extra extra transforms or data - * @param callable|null $modify optional per-value hook + * @param mixed $injdef Either an injdef object ({ extra, modify, errs, meta, handler }) + * or, for backward compatibility, extra transforms/data directly. */ public static function transform( mixed $data, @@ -2259,13 +2268,15 @@ public static function transform( $extra = null; $modify = null; $errs = null; - if (is_object($injdef) && ( + if ( + is_object($injdef) && ( property_exists($injdef, 'extra') || property_exists($injdef, 'modify') || property_exists($injdef, 'errs') || property_exists($injdef, 'meta') || property_exists($injdef, 'handler') - )) { + ) + ) { // New injdef pattern: { extra, modify, errs, meta, handler } $extra = property_exists($injdef, 'extra') ? $injdef->extra : null; $modify = property_exists($injdef, 'modify') ? $injdef->modify : null; @@ -2301,7 +2312,7 @@ public static function transform( self::S_DTOP => $dataClone, '$BT' => fn() => self::S_BT, '$DS' => fn() => self::S_DS, - '$WHEN' => fn() => (new \DateTime)->format(\DateTime::ATOM), + '$WHEN' => fn() => (new \DateTime())->format(\DateTime::ATOM), '$DELETE' => [self::class, 'transform_DELETE'], '$COPY' => [self::class, 'transform_COPY'], '$KEY' => [self::class, 'transform_KEY'], @@ -2371,25 +2382,6 @@ private static function _stdClassToArray(mixed $val, int $depth = 0): mixed return $val; } - /** - * Remove unresolved $REF list entries from a list spec. - * This handles PHP's value-type arrays where in-place mutation via references doesn't propagate. - */ - private static function _cleanRefEntries(array $list): array { - $cleaned = []; - foreach ($list as $item) { - if (self::islist($item) && count($item) >= 1 && self::_getprop($item, 0) === '`$REF`') { - // This is an unresolved $REF entry - remove it - continue; - } - if (self::islist($item)) { - $item = self::_cleanRefEntries($item); - } - $cleaned[] = $item; - } - return $cleaned; - } - /** @internal */ private static function _invalidTypeMsg(array $path, string $needtype, int $vt, mixed $v): string { @@ -2561,9 +2553,12 @@ public static function validate_CHILD(object $inj): mixed $tval = new \stdClass(); } elseif (!self::ismap($tval)) { $inj->errs[] = self::_invalidTypeMsg( - self::slice($inj->path, 0, -1), self::S_object, self::typify($tval), $tval); + self::slice($inj->path, 0, -1), + self::S_object, + self::typify($tval), + $tval + ); return self::undef(); - } $ckeys = self::keysof($tval); @@ -2599,7 +2594,11 @@ public static function validate_CHILD(object $inj): mixed if (!self::islist($inj->dparent)) { $msg = self::_invalidTypeMsg( - self::slice($inj->path, 0, -1), self::S_array, self::typify($inj->dparent), $inj->dparent); + self::slice($inj->path, 0, -1), + self::S_array, + self::typify($inj->dparent), + $inj->dparent + ); $inj->errs[] = $msg; $inj->keyI = count($parent); return $inj->dparent; @@ -2689,7 +2688,9 @@ public static function validate_ONE( $inj->errs[] = self::_invalidTypeMsg( $inj->path, (1 < count($tvals) ? 'one of ' : '') . $valdesc, - self::typify($inj->dparent), $inj->dparent); + self::typify($inj->dparent), + $inj->dparent + ); } return self::undef(); @@ -2754,7 +2755,9 @@ public static function validate_EXACT(object $inj): mixed $inj->path, (1 < count($inj->path) ? '' : 'value ') . 'exactly equal to ' . (1 === count($tvals) ? '' : 'one of ') . $valdesc, - self::typify($inj->dparent), $inj->dparent); + self::typify($inj->dparent), + $inj->dparent + ); } else { self::delprop($parent, $key); } @@ -2804,9 +2807,11 @@ private static function _validation( // map, treat an empty array/ListRef in the data as an empty map so // that validation does not produce a spurious type-mismatch error. // Go/Lua don't hit this because they have distinct map/list types. - if (0 < (self::T_map & $ptype) && + if ( + 0 < (self::T_map & $ptype) && (is_array($cval) || $cval instanceof \Voxgig\Struct\ListRef) && - 0 === count($cval)) { + 0 === count($cval) + ) { $cval = new \stdClass(); $ctype = self::typify($cval); } @@ -2895,7 +2900,7 @@ private static function _validatehandler( /** * Validate a data structure against a shape specification. * The shape specification follows the "by example" principle. - * + * * @param mixed $data Source data to validate * @param mixed $spec Validation specification * @param mixed $injdef Optional injection definition with extra validators, etc. @@ -2990,12 +2995,12 @@ public static function select(mixed $children, mixed $query): array } if (self::ismap($children)) { - $children = array_map(function($n) { + $children = array_map(function ($n) { self::setprop($n[1], self::S_DKEY, $n[0]); return $n[1]; }, self::items($children)); } else { - $children = array_map(function($n, $i) { + $children = array_map(function ($n, $i) { if (self::ismap($n)) { self::setprop($n, self::S_DKEY, $i); } @@ -3180,17 +3185,13 @@ private static function select_CMP(object $state, mixed $_val, mixed $ref, mixed if ('$GT' === $ref && $point > $term) { $pass = true; - } - elseif ('$LT' === $ref && $point < $term) { + } elseif ('$LT' === $ref && $point < $term) { $pass = true; - } - elseif ('$GTE' === $ref && $point >= $term) { + } elseif ('$GTE' === $ref && $point >= $term) { $pass = true; - } - elseif ('$LTE' === $ref && $point <= $term) { + } elseif ('$LTE' === $ref && $point <= $term) { $pass = true; - } - elseif ('$LIKE' === $ref && preg_match('/' . $term . '/', self::stringify($point))) { + } elseif ('$LIKE' === $ref && preg_match('/' . $term . '/', self::stringify($point))) { $pass = true; } @@ -3198,8 +3199,7 @@ private static function select_CMP(object $state, mixed $_val, mixed $ref, mixed // Update spec to match found value so that _validate does not complain $gp = self::getelem($state->nodes, -2); self::setprop($gp, $gkey, $point); - } - else { + } else { $state->errs[] = 'CMP: ' . self::pathify($ppath) . ': ' . self::stringify($point) . ' fail:' . $ref . ' ' . self::stringify($term); } @@ -3280,8 +3280,7 @@ public static function delprop(mixed $parent, mixed $key): mixed if (self::ismap($parent)) { $key = self::strkey($key); unset($parent->$key); - } - elseif (self::islist($parent)) { + } elseif (self::islist($parent)) { // Ensure key is an integer $keyI = (int)$key; if (!is_numeric($key) || (string)$keyI !== (string)$key) { @@ -3351,9 +3350,9 @@ public static function checkPlacement( if (0 === ($modes & $inj->mode)) { $modeItems = array_filter( [self::M_KEYPRE, self::M_KEYPOST, self::M_VAL], - fn($m) => $modes & $m + fn($m) => 0 !== ($modes & $m) ); - $placementNames = array_map(fn($m) => self::PLACEMENT[$m] ?? '', $modeItems); + $placementNames = array_map(fn($m) => self::PLACEMENT[$m], $modeItems); $inj->errs[] = '$' . $ijname . ': invalid placement as ' . (self::PLACEMENT[$inj->mode] ?? '') . ', expected: ' . implode(',', $placementNames) . '.'; return false; @@ -3400,8 +3399,7 @@ public static function injectChild(mixed $child, mixed $store, object $inj): obj $cinj = $inj->prior->prior->child($inj->prior->keyI, $inj->prior->keys); $cinj->val = $child; self::setprop($cinj->parent, $inj->prior->key, $child); - } - else { + } else { $cinj = $inj->prior->child($inj->keyI, $inj->keys); $cinj->val = $child; self::setprop($cinj->parent, $inj->key, $child); @@ -3412,13 +3410,11 @@ public static function injectChild(mixed $child, mixed $store, object $inj): obj return $cinj; } - } class Injection { - public int $mode; public bool $full; public int $keyI; @@ -3495,14 +3491,12 @@ public function descend(): mixed // Resolve current node in store for local paths. if (Struct::UNDEF === $this->dparent) { - // Even if there's no data, dpath should continue to match path, so that // relative paths work properly. if (1 < Struct::size($this->dpath)) { $this->dpath = Struct::flatten([$this->dpath, $parentkey]); } - } - else { + } else { // this->dparent is the containing node of the current store value. if (null !== $parentkey && Struct::UNDEF !== $parentkey) { $this->dparent = Struct::_getprop($this->dparent, $parentkey); @@ -3510,8 +3504,7 @@ public function descend(): mixed $lastpart = Struct::getelem($this->dpath, -1); if ($lastpart === '$:' . $parentkey) { $this->dpath = Struct::slice($this->dpath, -1); - } - else { + } else { $this->dpath = Struct::flatten([$this->dpath, $parentkey]); } } @@ -3559,8 +3552,7 @@ public function setval(mixed $val, ?int $ancestor = null): mixed } else { $parent = Struct::setprop($this->parent, $this->key, $val); } - } - else { + } else { $aval = Struct::getelem($this->nodes, 0 - $ancestor); $akey = Struct::getelem($this->path, 0 - $ancestor); if (Struct::UNDEF === $val) { @@ -3573,4 +3565,3 @@ public function setval(mixed $val, ?int $ancestor = null): mixed return $parent; } } -?> diff --git a/php/tests/ClientTest.php b/php/tests/ClientTest.php index 152835ad..29939353 100644 --- a/php/tests/ClientTest.php +++ b/php/tests/ClientTest.php @@ -3,6 +3,7 @@ namespace Voxgig\Struct; use PHPUnit\Framework\TestCase; + require_once __DIR__ . '/SDK.php'; require_once __DIR__ . '/Runner.php'; @@ -24,4 +25,4 @@ public function testClientCheckBasic(): void // If we get here without exceptions, the test passed $this->assertTrue(true); } -} \ No newline at end of file +} diff --git a/php/tests/Runner.php b/php/tests/Runner.php index eb05687e..11fa13b0 100644 --- a/php/tests/Runner.php +++ b/php/tests/Runner.php @@ -6,12 +6,13 @@ require_once __DIR__ . '/../src/Struct.php'; -class Runner { - +class Runner +{ private const NULLMARK = "__NULL__"; private const UNDEFMARK = "__UNDEF__"; - public static function makeRunner(string $testfile, $client): callable { + public static function makeRunner(string $testfile, $client): callable + { return function (string $name, $store = null) use ($testfile, $client) { $store = $store ?? []; $utility = $client->utility(); @@ -57,7 +58,8 @@ public static function makeRunner(string $testfile, $client): callable { }; } - private static function resolveSpec(string $name, string $testfile): array { + private static function resolveSpec(string $name, string $testfile): array + { // If $testfile is an absolute path, use it as-is; otherwise, build a path relative to __DIR__ if (preg_match('/^(\/|[A-Za-z]:[\/\\\\])/', $testfile)) { $path = $testfile; @@ -78,7 +80,8 @@ private static function resolveSpec(string $name, string $testfile): array { } } - private static function resolveClients($client, array $spec, $store, $structUtils): array { + private static function resolveClients($client, array $spec, $store, $structUtils): array + { $clients = []; if (isset($spec['DEF']) && isset($spec['DEF']['client'])) { foreach ($spec['DEF']['client'] as $cn => $cdef) { @@ -92,11 +95,13 @@ private static function resolveClients($client, array $spec, $store, $structUtil return $clients; } - private static function resolveSubject(string $name, $container, $subject = null) { + private static function resolveSubject(string $name, $container, $subject = null) + { return $subject ?? ($container->$name ?? null); } - private static function resolveFlags($flags = null): array { + private static function resolveFlags($flags = null): array + { if ($flags === null) { $flags = []; } @@ -105,14 +110,16 @@ private static function resolveFlags($flags = null): array { return $flags; } - private static function resolveEntry($entry, array $flags) { + private static function resolveEntry($entry, array $flags) + { if (!isset($entry['out']) && $flags['null']) { $entry['out'] = self::NULLMARK; } return $entry; } - private static function checkResult(array $entry, $res, $structUtils) { + private static function checkResult(array $entry, $res, $structUtils) + { $matched = false; if (isset($entry['match'])) { $result = [ @@ -136,7 +143,8 @@ private static function checkResult(array $entry, $res, $structUtils) { } } - private static function handleError(&$entry, \Exception $err, $structUtils) { + private static function handleError(&$entry, \Exception $err, $structUtils) + { $entry['thrown'] = $err->getMessage(); if (isset($entry['err'])) { if ($entry['err'] === true || self::matchval($entry['err'], $err->getMessage(), $structUtils)) { @@ -165,7 +173,8 @@ private static function handleError(&$entry, \Exception $err, $structUtils) { } } - private static function resolveArgs($entry, array $testpack, $structUtils): array { + private static function resolveArgs($entry, array $testpack, $structUtils): array + { $args = []; if (isset($entry['in'])) { $args[] = $structUtils->clone($entry['in']); @@ -185,7 +194,8 @@ private static function resolveArgs($entry, array $testpack, $structUtils): arra return $args; } - private static function resolveTestPack(string $name, $entry, $subject, $client, array $clients): array { + private static function resolveTestPack(string $name, $entry, $subject, $client, array $clients): array + { $testpack = [ 'client' => $client, 'subject' => $subject, @@ -199,7 +209,8 @@ private static function resolveTestPack(string $name, $entry, $subject, $client, return $testpack; } - private static function match($check, $base, $structUtils): void { + private static function match($check, $base, $structUtils): void + { $structUtils->walk($check, function ($key, $val, $parent, $path) use ($base, $structUtils) { if (!is_array($val) && !is_object($val)) { $baseval = $structUtils->getpath($base, $path); @@ -220,7 +231,8 @@ private static function match($check, $base, $structUtils): void { }); } - private static function matchval($check, $base, $structUtils): bool { + private static function matchval($check, $base, $structUtils): bool + { $pass = ($check === $base); if (!$pass) { if (is_string($check)) { @@ -237,7 +249,8 @@ private static function matchval($check, $base, $structUtils): bool { return $pass; } - private static function fixJSON($val, array $flags) { + private static function fixJSON($val, array $flags) + { if ($val === null) { return $flags['null'] ? self::NULLMARK : $val; } @@ -264,7 +277,8 @@ private static function fixJSON($val, array $flags) { return json_decode(json_encode($fixed), true); } - public static function nullModifier($val, $key, array &$parent) { + public static function nullModifier($val, $key, array &$parent) + { if ($val === self::NULLMARK) { $parent[$key] = null; } elseif (is_string($val)) { diff --git a/php/tests/SDK.php b/php/tests/SDK.php index 7e4d87ad..ea0b35a3 100644 --- a/php/tests/SDK.php +++ b/php/tests/SDK.php @@ -4,30 +4,33 @@ require_once __DIR__ . '/../src/Struct.php'; -class SDK { +class SDK +{ private array $opts; private object $utility; - public function __construct(array $opts = []) { + public function __construct(array $opts = []) + { $this->opts = $opts ?: []; // Capture opts for use in the closure. $optsCopy = $this->opts; $this->utility = (object)[ // An anonymous adapter that forwards method calls to the Struct class. 'struct' => new class { - public function __call(string $name, array $args) { + public function __call(string $name, array $args) + { // Map method name (if needed) here; otherwise, call directly. return call_user_func_array(['Voxgig\Struct\Struct', $name], $args); } }, // A contextify function that returns the context map as-is. - 'contextify' => function($ctxmap) { + 'contextify' => function ($ctxmap) { return $ctxmap; }, // A simple check function similar to the TS version. - 'check' => function($ctx) use ($optsCopy) { + 'check' => function ($ctx) use ($optsCopy) { $foo = isset($optsCopy['foo']) ? $optsCopy['foo'] : ''; - + // Handle both array and object contexts $bar = '0'; if (is_object($ctx) && isset($ctx->meta)) { @@ -43,7 +46,7 @@ public function __call(string $name, array $args) { $bar = $ctx['meta']->bar; } } - + return (object)[ 'zed' => 'ZED' . $foo . '_' . $bar ]; @@ -52,16 +55,19 @@ public function __call(string $name, array $args) { } // Static method to obtain a test SDK instance. - public static function test(array $opts = []): SDK { + public static function test(array $opts = []): SDK + { return new SDK($opts); } // Instance method (if needed) that mimics the async test() from TS. - public function testMethod(array $opts = []): SDK { + public function testMethod(array $opts = []): SDK + { return new SDK($opts); } - public function utility(): object { + public function utility(): object + { return $this->utility; } } diff --git a/php/tests/StructTest.php b/php/tests/StructTest.php index 112b058d..a38e87e4 100644 --- a/php/tests/StructTest.php +++ b/php/tests/StructTest.php @@ -9,7 +9,6 @@ class StructTest extends TestCase { - private stdClass $testSpec; protected function setUp(): void @@ -225,8 +224,8 @@ function ($input) { $val = property_exists($input, 'val') ? $input->val : Struct::undef(); $key = property_exists($input, 'key') ? $input->key : Struct::undef(); $alt = property_exists($input, 'alt') ? $input->alt : Struct::undef(); - return $alt === Struct::undef() ? - Struct::getelem($val, $key) : + return $alt === Struct::undef() ? + Struct::getelem($val, $key) : Struct::getelem($val, $key, $alt); } ); @@ -433,10 +432,12 @@ public function testGetpathHandler(): void function ($input) { $store = [ '$TOP' => $input->store, - '$FOO' => function() { return 'foo'; } + '$FOO' => function () { + return 'foo'; + } ]; $state = new \stdClass(); - $state->handler = function($inj, $val, $cur, $ref) { + $state->handler = function ($inj, $val, $cur, $ref) { return $val(); }; return Struct::getpath( @@ -618,7 +619,6 @@ public function testMergeSpecial(): void $obj2, Struct::merge([$obj2]) ); - } public function testGetpathBasic(): void @@ -969,8 +969,12 @@ function ($input) { public function testFilter(): void { $checkmap = [ - 'gt3' => function ($n) { return $n[1] > 3; }, - 'lt3' => function ($n) { return $n[1] < 3; }, + 'gt3' => function ($n) { + return $n[1] > 3; + }, + 'lt3' => function ($n) { + return $n[1] < 3; + }, ]; $this->testSet( $this->testSpec->minor->filter, @@ -1001,7 +1005,9 @@ function ($input) { public function testMinorEdgeClone(): void { - $f0 = function () { return null; }; + $f0 = function () { + return null; + }; $result = Struct::clone((object) ['a' => $f0]); $this->assertSame($f0, $result->a); @@ -1014,7 +1020,9 @@ public function testMinorEdgeClone(): void public function testMinorEdgeCloneClosures(): void { // Closure preserved by reference in an object. - $fn = function ($x) { return $x + 1; }; + $fn = function ($x) { + return $x + 1; + }; $obj = (object) ['a' => 1, 'f' => $fn]; $cloned = Struct::clone($obj); $this->assertSame($fn, $cloned->f); @@ -1030,7 +1038,9 @@ public function testMinorEdgeCloneClosures(): void $this->assertNotSame($nested->x, $clonedNested->x); // Closure preserved in an array. - $fn3 = function () { return 'hello'; }; + $fn3 = function () { + return 'hello'; + }; $arr = [$fn3, 1, 'two']; $clonedArr = Struct::clone($arr); $this->assertSame($fn3, $clonedArr[0]); @@ -1038,8 +1048,12 @@ public function testMinorEdgeCloneClosures(): void $this->assertEquals('two', $clonedArr[2]); // Multiple closures preserved independently. - $fnA = function () { return 'A'; }; - $fnB = function () { return 'B'; }; + $fnA = function () { + return 'A'; + }; + $fnB = function () { + return 'B'; + }; $multi = (object) ['a' => $fnA, 'b' => $fnB, 'c' => 99]; $clonedMulti = Struct::clone($multi); $this->assertSame($fnA, $clonedMulti->a); @@ -1063,14 +1077,19 @@ public function testMinorEdgeCloneClosures(): void // Invokable object preserved by reference. $invokable = new class { - public function __invoke(): string { return 'invoked'; } + public function __invoke(): string + { + return 'invoked'; + } }; $objWithInvokable = (object) ['f' => $invokable]; $clonedInvokable = Struct::clone($objWithInvokable); $this->assertSame($invokable, $clonedInvokable->f); // Bare closure as top-level value. - $topFn = function () { return 42; }; + $topFn = function () { + return 42; + }; $clonedTopFn = Struct::clone($topFn); $this->assertSame($topFn, $clonedTopFn); @@ -1085,7 +1104,9 @@ public function __invoke(): string { return 'invoked'; } public function testMinorEdgeGetelem(): void { - $this->assertEquals(2, Struct::getelem([], 1, function () { return 2; })); + $this->assertEquals(2, Struct::getelem([], 1, function () { + return 2; + })); } public function testMinorEdgeItems(): void @@ -1096,7 +1117,9 @@ public function testMinorEdgeItems(): void public function testMinorEdgeJsonify(): void { - $this->assertEquals('null', Struct::jsonify(function () { return 1; })); + $this->assertEquals('null', Struct::jsonify(function () { + return 1; + })); } public function testMinorEdgeKeysof(): void @@ -1122,7 +1145,9 @@ public function testMinorEdgeTypify(): void { $this->assertEquals(Struct::T_noval, Struct::typify(Struct::undef())); $this->assertEquals(Struct::T_scalar | Struct::T_null, Struct::typify(null)); - $this->assertEquals(Struct::T_scalar | Struct::T_function, Struct::typify(function () { return null; })); + $this->assertEquals(Struct::T_scalar | Struct::T_function, Struct::typify(function () { + return null; + })); } // ——— Merge depth ——— @@ -1331,5 +1356,4 @@ public function testValidateEmptyArrayAsMap(): void $result6 = Struct::validate($data6, $spec, $injdef6); $this->assertEmpty($errs6, 'empty ListRef should not cause type-mismatch against map spec'); } - } diff --git a/php/tests/WalkBench.php b/php/tests/WalkBench.php index 7e60eb4e..2a4a44dc 100644 --- a/php/tests/WalkBench.php +++ b/php/tests/WalkBench.php @@ -83,6 +83,6 @@ function benchOne(string $label, int $width, int $depth, int $iters): void // nodes to make ns/node meaningful. Sizes are intentionally modest so the // baseline (pre-optimization) fits in 512M memory while still producing // enough total visits for ns/node to stabilize. -benchOne('wide+deep', 8, 6, 3); // ~299k nodes -benchOne('very-wide', 1000, 2, 5); // ~1.001m nodes -benchOne('very-deep', 2, 14, 30); // ~32k nodes per tree, d=14 +benchOne('wide+deep', 8, 6, 3); // ~299k nodes +benchOne('very-wide', 1000, 2, 5); // ~1.001m nodes +benchOne('very-deep', 2, 14, 30); // ~32k nodes per tree, d=14 diff --git a/py/Makefile b/py/Makefile index 53f57b60..6ae9b29b 100644 --- a/py/Makefile +++ b/py/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test bench clean reset +.PHONY: inspect build test lint format-check typecheck bench clean reset audit inspect: @echo "Python version:" @@ -13,6 +13,18 @@ build: test: python3 -m unittest discover -s tests +# Code quality: Ruff (lint), Ruff (format check), mypy (static types). +lint: + ruff check voxgig_struct tests + ruff format --check voxgig_struct tests + mypy + +format-check: + ruff format --check voxgig_struct tests + +typecheck: + mypy + bench: WALK_BENCH=1 python3 -m unittest discover -s tests -p 'test_walk_bench.py' -v @@ -27,3 +39,8 @@ clean: rm -rf *.egg-info dist build reset: clean + +# Supply-chain + security: dependency vuln scan (pip-audit) + Bandit SAST. +audit: + pip-audit -r requirements-dev.txt + bandit -q -r voxgig_struct diff --git a/py/README.md b/py/README.md index d144b855..1797fcc3 100644 --- a/py/README.md +++ b/py/README.md @@ -64,8 +64,8 @@ from voxgig_struct import ( size, slice, strkey, stringify, transform, typename, typify, validate, walk, - # builders + Python aliases - jm, jt, jo, ja, + # builders + jm, jt, # extras (Python-specific convenience) replace, joinurl, @@ -274,15 +274,11 @@ select( # [{'age': 30, '$KEY': 'a'}] ``` -### Builders and aliases +### Builders ```python jm('a', 1, 'b', 2) # {'a': 1, 'b': 2} jt(1, 2, 3) # [1, 2, 3] - -# Aliases (Python "JSON Object" / "JSON Array") -jo(*args) # alias for jm -ja(*args) # alias for jt ``` ### Injection helpers diff --git a/py/pyproject.toml b/py/pyproject.toml new file mode 100644 index 00000000..5be4e445 --- /dev/null +++ b/py/pyproject.toml @@ -0,0 +1,38 @@ +# Tooling configuration for the Python port. +# Code quality: Ruff (lint + format) and mypy (static types). + +# ---- Ruff: linter + formatter ---- +# https://docs.astral.sh/ruff/ +[tool.ruff] +line-length = 100 +target-version = "py310" +extend-exclude = ["build", "dist"] + +[tool.ruff.lint] +# A sensible, widely used baseline: +# E/W pycodestyle F pyflakes I isort +# B bugbear UP pyupgrade C4 comprehensions +# SIM simplify RUF ruff-specific +select = ["E", "W", "F", "I", "B", "UP", "C4", "SIM", "RUF"] +ignore = [ + "E501", # line length is handled by the formatter + "B008", # function call in default argument (used deliberately here) +] + +[tool.ruff.lint.per-file-ignores] +# __init__.py re-exports the public API; the imports are intentional. +"voxgig_struct/__init__.py" = ["F401"] +# Test runners use `from voxgig_struct import *` by design. +"tests/**" = ["B011", "F403", "F405"] + +[tool.ruff.format] +quote-style = "single" + +# ---- mypy: static type checking ---- +# https://mypy.readthedocs.io/ +[tool.mypy] +python_version = "3.10" +files = ["voxgig_struct"] +ignore_missing_imports = true +warn_unused_ignores = true +warn_redundant_casts = true diff --git a/py/requirements-dev.txt b/py/requirements-dev.txt new file mode 100644 index 00000000..0dc17bc1 --- /dev/null +++ b/py/requirements-dev.txt @@ -0,0 +1,3 @@ +# Development-only tooling for the Python port. +ruff>=0.6 +mypy>=1.10 diff --git a/py/tests/runner.py b/py/tests/runner.py index f023741d..c1184170 100644 --- a/py/tests/runner.py +++ b/py/tests/runner.py @@ -1,22 +1,22 @@ # Test runner that uses the test model in build/test. -import os import json +import os import re -from typing import Any, Dict, List, Callable, TypedDict, Optional, Union - +from collections.abc import Callable +from typing import Any, TypedDict NULLMARK = '__NULL__' # Value is JSON null UNDEFMARK = '__UNDEF__' # Value is not present (thus, undefined) -EXISTSMARK = '__EXISTS__' # Value exists (not undefined). +EXISTSMARK = '__EXISTS__' # Value exists (not undefined). class RunPack(TypedDict): - spec: Dict[str, Any] + spec: dict[str, Any] runset: Callable runsetflags: Callable subject: Callable - client: Optional[Any] + client: Any | None def makeRunner(testfile: str, client: Any): @@ -26,17 +26,17 @@ def runner( store: Any = None, ) -> RunPack: store = store or {} - + utility = client.utility() structUtils = utility.struct - + spec = resolve_spec(name, testfile) clients = resolve_clients(client, spec, store, structUtils) subject = resolve_subject(name, utility) - + def runsetflags(testspec, flags, testsubject): nonlocal subject, clients - + subject = testsubject or subject flags = resolve_flags(flags) testspecmap = fixJSON(testspec, flags) @@ -48,34 +48,34 @@ def runsetflags(testspec, flags, testsubject): testpack = resolve_testpack(name, entry, subject, client, clients) args = resolve_args(entry, testpack, utility, structUtils) - + # Execute the test function - res = testpack["subject"](*args) + res = testpack['subject'](*args) res = fixJSON(res, flags) entry['res'] = res check_result(entry, args, res, structUtils) - + except Exception as err: handle_error(entry, err, structUtils) def runset(testspec, testsubject): return runsetflags(testspec, {}, testsubject) - + runpack = { - "spec": spec, - "runset": runset, - "runsetflags": runsetflags, - "subject": subject, - "client": client + 'spec': spec, + 'runset': runset, + 'runsetflags': runsetflags, + 'subject': subject, + 'client': client, } return runpack - + return runner -def resolve_spec(name: str, testfile: str) -> Dict[str, Any]: - with open(os.path.join(os.path.dirname(__file__), testfile), 'r', encoding='utf-8') as f: +def resolve_spec(name: str, testfile: str) -> dict[str, Any]: + with open(os.path.join(os.path.dirname(__file__), testfile), encoding='utf-8') as f: alltests = json.load(f) if 'primary' in alltests and name in alltests['primary']: @@ -84,24 +84,26 @@ def resolve_spec(name: str, testfile: str) -> Dict[str, Any]: spec = alltests[name] else: spec = alltests - + return spec -def resolve_clients(client: Any, spec: Dict[str, Any], store: Any, structUtils: Any) -> Dict[str, Any]: +def resolve_clients( + client: Any, spec: dict[str, Any], store: Any, structUtils: Any +) -> dict[str, Any]: clients = {} if 'DEF' in spec and 'client' in spec['DEF']: for client_name, client_val in structUtils.items(spec['DEF']['client']): # Get client options client_opts = client_val.get('test', {}).get('options', {}) - + # Apply store injections if needed if isinstance(store, dict) and structUtils.inject: structUtils.inject(client_opts, store) - + # Create and store the client using the passed client object clients[client_name] = client.tester(client_opts) - + return clients @@ -111,36 +113,36 @@ def resolve_subject(name: str, container: Any): def check_result(entry, args, res, structUtils): matched = False - + if 'match' in entry: - result = {'in': entry.get('in'), 'args': args, 'out': entry.get('res'), 'ctx': entry.get('ctx')} - match( - entry['match'], - result, - structUtils - ) + result = { + 'in': entry.get('in'), + 'args': args, + 'out': entry.get('res'), + 'ctx': entry.get('ctx'), + } + match(entry['match'], result, structUtils) matched = True - + out = entry.get('out') - + if out == res: return - + # NOTE: allow match with no out - if matched and (NULLMARK == out or out is None): + if matched and (out == NULLMARK or out is None): return - + try: cleaned_res = json.loads(json.dumps(res, default=str)) - except: + except (TypeError, ValueError, RecursionError): # If can't be serialized just use the original cleaned_res = res - + # Compare result with expected output using deep equality if cleaned_res != out: raise AssertionError( - f"Expected: {out}, got: {cleaned_res}\n" - f"Test: {entry.get('name', 'unknown')}" + f'Expected: {out}, got: {cleaned_res}\nTest: {entry.get("name", "unknown")}' ) @@ -148,17 +150,17 @@ def handle_error(entry, err, structUtils): # Record the error in the entry entry['thrown'] = err entry_err = entry.get('err') - + # If the test expects an error if entry_err is not None: # If it's any error or matches expected pattern if entry_err is True or matchval(entry_err, str(err), structUtils): # If we also need to match error details if 'match' in entry: - #err_json = None - #if None != err: - #err_json = {"message":str(err)} - + # err_json = None + # if None != err: + # err_json = {"message":str(err)} + match( entry['match'], { @@ -166,55 +168,50 @@ def handle_error(entry, err, structUtils): 'out': entry.get('res'), 'ctx': entry.get('ctx'), #'err': err_json - 'err': fixJSON(err) + 'err': fixJSON(err), }, - structUtils + structUtils, ) # Error was expected, continue return True - + # Expected error didn't match the actual error - raise AssertionError( - f"ERROR MATCH: [{structUtils.stringify(entry_err)}] <=> [{str(err)}]" - ) + raise AssertionError(f'ERROR MATCH: [{structUtils.stringify(entry_err)}] <=> [{err!s}]') # If the test doesn't expect an error elif isinstance(err, AssertionError): # Propagate assertion errors with added context - raise AssertionError( - f"{str(err)}\nTest: {entry.get('name', 'unknown')}" - ) + raise AssertionError(f'{err!s}\nTest: {entry.get("name", "unknown")}') else: # For other errors, include the full error stack import traceback - raise AssertionError( - f"{traceback.format_exc()}\nTest: {entry.get('name', 'unknown')}" - ) - + raise AssertionError(f'{traceback.format_exc()}\nTest: {entry.get("name", "unknown")}') + + def resolve_testpack( - name, - entry, - subject, - client, - clients, + name, + entry, + subject, + client, + clients, ): testpack = { - "client": client, - "subject": subject, - "utility": client.utility(), + 'client': client, + 'subject': subject, + 'utility': client.utility(), } - + if 'client' in entry: - testpack["client"] = clients[entry['client']] - testpack["utility"] = testpack["client"].utility() - testpack["subject"] = resolve_subject(name, testpack["utility"]) - + testpack['client'] = clients[entry['client']] + testpack['utility'] = testpack['client'].utility() + testpack['subject'] = resolve_subject(name, testpack['utility']) + return testpack def resolve_args(entry, testpack, utility, structUtils): args = [] - + if 'ctx' in entry: args = [entry['ctx']] elif 'args' in entry: @@ -231,128 +228,126 @@ def resolve_args(entry, testpack, utility, structUtils): first = utility.contextify(first) args[0] = first entry['ctx'] = first - first.client = testpack["client"] - first.utility = testpack["utility"] - + first.client = testpack['client'] + first.utility = testpack['utility'] + return args -def resolve_flags(flags: Dict[str, Any] = None) -> Dict[str, bool]: +def resolve_flags(flags: dict[str, Any] | None = None) -> dict[str, bool]: if flags is None: flags = {} - - flags["null"] = flags.get("null", True) - + + flags['null'] = flags.get('null', True) + return flags -def resolve_entry(entry: Dict[str, Any], flags: Dict[str, bool]) -> Dict[str, Any]: +def resolve_entry(entry: dict[str, Any], flags: dict[str, bool]) -> dict[str, Any]: # Set default output value for missing 'out' field - if 'out' not in entry and flags.get("null", True): - entry["out"] = NULLMARK - + if 'out' not in entry and flags.get('null', True): + entry['out'] = NULLMARK + return entry -def fixJSON(obj, flags={}): +def fixJSON(obj, flags=None): + if flags is None: + flags = {} # Handle nulls if obj is None: - return NULLMARK if flags.get("null", True) else None - + return NULLMARK if flags.get('null', True) else None + # Handle errors if isinstance(obj, Exception): - return { - **vars(obj), - 'name': type(obj).__name__, - 'message': str(obj) - } - + return {**vars(obj), 'name': type(obj).__name__, 'message': str(obj)} + # Handle collections recursively elif isinstance(obj, list): return [fixJSON(item, flags) for item in obj] elif isinstance(obj, dict): return {k: fixJSON(v, flags) for k, v in obj.items()} - + # Return everything else unchanged return obj def jsonfallback(obj): - return f"" + return f'' def match(check, base, structUtils): base = structUtils.clone(base) - + # Use walk function to iterate through the check structure def walk_apply(_key, val, _parent, path): if not structUtils.isnode(val): baseval = structUtils.getpath(base, path) - + if baseval == val: return val - + # Explicit undefined expected - if UNDEFMARK == val and baseval is None: + if val == UNDEFMARK and baseval is None: return val # Explicit defined expected - if EXISTSMARK == val and baseval is not None: + if val == EXISTSMARK and baseval is not None: return val - + if not matchval(val, baseval, structUtils): raise AssertionError( - f"MATCH: {'.'.join(map(str, path))}: " - f"[{structUtils.stringify(val)}] <=> [{structUtils.stringify(baseval)}]" + f'MATCH: {".".join(map(str, path))}: ' + f'[{structUtils.stringify(val)}] <=> [{structUtils.stringify(baseval)}]' ) return val - + # Use walk to apply the check function to each node structUtils.walk(check, walk_apply) - + def matchval(check, base, structUtils): # Handle undefined special case if check == '__UNDEF__' or check == NULLMARK: check = None - + if check == base: return True - + # String-based pattern matching if isinstance(check, str): # Convert base to string for comparison base_str = structUtils.stringify(base) - + # Check for regex pattern with /pattern/ syntax regex_match = re.match(r'^/(.+)/$', check) - + if regex_match: pattern = regex_match.group(1) return re.search(pattern, base_str) is not None else: # Case-insensitive substring check return structUtils.stringify(check).lower() in base_str.lower() - + # Functions automatically pass elif callable(check): return True - + # No match return False def nullModifier(val, key, parent, _state=None, _current=None, _store=None): - if NULLMARK == val: + if val == NULLMARK: parent[key] = None elif isinstance(val, str): - parent[key] = val.replace(NULLMARK, "null") + parent[key] = val.replace(NULLMARK, 'null') # Export the necessary components similar to TypeScript __all__ = [ 'NULLMARK', 'UNDEFMARK', - 'nullModifier', 'makeRunner', + 'nullModifier', ] diff --git a/py/tests/sdk.py b/py/tests/sdk.py index 3538915b..d2be3526 100644 --- a/py/tests/sdk.py +++ b/py/tests/sdk.py @@ -1,4 +1,3 @@ - import voxgig_struct # class StructUtils: @@ -14,6 +13,7 @@ def __init__(self): self.utility = None self.meta = {} + class Utility: def __init__(self, opts=None): self._opts = opts @@ -22,37 +22,37 @@ def __init__(self, opts=None): def contextify(self, ctxmap): ctx = Context() - meta = ctxmap.get('meta',{}) - for k,v in meta.items(): + meta = ctxmap.get('meta', {}) + for k, v in meta.items(): ctx.meta[k] = v return ctx - + def check(self, ctx): - zed = "ZED" - + zed = 'ZED' + if self._opts is None: - zed += "" + zed += '' else: - foo = self._opts.get("foo") - zed += "0" if foo is None else str(foo) + foo = self._opts.get('foo') + zed += '0' if foo is None else str(foo) - zed += "_" - zed += str(ctx.meta.get("bar")) + zed += '_' + zed += str(ctx.meta.get('bar')) - return {"zed": zed} + return {'zed': zed} class SDK: def __init__(self, opts=None): self._opts = opts or {} self._utility = Utility(opts) - + @staticmethod def test(opts=None): return SDK(opts) - + def tester(self, opts=None): - return SDK(self.opts if None == opts else opts) - + return SDK(self.opts if opts is None else opts) + def utility(self): return self._utility diff --git a/py/tests/test_voxgig_client.py b/py/tests/test_voxgig_client.py index 07c92da0..fedb6486 100644 --- a/py/tests/test_voxgig_client.py +++ b/py/tests/test_voxgig_client.py @@ -6,7 +6,6 @@ from runner import ( makeRunner, ) - from sdk import SDK # Create a runner for client testing @@ -14,17 +13,16 @@ runner = makeRunner('../../build/test/test.json', sdk_client) runparts = runner('check') -spec = runparts["spec"] -runset = runparts["runset"] -subject = runparts["subject"] +spec = runparts['spec'] +runset = runparts['runset'] +subject = runparts['subject'] class TestClient(unittest.TestCase): - def test_client_check_basic(self): - runset(spec["basic"], subject) + runset(spec['basic'], subject) # If you want to run this file directly, add: -if __name__ == "__main__": - unittest.main() \ No newline at end of file +if __name__ == '__main__': + unittest.main() diff --git a/py/tests/test_voxgig_struct.py b/py/tests/test_voxgig_struct.py index 78caf9d8..63e085a7 100644 --- a/py/tests/test_voxgig_struct.py +++ b/py/tests/test_voxgig_struct.py @@ -1,4 +1,3 @@ - # RUN: python -m unittest discover -s tests # RUN-SOME: python -m unittest discover -s tests -k getpath @@ -6,35 +5,34 @@ try: from .runner import ( + NULLMARK, makeRunner, nullModifier, - NULLMARK, - UNDEFMARK, ) from .sdk import SDK except ImportError: from runner import ( + NULLMARK, makeRunner, nullModifier, - NULLMARK, - UNDEFMARK, ) from sdk import SDK -from voxgig_struct import Injection from voxgig_struct.voxgig_struct import ( - T_noval, T_scalar, T_function, T_symbol, T_any, T_node, T_instance, T_null, + T_function, + T_noval, + T_null, + T_scalar, ) - sdk_client = SDK.test() runner = makeRunner('../../build/test/test.json', sdk_client) runparts = runner('struct') -spec = runparts["spec"] -runset = runparts["runset"] -runsetflags = runparts["runsetflags"] -client = runparts["client"] +spec = runparts['spec'] +runset = runparts['runset'] +runsetflags = runparts['runsetflags'] +client = runparts['client'] # Get all the struct utilities from the client struct_utils = client.utility().struct @@ -56,8 +54,8 @@ ismap = struct_utils.ismap isnode = struct_utils.isnode items = struct_utils.items -ja = struct_utils.ja -jo = struct_utils.jo +jm = struct_utils.jm +jt = struct_utils.jt joinurl = struct_utils.joinurl jsonify = struct_utils.jsonify keysof = struct_utils.keysof @@ -78,18 +76,17 @@ validate = struct_utils.validate walk = struct_utils.walk -minorSpec = spec["minor"] -walkSpec = spec["walk"] -mergeSpec = spec["merge"] -getpathSpec = spec["getpath"] -injectSpec = spec["inject"] -transformSpec = spec["transform"] -validateSpec = spec["validate"] -selectSpec = spec["select"] +minorSpec = spec['minor'] +walkSpec = spec['walk'] +mergeSpec = spec['merge'] +getpathSpec = spec['getpath'] +injectSpec = spec['inject'] +transformSpec = spec['transform'] +validateSpec = spec['validate'] +selectSpec = spec['select'] - -class TestStruct(unittest.TestCase): +class TestStruct(unittest.TestCase): # minor tests # =========== @@ -99,103 +96,104 @@ def test_exists(self): self.assertTrue(callable(escurl)) self.assertTrue(callable(getprop)) self.assertTrue(callable(getpath)) - + self.assertTrue(callable(haskey)) self.assertTrue(callable(inject)) self.assertTrue(callable(isempty)) self.assertTrue(callable(isfunc)) self.assertTrue(callable(iskey)) - + self.assertTrue(callable(islist)) self.assertTrue(callable(ismap)) self.assertTrue(callable(isnode)) self.assertTrue(callable(items)) self.assertTrue(callable(joinurl)) - + self.assertTrue(callable(keysof)) self.assertTrue(callable(merge)) self.assertTrue(callable(pathify)) self.assertTrue(callable(setprop)) self.assertTrue(callable(strkey)) - + self.assertTrue(callable(stringify)) self.assertTrue(callable(transform)) self.assertTrue(callable(typify)) self.assertTrue(callable(validate)) self.assertTrue(callable(walk)) - def test_minor_isnode(self): - runset(minorSpec["isnode"], isnode) + runset(minorSpec['isnode'], isnode) - def test_minor_ismap(self): - runset(minorSpec["ismap"], ismap) + runset(minorSpec['ismap'], ismap) - def test_minor_islist(self): - runset(minorSpec["islist"], islist) + runset(minorSpec['islist'], islist) - def test_minor_iskey(self): - runsetflags(minorSpec["iskey"], {"null": False}, iskey) + runsetflags(minorSpec['iskey'], {'null': False}, iskey) - def test_minor_strkey(self): - runsetflags(minorSpec["strkey"], {"null": False}, strkey) + runsetflags(minorSpec['strkey'], {'null': False}, strkey) - def test_minor_isempty(self): - runsetflags(minorSpec["isempty"], {"null": False}, isempty) + runsetflags(minorSpec['isempty'], {'null': False}, isempty) - def test_minor_isfunc(self): - runset(minorSpec["isfunc"], isfunc) + runset(minorSpec['isfunc'], isfunc) + def f0(): return None + self.assertTrue(isfunc(f0)) self.assertTrue(isfunc(lambda: None)) - def test_minor_clone(self): - runsetflags(minorSpec["clone"], {"null": False}, clone) + runsetflags(minorSpec['clone'], {'null': False}, clone) + def f0(): return None - self.assertEqual({"a":f0}, clone({"a":f0})) - + self.assertEqual({'a': f0}, clone({'a': f0})) + def test_minor_escre(self): - runset(minorSpec["escre"], escre) + runset(minorSpec['escre'], escre) - def test_minor_escurl(self): - runset(minorSpec["escurl"], escurl) + runset(minorSpec['escurl'], escurl) - def test_minor_stringify(self): - runset(minorSpec["stringify"], - lambda vin: stringify("null" if NULLMARK == vin.get('val') else - vin.get('val'), vin.get('max'))) + runset( + minorSpec['stringify'], + lambda vin: stringify( + 'null' if vin.get('val') == NULLMARK else vin.get('val'), vin.get('max') + ), + ) def test_minor_jsonify(self): - runsetflags(minorSpec["jsonify"], {"null": False}, - lambda vin: jsonify(vin.get("val"), vin.get("flags"))) + runsetflags( + minorSpec['jsonify'], + {'null': False}, + lambda vin: jsonify(vin.get('val'), vin.get('flags')), + ) def test_minor_getelem(self): def getelem_wrapper(vin): - if vin.get("alt") is None: - return getelem(vin.get("val"), vin.get("key")) + if vin.get('alt') is None: + return getelem(vin.get('val'), vin.get('key')) else: - return getelem(vin.get("val"), vin.get("key"), vin.get("alt")) - runsetflags(minorSpec["getelem"], {"null": False}, getelem_wrapper) + return getelem(vin.get('val'), vin.get('key'), vin.get('alt')) + + runsetflags(minorSpec['getelem'], {'null': False}, getelem_wrapper) def test_minor_delprop(self): def delprop_wrapper(vin): - return delprop(vin.get("parent"), vin.get("key")) - runset(minorSpec["delprop"], delprop_wrapper) + return delprop(vin.get('parent'), vin.get('key')) + + runset(minorSpec['delprop'], delprop_wrapper) def test_minor_edge_clone(self): - x = {"y": 1} + x = {'y': 1} xc = clone(x) self.assertEqual(x, xc) self.assertIsNot(x, xc) @@ -216,12 +214,14 @@ def test_minor_edge_keysof(self): def test_minor_edge_stringify(self): a = {} - a["a"] = a + a['a'] = a self.assertEqual(stringify(a), '__STRINGIFY_FAILED__') - self.assertEqual(stringify({"a": [9]}, -1, True), + self.assertEqual( + stringify({'a': [9]}, -1, True), '\x1b[38;5;81m\x1b[38;5;118m{\x1b[38;5;118ma\x1b[38;5;118m:' - '\x1b[38;5;213m[\x1b[38;5;213m9\x1b[38;5;213m]\x1b[38;5;118m}\x1b[0m') + '\x1b[38;5;213m[\x1b[38;5;213m9\x1b[38;5;213m]\x1b[38;5;118m}\x1b[0m', + ) def test_minor_edge_delprop(self): # String array tests @@ -229,7 +229,7 @@ def test_minor_edge_delprop(self): strarr1 = ['a', 'b', 'c', 'd', 'e'] self.assertEqual(delprop(strarr0, 2), ['a', 'b', 'd', 'e']) self.assertEqual(delprop(strarr1, '2'), ['a', 'b', 'd', 'e']) - + # Integer array tests intarr0 = [2, 3, 5, 7, 11] intarr1 = [2, 3, 5, 7, 11] @@ -237,89 +237,91 @@ def test_minor_edge_delprop(self): self.assertEqual(delprop(intarr1, '2'), [2, 3, 7, 11]) def test_minor_size(self): - runsetflags(minorSpec["size"], {"null": False}, size) + runsetflags(minorSpec['size'], {'null': False}, size) def test_minor_slice(self): def slice_wrapper(vin): - return slice(vin.get("val"), vin.get("start"), vin.get("end")) - runsetflags(minorSpec["slice"], {"null": False}, slice_wrapper) + return slice(vin.get('val'), vin.get('start'), vin.get('end')) + + runsetflags(minorSpec['slice'], {'null': False}, slice_wrapper) def test_minor_pad(self): def pad_wrapper(vin): - return pad(vin.get("val"), vin.get("pad"), vin.get("char")) - runsetflags(minorSpec["pad"], {"null": False}, pad_wrapper) + return pad(vin.get('val'), vin.get('pad'), vin.get('char')) + + runsetflags(minorSpec['pad'], {'null': False}, pad_wrapper) - def test_minor_pathify(self): def pathify_wrapper(vin=None): - path = vin.get("path") - path = None if NULLMARK == path else path - pathstr = pathify(path, vin.get("from")).replace("__NULL__.","") - pathstr = pathstr.replace(">", ":null>") if NULLMARK == vin.get('path') else pathstr + path = vin.get('path') + path = None if path == NULLMARK else path + pathstr = pathify(path, vin.get('from')).replace('__NULL__.', '') + pathstr = pathstr.replace('>', ':null>') if vin.get('path') == NULLMARK else pathstr return pathstr - runsetflags(minorSpec["pathify"], {"null": True}, pathify_wrapper) - - def test_minor_items(self): - runset(minorSpec["items"], items) + runsetflags(minorSpec['pathify'], {'null': True}, pathify_wrapper) + def test_minor_items(self): + runset(minorSpec['items'], items) def test_minor_getprop(self): def getprop_wrapper(vin): - if vin.get("alt") is None: - return getprop(vin.get("val"), vin.get("key")) + if vin.get('alt') is None: + return getprop(vin.get('val'), vin.get('key')) else: - return getprop(vin.get("val"), vin.get("key"), vin.get("alt")) - runsetflags(minorSpec["getprop"], {"null": False}, getprop_wrapper) - + return getprop(vin.get('val'), vin.get('key'), vin.get('alt')) + + runsetflags(minorSpec['getprop'], {'null': False}, getprop_wrapper) + def test_minor_edge_getprop(self): # String array tests strarr = ['a', 'b', 'c', 'd', 'e'] self.assertEqual(getprop(strarr, 2), 'c') self.assertEqual(getprop(strarr, '2'), 'c') - + # Integer array tests intarr = [2, 3, 5, 7, 11] self.assertEqual(getprop(intarr, 2), 5) self.assertEqual(getprop(intarr, '2'), 5) - def test_minor_setprop(self): - runset(minorSpec["setprop"], - lambda vin: setprop(vin.get("parent"), vin.get("key"), vin.get("val"))) + runset( + minorSpec['setprop'], + lambda vin: setprop(vin.get('parent'), vin.get('key'), vin.get('val')), + ) - def test_minor_edge_setprop(self): # String array tests strarr0 = ['a', 'b', 'c', 'd', 'e'] strarr1 = ['a', 'b', 'c', 'd', 'e'] self.assertEqual(setprop(strarr0, 2, 'C'), ['a', 'b', 'C', 'd', 'e']) self.assertEqual(setprop(strarr1, '2', 'CC'), ['a', 'b', 'CC', 'd', 'e']) - + # Integer array tests intarr0 = [2, 3, 5, 7, 11] intarr1 = [2, 3, 5, 7, 11] self.assertEqual(setprop(intarr0, 2, 55), [2, 3, 55, 7, 11]) self.assertEqual(setprop(intarr1, '2', 555), [2, 3, 555, 7, 11]) - def test_minor_haskey(self): - runsetflags(minorSpec["haskey"], {"null": False}, - lambda vin: haskey(vin.get("src"), vin.get("key"))) + runsetflags( + minorSpec['haskey'], {'null': False}, lambda vin: haskey(vin.get('src'), vin.get('key')) + ) - def test_minor_keysof(self): - runset(minorSpec["keysof"], keysof) + runset(minorSpec['keysof'], keysof) - def test_minor_joinurl(self): from voxgig_struct.voxgig_struct import join as struct_join - runsetflags(minorSpec["join"], {"null": False}, - lambda vin: struct_join(vin.get("val"), vin.get("sep"), vin.get("url"))) + runsetflags( + minorSpec['join'], + {'null': False}, + lambda vin: struct_join(vin.get('val'), vin.get('sep'), vin.get('url')), + ) def test_minor_typify(self): - runsetflags(minorSpec["typify"], {"null": False}, typify) + runsetflags(minorSpec['typify'], {'null': False}, typify) def test_minor_edge_typify(self): self.assertEqual(typify(), T_noval) @@ -328,49 +330,58 @@ def test_minor_edge_typify(self): self.assertEqual(typify(lambda: None), T_scalar | T_function) def test_minor_setpath(self): - runsetflags(minorSpec["setpath"], {"null": False}, - lambda vin: setpath(vin.get("store"), vin.get("path"), vin.get("val"))) + runsetflags( + minorSpec['setpath'], + {'null': False}, + lambda vin: setpath(vin.get('store'), vin.get('path'), vin.get('val')), + ) def test_minor_edge_setpath(self): - x = {"y": {"z": 1, "q": 2}} - self.assertEqual(setpath(x, 'y.q', DELETE), {"z": 1}) - self.assertEqual(x, {"y": {"z": 1}}) + x = {'y': {'z': 1, 'q': 2}} + self.assertEqual(setpath(x, 'y.q', DELETE), {'z': 1}) + self.assertEqual(x, {'y': {'z': 1}}) def test_minor_filter(self): checkmap = { 'gt3': lambda n: n[1] > 3, 'lt3': lambda n: n[1] < 3, } - runset(minorSpec["filter"], - lambda vin: filter_fn(vin.get("val"), checkmap[vin.get("check")])) + runset( + minorSpec['filter'], lambda vin: filter_fn(vin.get('val'), checkmap[vin.get('check')]) + ) def test_minor_typename(self): - runset(minorSpec["typename"], typename) + runset(minorSpec['typename'], typename) def test_minor_flatten(self): - runset(minorSpec["flatten"], - lambda vin: flatten(vin.get("val"), vin.get("depth"))) + runset(minorSpec['flatten'], lambda vin: flatten(vin.get('val'), vin.get('depth'))) # walk tests # ========== def test_walk_log(self): - test_data = clone(walkSpec["log"]) - + test_data = clone(walkSpec['log']) + log = [] - + def walklog(key, val, parent, path): - log.append('k=' + stringify(key) + - ', v=' + stringify(val) + - ', p=' + stringify(parent) + - ', t=' + pathify(path)) + log.append( + 'k=' + + stringify(key) + + ', v=' + + stringify(val) + + ', p=' + + stringify(parent) + + ', t=' + + pathify(path) + ) return val - + # Test after callback (Python walk only supports after, not before) # TODO: Python walk() needs to be updated to support before/after callbacks like TypeScript - walk(test_data["in"], walklog) - self.assertEqual(log, test_data["out"]["after"]) - + walk(test_data['in'], walklog) + self.assertEqual(log, test_data['out']['after']) + def test_walk_basic(self): def walkpath(_key, val, _parent, path): if isinstance(val, str): @@ -380,7 +391,7 @@ def walkpath(_key, val, _parent, path): def walk_wrapper(vin=None): return walk(vin, walkpath) - runset(walkSpec["basic"], walk_wrapper) + runset(walkSpec['basic'], walk_wrapper) def test_walk_copy(self): cur = [None] @@ -407,7 +418,7 @@ def walk_copy_wrapper(vin=None): walk(vin, before=walkcopy) return cur[0][0] - runset(walkSpec["copy"], walk_copy_wrapper) + runset(walkSpec['copy'], walk_copy_wrapper) def test_walk_depth(self): def walk_depth_wrapper(vin): @@ -425,78 +436,78 @@ def copy(key, val, _parent, _path): state['cur'][key] = val return val - walk(vin.get("src"), before=copy, maxdepth=vin.get("maxdepth")) + walk(vin.get('src'), before=copy, maxdepth=vin.get('maxdepth')) return state['top'] - runsetflags(walkSpec["depth"], {"null": False}, walk_depth_wrapper) + runsetflags(walkSpec['depth'], {'null': False}, walk_depth_wrapper) # merge tests # =========== - - def test_merge_basic(self): - test_data = clone(spec["merge"]["basic"]) - self.assertEqual(merge(test_data["in"]), test_data["out"]) + test_data = clone(spec['merge']['basic']) + self.assertEqual(merge(test_data['in']), test_data['out']) def test_merge_cases(self): - runset(spec["merge"]["cases"], merge) + runset(spec['merge']['cases'], merge) def test_merge_array(self): - runset(spec["merge"]["array"], merge) + runset(spec['merge']['array'], merge) def test_merge_integrity(self): - runset(spec["merge"]["integrity"], merge) + runset(spec['merge']['integrity'], merge) def test_merge_depth(self): - runset(spec["merge"]["depth"], - lambda vin: merge(vin.get("val"), vin.get("depth"))) + runset(spec['merge']['depth'], lambda vin: merge(vin.get('val'), vin.get('depth'))) def test_merge_special(self): def f0(): return None + self.assertEqual(merge([f0]), f0) self.assertEqual(merge([None, f0]), f0) - self.assertEqual(merge([{"a": f0}]), {"a": f0}) + self.assertEqual(merge([{'a': f0}]), {'a': f0}) self.assertEqual(merge([[f0]]), [f0]) - self.assertEqual(merge([{"a": {"b": f0}}]), {"a": {"b": f0}}) + self.assertEqual(merge([{'a': {'b': f0}}]), {'a': {'b': f0}}) - # ------------------------------------------------- # getpath tests # ------------------------------------------------- - - def test_getpath_basic(self): def getpath_wrapper(vin): - return getpath(vin.get("store"), vin.get("path")) - runset(spec["getpath"]["basic"], getpath_wrapper) + return getpath(vin.get('store'), vin.get('path')) + + runset(spec['getpath']['basic'], getpath_wrapper) def test_getpath_relative(self): def getpath_wrapper(vin): - dpath = vin.get("dpath") + dpath = vin.get('dpath') if dpath: dpath = dpath.split('.') - injdef = {"dparent": vin.get("dparent"), "dpath": dpath} - return getpath(vin.get("store"), vin.get("path"), injdef) - runset(spec["getpath"]["relative"], getpath_wrapper) + injdef = {'dparent': vin.get('dparent'), 'dpath': dpath} + return getpath(vin.get('store'), vin.get('path'), injdef) + + runset(spec['getpath']['relative'], getpath_wrapper) def test_getpath_special(self): def getpath_wrapper(vin): - return getpath(vin.get("store"), vin.get("path"), vin.get("inj")) - runset(spec["getpath"]["special"], getpath_wrapper) + return getpath(vin.get('store'), vin.get('path'), vin.get('inj')) + + runset(spec['getpath']['special'], getpath_wrapper) def test_getpath_handler(self): def getpath_wrapper(vin): def handler(inj, val, ref, store): return val() if callable(val) else val - - return getpath({ - "$TOP": vin.get("store"), - "$FOO": lambda: "foo" - }, vin.get("path"), {"handler": handler}) - runset(spec["getpath"]["handler"], getpath_wrapper) + + return getpath( + {'$TOP': vin.get('store'), '$FOO': lambda: 'foo'}, + vin.get('path'), + {'handler': handler}, + ) + + runset(spec['getpath']['handler'], getpath_wrapper) # TODO: Add test data for getpath current and state sections # def test_getpath_current(self): @@ -507,7 +518,7 @@ def handler(inj, val, ref, store): # def test_getpath_state(self): # def handler_fn(state, val, _current=None, _ref=None, _store=None): # out = f"{state.meta['step']}:{val}" - # state.meta["step"] = state.meta["step"]+1 + # state.meta["step"] = state.meta["step"]+1 # return out # state = Injection( @@ -533,22 +544,22 @@ def handler(inj, val, ref, store): # inject tests # ------------------------------------------------- - - def test_inject_basic(self): - test_data = clone(spec["inject"]["basic"]) - self.assertEqual( - inject(test_data["in"]["val"], test_data["in"]["store"]), - test_data["out"] - ) + test_data = clone(spec['inject']['basic']) + self.assertEqual(inject(test_data['in']['val'], test_data['in']['store']), test_data['out']) def test_inject_string(self): def inject_wrapper(vin): - return inject(vin.get("val"), vin.get("store"), {"modify": nullModifier, "extra": vin.get("current")}) - runset(spec["inject"]["string"], inject_wrapper) + return inject( + vin.get('val'), + vin.get('store'), + {'modify': nullModifier, 'extra': vin.get('current')}, + ) + + runset(spec['inject']['string'], inject_wrapper) def test_inject_deep(self): - runset(spec["inject"]["deep"], lambda vin: inject(vin.get("val"), vin.get("store"))) + runset(spec['inject']['deep'], lambda vin: inject(vin.get('val'), vin.get('store'))) # ------------------------------------------------- # transform tests @@ -556,49 +567,56 @@ def test_inject_deep(self): # ------------------------------------------------- def test_transform_basic(self): - test_data = clone(spec["transform"]["basic"]) - test_data_in = test_data.get("in") + test_data = clone(spec['transform']['basic']) + test_data_in = test_data.get('in') self.assertEqual( transform( - test_data_in.get("data"), - test_data_in.get("spec"), - test_data_in.get("store") + test_data_in.get('data'), test_data_in.get('spec'), test_data_in.get('store') ), - test_data["out"] + test_data['out'], ) def test_transform_paths(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec"), vin.get("store")) - runset(spec["transform"]["paths"], transform_wrapper) + return transform(vin.get('data'), vin.get('spec'), vin.get('store')) + + runset(spec['transform']['paths'], transform_wrapper) def test_transform_cmds(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec"), vin.get("store")) - runset(spec["transform"]["cmds"], transform_wrapper) + return transform(vin.get('data'), vin.get('spec'), vin.get('store')) + + runset(spec['transform']['cmds'], transform_wrapper) def test_transform_each(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec"), vin.get("store")) - runset(spec["transform"]["each"], transform_wrapper) + return transform(vin.get('data'), vin.get('spec'), vin.get('store')) + + runset(spec['transform']['each'], transform_wrapper) def test_transform_pack(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec"), vin.get("store")) - runset(spec["transform"]["pack"], transform_wrapper) + return transform(vin.get('data'), vin.get('spec'), vin.get('store')) + + runset(spec['transform']['pack'], transform_wrapper) def test_transform_ref(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec"), vin.get("store")) - runset(spec["transform"]["ref"], transform_wrapper) + return transform(vin.get('data'), vin.get('spec'), vin.get('store')) + + runset(spec['transform']['ref'], transform_wrapper) def test_transform_modify(self): def modifier(val, key, parent, inj): if key is not None and parent is not None and isinstance(val, str): parent[key] = '@' + val - runset(spec["transform"]["modify"], - lambda vin: transform(vin.get("data"), vin.get("spec"), {"modify": modifier, "extra": vin.get("store")})) + runset( + spec['transform']['modify'], + lambda vin: transform( + vin.get('data'), vin.get('spec'), {'modify': modifier, 'extra': vin.get('store')} + ), + ) def test_transform_extra(self): def upper_func(state, val, current, ref, store): @@ -608,27 +626,24 @@ def upper_func(state, val, current, ref, store): self.assertEqual( transform( - {"a": 1}, - {"x": "`a`", "b": "`$COPY`", "c": "`$UPPER`"}, - { - "extra": { - "b": 2, - "$UPPER": upper_func - } - } + {'a': 1}, + {'x': '`a`', 'b': '`$COPY`', 'c': '`$UPPER`'}, + {'extra': {'b': 2, '$UPPER': upper_func}}, ), - {"x": 1, "b": 2, "c": "C"} + {'x': 1, 'b': 2, 'c': 'C'}, ) def test_transform_format(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec")) - runsetflags(spec["transform"]["format"], {"null": False}, transform_wrapper) + return transform(vin.get('data'), vin.get('spec')) + + runsetflags(spec['transform']['format'], {'null': False}, transform_wrapper) def test_transform_apply(self): def transform_wrapper(vin): - return transform(vin.get("data"), vin.get("spec")) - runset(spec["transform"]["apply"], transform_wrapper) + return transform(vin.get('data'), vin.get('spec')) + + runset(spec['transform']['apply'], transform_wrapper) def test_transform_edge_apply(self): self.assertEqual(2, transform({}, ['`$APPLY`', lambda v: 1 + v, 1])) @@ -637,91 +652,90 @@ def test_transform_funcval(self): def f0(): return 99 - self.assertEqual(transform({}, {"x": 1}), {"x": 1}) - self.assertEqual(transform({}, {"x": f0}), {"x": f0}) - self.assertEqual(transform({"a": 1}, {"x": "`a`"}), {"x": 1}) - self.assertEqual(transform({"f0": f0}, {"x": "`f0`"}), {"x": f0}) + self.assertEqual(transform({}, {'x': 1}), {'x': 1}) + self.assertEqual(transform({}, {'x': f0}), {'x': f0}) + self.assertEqual(transform({'a': 1}, {'x': '`a`'}), {'x': 1}) + self.assertEqual(transform({'f0': f0}, {'x': '`f0`'}), {'x': f0}) # ------------------------------------------------- # validate tests # ------------------------------------------------- - - def test_validate_basic(self): def validate_wrapper(vin): - return validate(vin.get("data"), vin.get("spec")) - runsetflags(spec["validate"]["basic"], {"null": False}, validate_wrapper) + return validate(vin.get('data'), vin.get('spec')) + + runsetflags(spec['validate']['basic'], {'null': False}, validate_wrapper) - def test_validate_child(self): def validate_wrapper(vin): - return validate(vin.get("data"), vin.get("spec")) - runset(spec["validate"]["child"], validate_wrapper) - + return validate(vin.get('data'), vin.get('spec')) + + runset(spec['validate']['child'], validate_wrapper) + def test_validate_one(self): def validate_wrapper(vin): - return validate(vin.get("data"), vin.get("spec")) - runset(spec["validate"]["one"], validate_wrapper) - + return validate(vin.get('data'), vin.get('spec')) + + runset(spec['validate']['one'], validate_wrapper) + def test_validate_exact(self): def validate_wrapper(vin): - return validate(vin.get("data"), vin.get("spec")) - runset(spec["validate"]["exact"], validate_wrapper) + return validate(vin.get('data'), vin.get('spec')) + + runset(spec['validate']['exact'], validate_wrapper) - def test_validate_invalid(self): - runsetflags(spec["validate"]["invalid"], {"null": False}, - lambda vin: validate(vin.get("data"), vin.get("spec"))) + runsetflags( + spec['validate']['invalid'], + {'null': False}, + lambda vin: validate(vin.get('data'), vin.get('spec')), + ) def test_validate_special(self): def validate_wrapper(vin): - return validate(vin.get("data"), vin.get("spec"), vin.get("inj")) - runset(spec["validate"]["special"], validate_wrapper) + return validate(vin.get('data'), vin.get('spec'), vin.get('inj')) + + runset(spec['validate']['special'], validate_wrapper) - def test_validate_custom(self): errs = [] def integer_check(state, _val, current, _ref, _store): key = state.key out = getprop(current, key) - + if not isinstance(out, int) and not (isinstance(out, float) and out.is_integer()): - state.errs.append( - f"Not an integer at {'.'.join(state.path[1:])}: {out}" - ) + state.errs.append(f'Not an integer at {".".join(state.path[1:])}: {out}') return None - + return out - extra = { - "$INTEGER": integer_check - } + extra = {'$INTEGER': integer_check} + + shape = {'a': '`$INTEGER`'} - shape = {"a": "`$INTEGER`"} - # Test with valid integer - out = validate({"a": 1}, shape, {"extra": extra, "errs": errs}) - self.assertEqual(out, {"a": 1}) + out = validate({'a': 1}, shape, {'extra': extra, 'errs': errs}) + self.assertEqual(out, {'a': 1}) self.assertEqual(len(errs), 0) # Test with invalid value - out = validate({"a": "A"}, shape, {"extra": extra, "errs": errs}) - self.assertEqual(out, {"a": "A"}) - self.assertEqual(errs, ["Not an integer at a: A"]) + out = validate({'a': 'A'}, shape, {'extra': extra, 'errs': errs}) + self.assertEqual(out, {'a': 'A'}) + self.assertEqual(errs, ['Not an integer at a: A']) def test_validate_edge(self): errs = [] - validate({"x": 1}, {"x": '`$INSTANCE`'}, {"errs": errs}) + validate({'x': 1}, {'x': '`$INSTANCE`'}, {'errs': errs}) self.assertEqual(errs[0], 'Expected field x to be instance, but found integer: 1.') errs = [] - validate({"x": {}}, {"x": '`$INSTANCE`'}, {"errs": errs}) + validate({'x': {}}, {'x': '`$INSTANCE`'}, {'errs': errs}) self.assertEqual(errs[0], 'Expected field x to be instance, but found map: {}.') errs = [] - validate({"x": []}, {"x": '`$INSTANCE`'}, {"errs": errs}) + validate({'x': []}, {'x': '`$INSTANCE`'}, {'errs': errs}) self.assertEqual(errs[0], 'Expected field x to be instance, but found list: [].') # ------------------------------------------------- @@ -730,58 +744,58 @@ def test_validate_edge(self): def test_select_basic(self): def select_wrapper(vin): - return select(vin.get("obj"), vin.get("query")) - runset(selectSpec["basic"], select_wrapper) + return select(vin.get('obj'), vin.get('query')) + + runset(selectSpec['basic'], select_wrapper) def test_select_operators(self): def select_wrapper(vin): - return select(vin.get("obj"), vin.get("query")) - runset(selectSpec["operators"], select_wrapper) + return select(vin.get('obj'), vin.get('query')) + + runset(selectSpec['operators'], select_wrapper) def test_select_edge(self): def select_wrapper(vin): - return select(vin.get("obj"), vin.get("query")) - runset(selectSpec["edge"], select_wrapper) + return select(vin.get('obj'), vin.get('query')) + + runset(selectSpec['edge'], select_wrapper) def test_select_alts(self): def select_wrapper(vin): - return select(vin.get("obj"), vin.get("query")) - runset(selectSpec["alts"], select_wrapper) + return select(vin.get('obj'), vin.get('query')) + + runset(selectSpec['alts'], select_wrapper) # ------------------------------------------------- # JSON Builder tests # ------------------------------------------------- def test_json_builder(self): - self.assertEqual(jsonify(jo('a', 1)), '{\n "a": 1\n}') - - self.assertEqual(jsonify(ja('b', 2)), '[\n "b",\n 2\n]') - - self.assertEqual(jsonify(jo( - 'c', 'C', - 'd', jo('x', True), - 'e', ja(None, False) - )), '{\n "c": "C",\n "d": {\n "x": true\n },\n "e": [\n null,\n false\n ]\n}') - - self.assertEqual(jsonify(ja( - 3.3, jo( - 'f', True, - 'g', False, - 'h', None, - 'i', ja('y', 0), - 'j', jo('z', -1), - 'k') - )), '[\n 3.3,\n {\n "f": true,\n "g": false,\n "h": null,\n "i": [\n "y",\n 0\n ],\n "j": {\n "z": -1\n },\n "k": null\n }\n]') - - self.assertEqual(jsonify(jo( - True, 1, - False, 2, - None, 3, - ['a'], 4, - {'b': 0}, 5 - )), '{\n "true": 1,\n "false": 2,\n "null": 3,\n "[a]": 4,\n "{b:0}": 5\n}') + self.assertEqual(jsonify(jm('a', 1)), '{\n "a": 1\n}') + + self.assertEqual(jsonify(jt('b', 2)), '[\n "b",\n 2\n]') + + self.assertEqual( + jsonify(jm('c', 'C', 'd', jm('x', True), 'e', jt(None, False))), + '{\n "c": "C",\n "d": {\n "x": true\n },\n "e": [\n null,\n false\n ]\n}', + ) + + self.assertEqual( + jsonify( + jt( + 3.3, + jm('f', True, 'g', False, 'h', None, 'i', jt('y', 0), 'j', jm('z', -1), 'k'), + ) + ), + '[\n 3.3,\n {\n "f": true,\n "g": false,\n "h": null,\n "i": [\n "y",\n 0\n ],\n "j": {\n "z": -1\n },\n "k": null\n }\n]', + ) + + self.assertEqual( + jsonify(jm(True, 1, False, 2, None, 3, ['a'], 4, {'b': 0}, 5)), + '{\n "true": 1,\n "false": 2,\n "null": 3,\n "[a]": 4,\n "{b:0}": 5\n}', + ) # If you want to run this file directly, add: -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() diff --git a/py/tests/test_walk_bench.py b/py/tests/test_walk_bench.py index deb303fe..31330f6a 100644 --- a/py/tests/test_walk_bench.py +++ b/py/tests/test_walk_bench.py @@ -18,15 +18,14 @@ from voxgig_struct.voxgig_struct import walk - -BENCH = '1' == os.environ.get('WALK_BENCH', '') +BENCH = os.environ.get('WALK_BENCH', '') == '1' def buildTree(width, depth): """Build a balanced tree of maps with given width and depth. Total nodes: (width^(depth+1) - 1) / (width - 1). """ - if 0 == depth: + if depth == 0: return 0 out = {} for i in range(width): @@ -39,7 +38,7 @@ def countNodes(val): return 1 n = 1 if isinstance(val, dict): - for k in val.keys(): + for k in val: n += countNodes(val[k]) else: for v in val: @@ -77,10 +76,10 @@ def cb(_k, v, _p, path): ns_per_node = (median * 1e6) / nodes print( - f"[walk-bench] {label}: nodes={nodes} runs={runs} " - f"min={tmin:.2f}ms median={median:.2f}ms " - f"mean={mean:.2f}ms max={tmax:.2f}ms " - f"ns/node={ns_per_node:.1f} sink={sink[0]}" + f'[walk-bench] {label}: nodes={nodes} runs={runs} ' + f'min={tmin:.2f}ms median={median:.2f}ms ' + f'mean={mean:.2f}ms max={tmax:.2f}ms ' + f'ns/node={ns_per_node:.1f} sink={sink[0]}' ) @@ -91,9 +90,8 @@ def _env_int(name, default): return default -@unittest.skipUnless(BENCH, "WALK_BENCH=1 not set") +@unittest.skipUnless(BENCH, 'WALK_BENCH=1 not set') class TestWalkBench(unittest.TestCase): - def test_walk_bench_a_wide_and_deep(self): w = _env_int('WALK_BENCH_WD_WIDTH', 8) d = _env_int('WALK_BENCH_WD_DEPTH', 6) diff --git a/py/voxgig_struct/__init__.py b/py/voxgig_struct/__init__.py index 6084fc25..c8bec404 100644 --- a/py/voxgig_struct/__init__.py +++ b/py/voxgig_struct/__init__.py @@ -1,6 +1,29 @@ # voxgig_struct init from .voxgig_struct import ( + DELETE, + M_KEYPOST, + M_KEYPRE, + M_VAL, + SKIP, + Injection, + StructUtility, + T_any, + T_boolean, + T_decimal, + T_function, + T_instance, + T_integer, + T_list, + T_map, + T_node, + T_noval, + T_null, + T_number, + T_scalar, + T_string, + T_symbol, + checkPlacement, clone, delprop, escre, @@ -13,6 +36,8 @@ getprop, haskey, inject, + injectChild, + injectorArgs, isempty, isfunc, iskey, @@ -20,9 +45,7 @@ ismap, isnode, items, - ja, jm, - jo, join, joinurl, jsonify, @@ -44,29 +67,4 @@ typify, validate, walk, - Injection, - StructUtility, - checkPlacement, - injectorArgs, - injectChild, - SKIP, - DELETE, - T_any, - T_noval, - T_boolean, - T_decimal, - T_integer, - T_number, - T_string, - T_function, - T_symbol, - T_null, - T_list, - T_map, - T_instance, - T_scalar, - T_node, - M_KEYPRE, - M_KEYPOST, - M_VAL, ) diff --git a/py/voxgig_struct/voxgig_struct.py b/py/voxgig_struct/voxgig_struct.py index e0d6ea4e..a855ac3c 100644 --- a/py/voxgig_struct/voxgig_struct.py +++ b/py/voxgig_struct/voxgig_struct.py @@ -34,23 +34,23 @@ # - joinurl: join parts of a url, merging forward slashes. -from typing import * -from datetime import datetime -import urllib.parse +import inspect import json -import re import math -import inspect +import re +import urllib.parse +from datetime import datetime +from typing import Any # Regex patterns for path processing R_META_PATH = re.compile(r'^([^$]+)\$([=~])(.+)$') # Meta path syntax. -R_DOUBLE_DOLLAR = re.compile(r'\$\$') # Double dollar escape sequence. +R_DOUBLE_DOLLAR = re.compile(r'\$\$') # Double dollar escape sequence. # Mode value for inject step. -S_MKEYPRE = 'key:pre' -S_MKEYPOST = 'key:post' -S_MVAL = 'val' -S_MKEY = 'key' +S_MKEYPRE = 'key:pre' +S_MKEYPOST = 'key:post' +S_MVAL = 'val' +S_MKEY = 'key' M_KEYPRE = 1 M_KEYPOST = 2 @@ -60,18 +60,18 @@ MODENAME = {M_VAL: 'val', M_KEYPRE: 'key:pre', M_KEYPOST: 'key:post'} # Special keys. -S_DKEY = '$KEY' -S_BANNO = '`$ANNO`' -S_DTOP = '$TOP' -S_DERRS = '$ERRS' -S_DSPEC = '$SPEC' -S_BMETA = 'meta' -S_BEXACT = '`$EXACT`' +S_DKEY = '$KEY' +S_BANNO = '`$ANNO`' +S_DTOP = '$TOP' +S_DERRS = '$ERRS' +S_DSPEC = '$SPEC' +S_BMETA = 'meta' +S_BEXACT = '`$EXACT`' S_BVAL = '`$VAL`' S_BKEY = '`$KEY`' # General strings. -S_array = 'array' +S_array = 'array' S_integer = 'integer' S_decimal = 'decimal' S_map = 'map' @@ -81,23 +81,23 @@ S_node = 'node' S_scalar = 'scalar' S_any = 'any' -S_base = 'base' -S_boolean = 'boolean' -S_function = 'function' -S_number = 'number' -S_object = 'object' -S_string = 'string' -S_null = 'null' -S_key = 'key' -S_parent = 'parent' -S_MT = '' -S_BT = '`' -S_DS = '$' -S_DT = '.' -S_CM = ',' -S_CN = ':' -S_FS = '/' -S_KEY = 'KEY' +S_base = 'base' +S_boolean = 'boolean' +S_function = 'function' +S_number = 'number' +S_object = 'object' +S_string = 'string' +S_null = 'null' +S_key = 'key' +S_parent = 'parent' +S_MT = '' +S_BT = '`' +S_DS = '$' +S_DT = '.' +S_CM = ',' +S_CN = ':' +S_FS = '/' +S_KEY = 'KEY' # Type bit flags (mirroring TypeScript) @@ -131,12 +131,20 @@ S_function, 'symbol', S_null, - '', '', '', - '', '', '', '', + '', + '', + '', + '', + '', + '', + '', S_list, S_map, S_instance, - '', '', '', '', + '', + '', + '', + '', S_scalar, S_node, ] @@ -144,7 +152,7 @@ S_VIZ = ': ' # The standard undefined value for this language. -UNDEF = None +UNDEF: Any = None SKIP = {'`$SKIP`': True} DELETE = {'`$DELETE`': True} @@ -153,23 +161,24 @@ class Injection: """ Injection state used for recursive injection into JSON-like data structures. """ + def __init__( self, - mode: str, # Injection mode: key:pre, val, key:post. - full: bool, # Transform escape was full key name. - keyI: int, # Index of parent key in list of parent keys. - keys: List[str], # List of parent keys. - key: str, # Current parent key. - val: Any, # Current child value. - parent: Any, # Current parent (in transform specification). - path: List[str], # Path to current node. - nodes: List[Any], # Stack of ancestor nodes - handler: Any, # Custom handler for injections. - errs: List[Any] = None, # Error collector. - meta: Dict[str, Any] = None, # Custom meta data. - base: Optional[str] = None, # Base key for data in store, if any. - modify: Optional[Any] = None, # Modify injection output. - extra: Optional[Any] = None # Extra data for injection. + mode: str, # Injection mode: key:pre, val, key:post. + full: bool, # Transform escape was full key name. + keyI: int, # Index of parent key in list of parent keys. + keys: list[str], # List of parent keys. + key: str, # Current parent key. + val: Any, # Current child value. + parent: Any, # Current parent (in transform specification). + path: list[str], # Path to current node. + nodes: list[Any], # Stack of ancestor nodes + handler: Any, # Custom handler for injections. + errs: list[Any] = UNDEF, # Error collector. + meta: dict[str, Any] = UNDEF, # Custom meta data. + base: str | None = None, # Base key for data in store, if any. + modify: Any | None = None, # Modify injection output. + extra: Any | None = None, # Extra data for injection. ) -> None: self.mode = mode self.full = full @@ -186,10 +195,12 @@ def __init__( self.base = base self.modify = modify self.extra = extra - self.prior = None + self.prior: Injection | None = None self.dparent = UNDEF self.dpath = [S_DTOP] - self.root = None # Virtual root parent; set at top level so we can return it after transforms + self.root = ( + None # Virtual root parent; set at top level so we can return it after transforms + ) def descend(self): if '__d' not in self.meta: @@ -199,8 +210,8 @@ def descend(self): parentkey = getelem(self.path, -2) if self.dparent is UNDEF: - if 1 < size(self.dpath): - self.dpath = self.dpath + [parentkey] + if size(self.dpath) > 1: + self.dpath = [*self.dpath, parentkey] else: if parentkey is not None: self.dparent = getprop(self.dparent, parentkey) @@ -209,15 +220,15 @@ def descend(self): if lastpart == '$:' + str(parentkey): self.dpath = slice(self.dpath, -1) else: - self.dpath = self.dpath + [parentkey] + self.dpath = [*self.dpath, parentkey] return self.dparent - def child(self, keyI: int, keys: List[str]) -> 'Injection': + def child(self, keyI: int, keys: list[str]) -> 'Injection': """Create a child state object with the given key index and keys.""" key = strkey(keys[keyI]) val = self.val - + cinj = Injection( mode=self.mode, full=self.full, @@ -226,23 +237,23 @@ def child(self, keyI: int, keys: List[str]) -> 'Injection': key=key, val=getprop(val, key), parent=val, - path=self.path + [key], - nodes=self.nodes + [val], + path=[*self.path, key], + nodes=[*self.nodes, val], handler=self.handler, errs=self.errs, meta=self.meta, base=self.base, - modify=self.modify + modify=self.modify, ) cinj.prior = self cinj.dpath = self.dpath[:] cinj.dparent = self.dparent cinj.extra = self.extra # Preserve extra (contains transform functions) cinj.root = getattr(self, 'root', None) - + return cinj - def setval(self, val: Any, ancestor: Optional[int] = None) -> Any: + def setval(self, val: Any, ancestor: int | None = None) -> Any: """Set the value in the parent node at the specified ancestor level.""" if ancestor is None or ancestor < 2: return setprop(self.parent, self.key, val) @@ -281,9 +292,7 @@ def iskey(key: Any = UNDEF) -> bool: return False if isinstance(key, int): return True - if isinstance(key, float): - return True - return False + return isinstance(key, float) def size(val: Any = UNDEF) -> int: @@ -294,7 +303,7 @@ def size(val: Any = UNDEF) -> int: return len(val) elif ismap(val): return len(val.keys()) - + if isinstance(val, str): return len(val) elif isinstance(val, (int, float)): @@ -313,12 +322,10 @@ def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False) if isinstance(val, (int, float)): if start is None: start = float('-inf') - if end is None: - end = float('inf') - else: - end = end - 1 # TypeScript uses exclusive end, so subtract 1 + # TypeScript uses exclusive end, so subtract 1 when bounded. + end = float('inf') if end is None else end - 1 return max(start, min(val, end)) - + if islist(val) or isinstance(val, str): vlen = size(val) if end is not None and start is None: @@ -342,13 +349,13 @@ def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False) if vlen < start: start = vlen - if -1 < start and start <= end and end <= vlen: + if start > -1 and start <= end and end <= vlen: if islist(val) and mutate: j = start for i in range(end - start): val[i] = val[j] j += 1 - del val[end - start:] + del val[end - start :] return val return val[start:end] else: @@ -356,7 +363,7 @@ def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False) if mutate: del val[:] return val if mutate else [] - return "" + return '' # No slice performed; return original value unchanged return val @@ -367,7 +374,7 @@ def pad(s: Any, padding: int = UNDEF, padchar: str = UNDEF) -> str: s = stringify(s) padding = 44 if padding is UNDEF else padding padchar = ' ' if padchar is UNDEF else (padchar + ' ')[0] - + if padding > -1: return s.ljust(padding, padchar) else: @@ -375,7 +382,7 @@ def pad(s: Any, padding: int = UNDEF, padchar: str = UNDEF) -> str: def strkey(key: Any = UNDEF) -> str: - if UNDEF == key: + if key == UNDEF: return S_MT if isinstance(key, str): @@ -395,19 +402,16 @@ def strkey(key: Any = UNDEF) -> str: def isempty(val: Any = UNDEF) -> bool: "Check for an 'empty' value - None, empty string, array, object." - if UNDEF == val: + if val == UNDEF: return True - + if val == S_MT: return True - + if islist(val) and len(val) == 0: return True - - if ismap(val) and len(val) == 0: - return True - - return False + + return ismap(val) and len(val) == 0 def isfunc(val: Any = UNDEF) -> bool: @@ -439,6 +443,7 @@ def typify(value: Any = _TYPIFY_NO_ARG) -> int: return T_scalar | T_number | T_integer if isinstance(value, float): import math + if math.isnan(value): return T_noval return T_scalar | T_number | T_decimal @@ -460,7 +465,7 @@ def getelem(val: Any, key: Any, alt: Any = UNDEF) -> Any: """ out = UNDEF - if UNDEF == val or UNDEF == key: + if val == UNDEF or key == UNDEF: return alt if islist(val): @@ -473,8 +478,8 @@ def getelem(val: Any, key: Any, alt: Any = UNDEF) -> Any: except (ValueError, IndexError): pass - if UNDEF == out: - return alt() if 0 < (T_function & typify(alt)) else alt + if out == UNDEF: + return alt() if (T_function & typify(alt)) > 0 else alt return out @@ -484,21 +489,21 @@ def getprop(val: Any = UNDEF, key: Any = UNDEF, alt: Any = UNDEF) -> Any: Safely get a property of a node. Undefined arguments return undefined. If the key is not found, return the alternative value. """ - if UNDEF == val: + if val == UNDEF: return alt - if UNDEF == key: + if key == UNDEF: return alt out = alt - + if ismap(val): out = val.get(str(key), alt) - + elif islist(val): try: key = int(key) - except: + except (ValueError, TypeError): return alt if 0 <= key < len(val): @@ -506,9 +511,9 @@ def getprop(val: Any = UNDEF, key: Any = UNDEF, alt: Any = UNDEF) -> Any: else: return alt - if UNDEF == out: + if out == UNDEF: return alt - + return out @@ -524,9 +529,9 @@ def keysof(val: Any = UNDEF) -> list[str]: def haskey(val: Any = UNDEF, key: Any = UNDEF) -> bool: "Value of property with name key in node val is defined." - return UNDEF != getprop(val, key) + return getprop(val, key) != UNDEF + - def items(val: Any = UNDEF, apply=None): "List the keys of a map or list as an array of [key, value] tuples." if not isnode(val): @@ -536,7 +541,7 @@ def items(val: Any = UNDEF, apply=None): if apply is not None: out = [apply(item) for item in out] return out - + def flatten(lst, depth=None): if depth is None: @@ -564,26 +569,26 @@ def filter(val, check): def escre(s: Any): "Escape regular expression." - if UNDEF == s: - s = "" + if s == UNDEF: + s = '' pattern = r'([.*+?^${}()|\[\]\\])' return re.sub(pattern, r'\\\1', s) def escurl(s: Any): "Escape URLs." - if UNDEF == s: + if s == UNDEF: s = S_MT - return urllib.parse.quote(s, safe="") + return urllib.parse.quote(s, safe='') def replace(s, from_pat, to_str): "Replace a search string (all), or a regexp, in a source string." rs = s ts = typify(s) - if 0 == (T_string & ts): + if (T_string & ts) == 0: rs = stringify(s) - elif 0 < ((T_noval | T_null) & ts): + elif ((T_noval | T_null) & ts) > 0: rs = S_MT else: rs = stringify(s) @@ -597,27 +602,27 @@ def join(arr, sep=UNDEF, url=UNDEF): if not islist(arr): return S_MT sepdef = S_CM if sep is UNDEF or sep is None else sep - sepre = escre(sepdef) if 1 == size(sepdef) else UNDEF + sepre = escre(sepdef) if size(sepdef) == 1 else UNDEF sarr = size(arr) - filtered = [(i, s) for i, s in enumerate(arr) - if isinstance(s, str) and S_MT != s] + filtered = [(i, s) for i, s in enumerate(arr) if isinstance(s, str) and s != S_MT] result = [] for idx, s in filtered: - if sepre is not UNDEF and S_MT != sepre: - if url and 0 == idx: + if sepre is not UNDEF and sepre != S_MT: + if url and idx == 0: s = re.sub(sepre + '+$', S_MT, s) result.append(s) continue - if 0 < idx: + if idx > 0: s = re.sub('^' + sepre + '+', S_MT, s) if idx < sarr - 1 or not url: s = re.sub(sepre + '+$', S_MT, s) - s = re.sub('([^' + sepre + '])' + sepre + '+([^' + sepre + '])', - r'\1' + sepdef + r'\2', s) + s = re.sub( + '([^' + sepre + '])' + sepre + '+([^' + sepre + '])', r'\1' + sepdef + r'\2', s + ) - if S_MT != s: + if s != S_MT: result.append(s) return sepdef.join(result) @@ -659,43 +664,43 @@ def delprop(parent: Any, key: Any): return parent -def jsonify(val: Any = UNDEF, flags: Dict[str, Any] = None) -> str: +def jsonify(val: Any = UNDEF, flags: dict[str, Any] = UNDEF) -> str: """ Convert a value to a formatted JSON string. In general, the behavior of JavaScript's JSON.stringify(val, null, 2) is followed. """ flags = flags or {} - + if val is UNDEF: return S_null - + indent = getprop(flags, 'indent', 2) - + try: json_str = json.dumps(val, indent=indent, separators=(',', ': ') if indent else (',', ':')) except Exception: return S_null - + if json_str is None: return S_null - + offset = getprop(flags, 'offset', 0) - if 0 < offset: + if offset > 0: lines = json_str.split('\n') padded = [pad(n[1], 0 - offset - size(n[1])) for n in items(lines[1:])] json_str = '{\n' + '\n'.join(padded) - + return json_str -def jo(*kv: Any) -> Dict[str, Any]: +def jm(*kv: Any) -> dict[str, Any]: """ Define a JSON Object using function arguments. Arguments are treated as key-value pairs. """ kvsize = len(kv) o = {} - + for i in range(0, kvsize, 2): k = kv[i] if i < kvsize else f'$KEY{i}' # Handle None specially to become "null" for keys @@ -706,30 +711,25 @@ def jo(*kv: Any) -> Dict[str, Any]: else: k = stringify(k) o[k] = kv[i + 1] if i + 1 < kvsize else None - + return o -def ja(*v: Any) -> List[Any]: +def jt(*v: Any) -> list[Any]: """ Define a JSON Array using function arguments. """ vsize = len(v) a = [None] * vsize - + for i in range(vsize): a[i] = v[i] if i < vsize else None - - return a - -# Aliases to match TS canonical names -jm = jo -jt = ja + return a def select_AND(state, _val, _ref, store): - if S_MKEYPRE == state.mode: + if state.mode == S_MKEYPRE: terms = getprop(state.parent, state.key) ppath = slice(state.path, -1) point = getpath(store, ppath) @@ -739,15 +739,24 @@ def select_AND(state, _val, _ref, store): for term in terms: terrs = [] - validate(point, term, { - 'extra': vstore, - 'errs': terrs, - 'meta': state.meta, - }) - if 0 != len(terrs): + validate( + point, + term, + { + 'extra': vstore, + 'errs': terrs, + 'meta': state.meta, + }, + ) + if len(terrs) != 0: state.errs.append( - 'AND:' + pathify(ppath) + '\u2A2F' + stringify(point) + - ' fail:' + stringify(terms)) + 'AND:' + + pathify(ppath) + + '\u2a2f' + + stringify(point) + + ' fail:' + + stringify(terms) + ) gkey = getelem(state.path, -2) gp = getelem(state.nodes, -2) @@ -757,7 +766,7 @@ def select_AND(state, _val, _ref, store): def select_OR(state, _val, _ref, store): - if S_MKEYPRE == state.mode: + if state.mode == S_MKEYPRE: terms = getprop(state.parent, state.key) ppath = slice(state.path, -1) point = getpath(store, ppath) @@ -767,26 +776,30 @@ def select_OR(state, _val, _ref, store): for term in terms: terrs = [] - validate(point, term, { - 'extra': vstore, - 'errs': terrs, - 'meta': state.meta, - }) - if 0 == len(terrs): + validate( + point, + term, + { + 'extra': vstore, + 'errs': terrs, + 'meta': state.meta, + }, + ) + if len(terrs) == 0: gkey = getelem(state.path, -2) gp = getelem(state.nodes, -2) setprop(gp, gkey, point) return UNDEF state.errs.append( - 'OR:' + pathify(ppath) + '\u2A2F' + stringify(point) + - ' fail:' + stringify(terms)) + 'OR:' + pathify(ppath) + '\u2a2f' + stringify(point) + ' fail:' + stringify(terms) + ) return UNDEF def select_NOT(state, _val, _ref, store): - if S_MKEYPRE == state.mode: + if state.mode == S_MKEYPRE: term = getprop(state.parent, state.key) ppath = slice(state.path, -1) point = getpath(store, ppath) @@ -795,16 +808,20 @@ def select_NOT(state, _val, _ref, store): vstore['$TOP'] = point terrs = [] - validate(point, term, { - 'extra': vstore, - 'errs': terrs, - 'meta': state.meta, - }) + validate( + point, + term, + { + 'extra': vstore, + 'errs': terrs, + 'meta': state.meta, + }, + ) - if 0 == len(terrs): + if len(terrs) == 0: state.errs.append( - 'NOT:' + pathify(ppath) + '\u2A2F' + stringify(point) + - ' fail:' + stringify(term)) + 'NOT:' + pathify(ppath) + '\u2a2f' + stringify(point) + ' fail:' + stringify(term) + ) gkey = getelem(state.path, -2) gp = getelem(state.nodes, -2) @@ -814,7 +831,7 @@ def select_NOT(state, _val, _ref, store): def select_CMP(state, _val, ref, store): - if S_MKEYPRE == state.mode: + if state.mode == S_MKEYPRE: term = getprop(state.parent, state.key) gkey = getelem(state.path, -2) ppath = slice(state.path, -1) @@ -822,16 +839,16 @@ def select_CMP(state, _val, ref, store): pass_test = False - if '$GT' == ref and point > term: + if ( + (ref == '$GT' and point > term) + or (ref == '$LT' and point < term) + or (ref == '$GTE' and point >= term) + or (ref == '$LTE' and point <= term) + ): pass_test = True - elif '$LT' == ref and point < term: - pass_test = True - elif '$GTE' == ref and point >= term: - pass_test = True - elif '$LTE' == ref and point <= term: - pass_test = True - elif '$LIKE' == ref: + elif ref == '$LIKE': import re as re_mod + if re_mod.search(term, stringify(point)): pass_test = True @@ -840,13 +857,20 @@ def select_CMP(state, _val, ref, store): setprop(gp, gkey, point) else: state.errs.append( - 'CMP: ' + pathify(ppath) + '\u2A2F' + stringify(point) + - ' fail:' + ref + ' ' + stringify(term)) + 'CMP: ' + + pathify(ppath) + + '\u2a2f' + + stringify(point) + + ' fail:' + + ref + + ' ' + + stringify(term) + ) return UNDEF -def select(children: Any, query: Any) -> List[Any]: +def select(children: Any, query: Any) -> list[Any]: """ Select children from a top-level object that match a MongoDB-style query. Supports $and, $or, and equality comparisons. @@ -854,12 +878,12 @@ def select(children: Any, query: Any) -> List[Any]: """ if not isnode(children): return [] - + if ismap(children): children = [setprop(v, S_DKEY, k) or v for k, v in items(children)] else: children = [setprop(n, S_DKEY, i) or n if ismap(n) else n for i, n in enumerate(children)] - + results = [] injdef = { 'errs': [], @@ -873,26 +897,26 @@ def select(children: Any, query: Any) -> List[Any]: '$GTE': select_CMP, '$LTE': select_CMP, '$LIKE': select_CMP, - } + }, } - + q = clone(query) - + # Add $OPEN to all maps in the query def add_open(_k, v, _parent, _path): if ismap(v): setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', True)) return v - + walk(q, add_open) - + for child in children: injdef['errs'] = [] validate(child, clone(q), injdef) - + if size(injdef['errs']) == 0: results.append(child) - + return results @@ -902,7 +926,7 @@ def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None): pretty = bool(pretty) valstr = S_MT - if UNDEF == val: + if val == UNDEF: return '<>' if pretty else valstr if isinstance(val, str): @@ -914,9 +938,9 @@ def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None): except Exception: valstr = '__STRINGIFY_FAILED__' - if maxlen is not UNDEF and maxlen is not None and -1 < maxlen: + if maxlen is not UNDEF and maxlen is not None and maxlen > -1: js = valstr[:maxlen] - valstr = (js[:maxlen - 3] + '...') if maxlen < len(valstr) else valstr + valstr = (js[: maxlen - 3] + '...') if maxlen < len(valstr) else valstr if pretty: colors = [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69] @@ -943,29 +967,26 @@ def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None): def pathify(val: Any = UNDEF, startin: int = UNDEF, endin: int = UNDEF) -> str: pathstr = UNDEF - + # Convert input to a path array - path = val if islist(val) else \ - [val] if iskey(val) else \ - UNDEF + path = val if islist(val) else [val] if iskey(val) else UNDEF # [val] if isinstance(val, str) else \ - # [val] if isinstance(val, (int, float)) else \ + # [val] if isinstance(val, (int, float)) else \ - # Determine starting index and ending index - start = 0 if startin is UNDEF else startin if -1 < startin else 0 - end = 0 if endin is UNDEF else endin if -1 < endin else 0 + start = 0 if startin is UNDEF else startin if startin > -1 else 0 + end = 0 if endin is UNDEF else endin if endin > -1 else 0 - if UNDEF != path and 0 <= start: - path = path[start:len(path)-end] + if path != UNDEF and start >= 0: + path = path[start : len(path) - end] - if 0 == len(path): - pathstr = "" + if len(path) == 0: + pathstr = '' else: # Filter path parts to include only valid keys filtered_path = [p for p in path if iskey(p)] - + # Map path parts: convert numbers to strings and remove any dots mapped_path = [] for p in filtered_path: @@ -973,12 +994,12 @@ def pathify(val: Any = UNDEF, startin: int = UNDEF, endin: int = UNDEF) -> str: mapped_path.append(S_MT + str(int(p))) else: mapped_path.append(str(p).replace('.', S_MT)) - + pathstr = S_DT.join(mapped_path) # Handle the case where we couldn't create a path - if UNDEF == pathstr: - pathstr = f"" + if pathstr == UNDEF: + pathstr = f'' return pathstr @@ -988,7 +1009,7 @@ def clone(val: Any = UNDEF): Clone a JSON-like data structure. NOTE: function value references are copied, *not* cloned. """ - if UNDEF == val: + if val == UNDEF: return UNDEF refs = [] @@ -1004,7 +1025,7 @@ def replacer(item): elif hasattr(item, 'to_json'): return item.to_json() elif hasattr(item, '__dict__'): - return item.__dict__ + return item.__dict__ else: return item @@ -1077,16 +1098,16 @@ def setprop(parent: Any, key: Any, val: Any): def walk( - val: Any, - apply: Any = None, - key: Any = UNDEF, - parent: Any = UNDEF, - path: Any = UNDEF, - *, - before: Any = None, - after: Any = None, - maxdepth: Any = None, - pool: Any = UNDEF, + val: Any, + apply: Any = None, + key: Any = UNDEF, + parent: Any = UNDEF, + path: Any = UNDEF, + *, + before: Any = None, + after: Any = None, + maxdepth: Any = None, + pool: Any = UNDEF, ): """ Walk a data structure depth-first. @@ -1111,8 +1132,8 @@ def walk( out = val if _before is None else _before(key, val, parent, path) - md = maxdepth if maxdepth is not None and 0 <= maxdepth else MAXDEPTH - if 0 == md or (0 < md and md <= depth): + md = maxdepth if maxdepth is not None and maxdepth >= 0 else MAXDEPTH + if md == 0 or (md > 0 and md <= depth): return out if isnode(out): @@ -1128,12 +1149,16 @@ def walk( for i in range(depth): child_path[i] = path[i] - for (ckey, child) in items(out): + for ckey, child in items(out): child_path[depth] = str(ckey) result = walk( - child, key=ckey, parent=out, + child, + key=ckey, + parent=out, path=child_path, - before=_before, after=_after, maxdepth=md, + before=_before, + after=_after, + maxdepth=md, pool=pool, ) if ismap(out): @@ -1147,7 +1172,7 @@ def walk( return out -def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any: +def merge(objs: list[Any] = UNDEF, maxdepth: Any = None) -> Any: """ Merge a list of values into each other. Later values have precedence. Nodes override scalars. Node kinds (list or map) @@ -1162,9 +1187,9 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any: lenlist = len(objs) - if 0 == lenlist: + if lenlist == 0: return UNDEF - if 1 == lenlist: + if lenlist == 1: return objs[0] out = getprop(objs, 0, {}) @@ -1178,7 +1203,7 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any: cur = [out] dst = [out] - def before(key, val, _parent, path): + def before(key, val, _parent, path, cur=cur, dst=dst): pI = size(path) if md <= pI: @@ -1204,10 +1229,10 @@ def before(key, val, _parent, path): if pI >= cur_len: cur.extend([UNDEF] * (pI + 1 - cur_len)) - dst[pI] = getprop(dst[pI - 1], key) if 0 < pI else dst[pI] + dst[pI] = getprop(dst[pI - 1], key) if pI > 0 else dst[pI] tval = dst[pI] - if UNDEF == tval: + if tval == UNDEF: cur[pI] = [] if islist(val) else {} elif (islist(val) and islist(tval)) or (ismap(val) and ismap(tval)): cur[pI] = tval @@ -1217,7 +1242,7 @@ def before(key, val, _parent, path): return val - def after(key, _val, _parent, path): + def after(key, _val, _parent, path, cur=cur): cI = size(path) if cI < 1: return cur[0] if len(cur) > 0 else _val @@ -1230,7 +1255,7 @@ def after(key, _val, _parent, path): out = walk(obj, before=before, after=after) - if 0 == md: + if md == 0: out = getprop(objs, lenlist - 1, UNDEF) out = [] if islist(out) else {} if ismap(out) else out @@ -1251,7 +1276,7 @@ def getpath(store, path, injdef=UNDEF): parts = [strkey(path)] else: return UNDEF - + val = store # Support both dict-style injdef and Injection instance if isinstance(injdef, Injection): @@ -1269,33 +1294,31 @@ def getpath(store, path, injdef=UNDEF): src = getprop(store, base, store) if base else store numparts = size(parts) - + # An empty path (incl empty string) just finds the store. - if path is UNDEF or store is UNDEF or (1 == numparts and parts[0] == S_MT) or numparts == 0: + if path is UNDEF or store is UNDEF or (numparts == 1 and parts[0] == S_MT) or numparts == 0: val = src return val elif numparts > 0: - # Check for $ACTIONs - if 1 == numparts: + if numparts == 1: val = getprop(store, parts[0]) - + if not isfunc(val): val = src - + # Check for meta path syntax m = R_META_PATH.match(parts[0]) if parts[0] else None if m and inj_meta: val = getprop(inj_meta, m.group(1)) parts[0] = m.group(3) - - + for pI in range(numparts): if val is UNDEF: break - + part = parts[pI] - + # Handle special path components if injdef and part == S_DKEY: part = inj_key if inj_key is not UNDEF else part @@ -1308,55 +1331,52 @@ def getpath(store, path, injdef=UNDEF): elif injdef and isinstance(part, str) and part.startswith('$META:'): # $META:metapath$ -> get meta value, use as path part (string) part = stringify(getpath(inj_meta, part[6:-1])) - + # $$ escapes $ (path parts can be int e.g. list indices) - if isinstance(part, str): - part = R_DOUBLE_DOLLAR.sub('$', part) - else: - part = strkey(part) - + part = R_DOUBLE_DOLLAR.sub('$', part) if isinstance(part, str) else strkey(part) + if part == S_MT: ascends = 0 while pI + 1 < len(parts) and parts[pI + 1] == S_MT: ascends += 1 pI += 1 - if injdef and 0 < ascends: + if injdef and ascends > 0: if pI == len(parts) - 1: ascends -= 1 - if 0 == ascends: + if ascends == 0: val = dparent else: - fullpath = flatten( - [slice(dpath, 0 - ascends), parts[pI + 1:]]) - if ascends <= size(dpath): - val = getpath(store, fullpath) - else: - val = UNDEF + fullpath = flatten([slice(dpath, 0 - ascends), parts[pI + 1 :]]) + val = getpath(store, fullpath) if ascends <= size(dpath) else UNDEF break else: val = dparent else: val = getprop(val, part) - + # Injdef may provide a custom handler to modify found value. - handler = injdef.handler if isinstance(injdef, Injection) else (getprop(injdef, 'handler') if injdef else UNDEF) + handler = ( + injdef.handler + if isinstance(injdef, Injection) + else (getprop(injdef, 'handler') if injdef else UNDEF) + ) if handler and isfunc(handler): ref = pathify(path) val = handler(injdef, val, ref, store) - + return val def setpath(store, path, val, injdef=UNDEF): pathType = typify(path) - if 0 < (T_list & pathType): + if (T_list & pathType) > 0: parts = path - elif 0 < (T_string & pathType): + elif (T_string & pathType) > 0: parts = path.split(S_DT) - elif 0 < (T_number & pathType): + elif (T_number & pathType) > 0: parts = [path] else: return UNDEF @@ -1370,7 +1390,7 @@ def setpath(store, path, val, injdef=UNDEF): nextParent = getprop(parent, partKey) if not isnode(nextParent): nextPart = getelem(parts, pI + 1) - nextParent = [] if 0 < (T_number & typify(nextPart)) else {} + nextParent = [] if (T_number & typify(nextPart)) > 0 else {} setprop(parent, partKey, nextParent) parent = nextParent @@ -1386,8 +1406,6 @@ def inject(val, store, injdef=UNDEF): """ Inject values from `store` into `val` recursively, respecting backtick syntax. """ - valtype = type(val) - # Reuse existing injection state during recursion; otherwise create a new one. if isinstance(injdef, Injection): inj = injdef @@ -1410,7 +1428,7 @@ def inject(val, store, injdef=UNDEF): base=S_DTOP, modify=getprop(injdef, 'modify') if injdef else None, meta=getprop(injdef, 'meta', {}), - errs=getprop(store, S_DERRS, []) + errs=getprop(store, S_DERRS, []), ) inj.dparent = store inj.dpath = [S_DTOP] @@ -1433,9 +1451,9 @@ def inject(val, store, injdef=UNDEF): # Keys are sorted alphanumerically to ensure determinism. # Injection transforms ($FOO) are processed *after* other keys. if ismap(val): - normal_keys = [k for k in val.keys() if S_DS not in k] + normal_keys = [k for k in val if S_DS not in k] normal_keys.sort() - transform_keys = [k for k in val.keys() if S_DS in k] + transform_keys = [k for k in val if S_DS in k] transform_keys.sort() nodekeys = normal_keys + transform_keys else: @@ -1498,7 +1516,11 @@ def inject(val, store, injdef=UNDEF): inj.val = val # Return the (possibly transform-replaced) root only at top level (prior is None). - if getattr(inj, 'prior', None) is None and getattr(inj, 'root', None) is not None and haskey(inj.root, S_DTOP): + if ( + getattr(inj, 'prior', None) is None + and getattr(inj, 'root', None) is not None + and haskey(inj.root, S_DTOP) + ): return getprop(inj.root, S_DTOP) if inj.key == S_DTOP and inj.parent is not UNDEF and haskey(inj.parent, S_DTOP): return getprop(inj.parent, S_DTOP) @@ -1509,7 +1531,7 @@ def inject(val, store, injdef=UNDEF): # call the function passing the injection state. This is how transforms operate. def _injecthandler(inj, val, ref, store): out = val - iscmd = isfunc(val) and (UNDEF == ref or (isinstance(ref, str) and ref.startswith(S_DS))) + iscmd = isfunc(val) and (ref == UNDEF or (isinstance(ref, str) and ref.startswith(S_DS))) # Only call val function if it is a special command ($NAME format). if iscmd: @@ -1548,7 +1570,6 @@ def transform_COPY(inj, val, ref, store): """ mode = inj.mode key = inj.key - parent = inj.parent out = UNDEF if mode.startswith('key'): @@ -1648,7 +1669,7 @@ def transform_MERGE(inj, val, ref, store): # Literals in the parent have precedence, but we still merge onto # the parent object, so that node tree references are not changed. - mergelist = [parent] + args + [clone(parent)] + mergelist = [parent, *args, clone(parent)] merge(mergelist) @@ -1691,7 +1712,7 @@ def transform_EACH(inj, val, ref, store): # Source data srcstore = getprop(store, inj.base, store) src = getpath(srcstore, srcpath, inj) - + # Create parallel data structures: # source entries :: child templates tcurrent = [] @@ -1701,26 +1722,26 @@ def transform_EACH(inj, val, ref, store): target = nodes_[-2] if len(nodes_) >= 2 else nodes_[-1] rval = [] - + if isnode(src): if islist(src): tval = [clone(child_template) for _ in src] else: # Convert dict to a list of child templates tval = [] - for k, v in src.items(): + for k in src: # Keep key in meta for usage by `$KEY` copy_child = clone(child_template) if ismap(copy_child): setprop(copy_child, S_BANNO, {S_KEY: k}) tval.append(copy_child) tcurrent = list(src.values()) if ismap(src) else src - - if 0 < size(tval): + + if size(tval) > 0: # Build tcurrent structure matching TypeScript approach ckey = getelem(path, -2) if len(path) >= 2 else UNDEF tpath = path[:-1] if len(path) > 0 else [] - + # Build dpath: [S_DTOP, ...srcpath parts, '$:' + ckey] dpath = [S_DTOP] if isinstance(srcpath, str) and srcpath: @@ -1729,34 +1750,34 @@ def transform_EACH(inj, val, ref, store): dpath.append(part) if ckey is not UNDEF: dpath.append('$:' + str(ckey)) - + tcur = {ckey: tcurrent} - if 1 < size(tpath): + if size(tpath) > 1: pkey = getelem(path, -3, S_DTOP) tcur = {pkey: tcur} dpath.append('$:' + str(pkey)) - + # Create child injection state tinj = inj.child(0, [ckey] if ckey is not UNDEF else []) tinj.path = tpath tinj.nodes = nodes_[:-1] if len(nodes_) > 0 else [] tinj.parent = getelem(tinj.nodes, -1) if len(tinj.nodes) > 0 else UNDEF - + if ckey is not UNDEF and tinj.parent is not UNDEF: setprop(tinj.parent, ckey, tval) - + tinj.val = tval tinj.dpath = dpath tinj.dparent = tcur - + # Inject the entire list at once inject(tval, store, tinj) rval = tinj.val setprop(target, tkey, rval) - return rval[0] if islist(rval) and 0 < size(rval) else UNDEF + return rval[0] if islist(rval) and size(rval) > 0 else UNDEF def transform_PACK(inj, val, ref, store): @@ -1766,7 +1787,7 @@ def transform_PACK(inj, val, ref, store): parent = inj.parent nodes_ = inj.nodes - if (mode != S_MKEYPRE or not isinstance(key, str) or path is UNDEF or nodes_ is UNDEF): + if mode != S_MKEYPRE or not isinstance(key, str) or path is UNDEF or nodes_ is UNDEF: return UNDEF args_val = getprop(parent, key) @@ -1842,7 +1863,7 @@ def transform_PACK(inj, val, ref, store): tcur = {ckey: tsrc} - if 1 < size(tpath): + if size(tpath) > 1: pkey = getelem(inj.path, -3, S_DTOP) tcur = {pkey: tcur} dpath.append('$:' + str(pkey)) @@ -1869,7 +1890,6 @@ def transform_REF(inj, val, _ref, store): Format: ['`$REF`', '`spec-path`'] """ nodes = inj.nodes - modify = inj.modify if inj.mode != S_MVAL: return UNDEF @@ -1888,6 +1908,7 @@ def transform_REF(inj, val, _ref, store): # Check if ref has another $REF inside hasSubRef = False if isnode(ref): + def check_subref(k, v, parent, path): nonlocal hasSubRef if v == '`$REF`': @@ -1898,8 +1919,8 @@ def check_subref(k, v, parent, path): tref = clone(ref) - cpath = slice(inj.path, 0, len(inj.path)-3) - tpath = slice(inj.path, 0, len(inj.path)-1) + cpath = slice(inj.path, 0, len(inj.path) - 3) + tpath = slice(inj.path, 0, len(inj.path) - 1) tcur = getpath(store, cpath) tval = getpath(store, tpath) rval = UNDEF @@ -1909,7 +1930,7 @@ def check_subref(k, v, parent, path): # Create child state for the next level child_state = inj.child(0, [getelem(tpath, -1)]) child_state.path = tpath - child_state.nodes = slice(inj.nodes, 0, len(inj.nodes)-1) + child_state.nodes = slice(inj.nodes, 0, len(inj.nodes) - 1) child_state.parent = getelem(nodes, -2) child_state.val = tref @@ -1922,7 +1943,7 @@ def check_subref(k, v, parent, path): # Set the value in grandparent, using setval inj.setval(rval, 2) - + # Handle lists by decrementing keyI if islist(inj.parent) and inj.prior: inj.prior.keyI -= 1 @@ -1969,26 +1990,41 @@ def _jsstr(v): 'string': lambda _k, v, *_a: v if isnode(v) else _jsstr(v), 'number': _fmt_number, 'integer': _fmt_integer, - 'concat': lambda k, v, *_a: join( - items(v, lambda n: '' if isnode(n[1]) else _jsstr(n[1])), '') if k is None and islist(v) else v, + 'concat': lambda k, v, *_a: ( + join(items(v, lambda n: '' if isnode(n[1]) else _jsstr(n[1])), '') + if k is None and islist(v) + else v + ), } def checkPlacement(modes, ijname, parentTypes, inj): mode_num = _MODE_TO_NUM.get(inj.mode, 0) - if 0 == (modes & mode_num): + if (modes & mode_num) == 0: allowed = [m for m in [M_KEYPRE, M_KEYPOST, M_VAL] if modes & m] - placements = join( - items(allowed, lambda n: _PLACEMENT.get(n[1], '')), ',') - inj.errs.append('$' + ijname + ': invalid placement as ' + - _PLACEMENT.get(mode_num, '') + - ', expected: ' + placements + '.') + placements = join(items(allowed, lambda n: _PLACEMENT.get(n[1], '')), ',') + inj.errs.append( + '$' + + ijname + + ': invalid placement as ' + + _PLACEMENT.get(mode_num, '') + + ', expected: ' + + placements + + '.' + ) return False if not isempty(parentTypes): ptype = typify(inj.parent) - if 0 == (parentTypes & ptype): - inj.errs.append('$' + ijname + ': invalid placement in parent ' + - typename(ptype) + ', expected: ' + typename(parentTypes) + '.') + if (parentTypes & ptype) == 0: + inj.errs.append( + '$' + + ijname + + ': invalid placement in parent ' + + typename(ptype) + + ', expected: ' + + typename(parentTypes) + + '.' + ) return False return True @@ -2000,10 +2036,18 @@ def injectorArgs(argTypes, args): for argI in range(numargs): arg = getelem(args, argI) argType = typify(arg) - if 0 == (argTypes[argI] & argType): - found[0] = ('invalid argument: ' + stringify(arg, 22) + - ' (' + typename(argType) + ' at position ' + str(1 + argI) + - ') is not of type: ' + typename(argTypes[argI]) + '.') + if (argTypes[argI] & argType) == 0: + found[0] = ( + 'invalid argument: ' + + stringify(arg, 22) + + ' (' + + typename(argType) + + ' at position ' + + str(1 + argI) + + ') is not of type: ' + + typename(argTypes[argI]) + + '.' + ) break found[1 + argI] = arg return found @@ -2027,7 +2071,7 @@ def injectChild(child, store, inj): def transform_FORMAT(inj, _val, _ref, store): slice(inj.keys, 0, 1, True) - if S_MVAL != inj.mode: + if inj.mode != S_MVAL: return UNDEF name = getprop(inj.parent, 1) @@ -2039,7 +2083,7 @@ def transform_FORMAT(inj, _val, _ref, store): cinj = injectChild(child, store, inj) resolved = cinj.val - formatter = name if 0 < (T_function & typify(name)) else getprop(FORMATTER, name) + formatter = name if (T_function & typify(name)) > 0 else getprop(FORMATTER, name) if formatter is UNDEF: inj.errs.append('$FORMAT: unknown format: ' + str(name) + '.') @@ -2063,7 +2107,7 @@ def transform_APPLY(inj, _val, _ref, store): apply_fn = err_apply_child[1] child = err_apply_child[2] if len(err_apply_child) > 2 else UNDEF - if UNDEF != err: + if err != UNDEF: inj.errs.append('$' + ijname + ': ' + err) return UNDEF @@ -2089,23 +2133,23 @@ def transform_APPLY(inj, _val, _ref, store): # Transform data using spec. # Only operates on static JSON-like data. # Arrays are treated as if they are objects with indices as keys. -def transform( - data, - spec, - injdef=UNDEF -): +def transform(data, spec, injdef=UNDEF): # Clone the spec so that the clone can be modified in place as the transform result. origspec = spec spec = clone(spec) extra = getprop(injdef, 'extra') if injdef else UNDEF - collect = getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF if injdef else False + collect = ( + getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF + if injdef + else False + ) errs = getprop(injdef, 'errs') if collect else [] extraTransforms = {} - extraData = {} if UNDEF == extra else {} - + extraData = {} + if extra: for k, v in items(extra): if isinstance(k, str) and k.startswith(S_DS): @@ -2114,10 +2158,7 @@ def transform( extraData[k] = v # Combine extra data with user data - data_clone = merge([ - clone(extraData) if not isempty(extraData) else UNDEF, - clone(data) - ]) + data_clone = merge([clone(extraData) if not isempty(extraData) else UNDEF, clone(data)]) # Top-level store used by inject store = { @@ -2125,19 +2166,14 @@ def transform( # NOTE: to escape data that contains "`$FOO`" keys at the top level, # place that data inside a holding map: { myholder: mydata }. S_DTOP: data_clone, - # Original spec (before clone) for $REF to resolve refpath. S_DSPEC: lambda: origspec, - # Escape backtick (this also works inside backticks). '$BT': lambda *args, **kwargs: S_BT, - # Escape dollar sign (this also works inside backticks). '$DS': lambda *args, **kwargs: S_DS, - # Insert current date and time as an ISO string. '$WHEN': lambda *args, **kwargs: datetime.utcnow().isoformat(), - '$DELETE': transform_DELETE, '$COPY': transform_COPY, '$KEY': transform_KEY, @@ -2148,10 +2184,8 @@ def transform( '$REF': transform_REF, '$FORMAT': transform_FORMAT, '$APPLY': transform_APPLY, - # Custom extra transforms, if any. **extraTransforms, - S_DERRS: errs, } @@ -2163,7 +2197,7 @@ def transform( out = inject(spec, store, injdef) - generr = 0 < size(errs) and not collect + generr = size(errs) > 0 and not collect if generr: raise ValueError(join(errs, ' | ')) @@ -2174,11 +2208,11 @@ def validate_STRING(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): out = getprop(inj.dparent, inj.key) t = typify(out) - if 0 == (T_string & t): + if (T_string & t) == 0: inj.errs.append(_invalidTypeMsg(inj.path, S_string, t, out, 'V1010')) return UNDEF - if S_MT == out: + if out == S_MT: inj.errs.append('Empty string at ' + pathify(inj.path, 1)) return UNDEF @@ -2195,8 +2229,9 @@ def validate_STRING(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): S_map: lambda v: isinstance(v, dict), S_list: lambda v: isinstance(v, list), S_function: lambda v: callable(v) and not isinstance(v, type), - S_instance: lambda v: (not isinstance(v, (dict, list, str, int, float, bool)) - and v is not None and v is not UNDEF), + S_instance: lambda v: ( + not isinstance(v, (dict, list, str, int, float, bool)) and v is not None and v is not UNDEF + ), } @@ -2208,7 +2243,7 @@ def validate_TYPE(inj, _val=UNDEF, ref=UNDEF, _store=UNDEF): out = getprop(inj.dparent, inj.key) t = typify(out) - if 0 == (t & typev): + if (t & typev) == 0: inj.errs.append(_invalidTypeMsg(inj.path, tname, t, out, 'V1001')) return UNDEF @@ -2227,17 +2262,16 @@ def validate_CHILD(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): keys = inj.keys # Map syntax. - if S_MKEYPRE == mode: + if mode == S_MKEYPRE: childtm = getprop(parent, key) pkey = getelem(path, -2) tval = getprop(inj.dparent, pkey) - if UNDEF == tval: + if tval == UNDEF: tval = {} elif not ismap(tval): - inj.errs.append(_invalidTypeMsg( - path[:-1], S_object, typify(tval), tval, 'V0220')) + inj.errs.append(_invalidTypeMsg(path[:-1], S_object, typify(tval), tval, 'V0220')) return UNDEF ckeys = keysof(tval) @@ -2249,28 +2283,26 @@ def validate_CHILD(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): return UNDEF # List syntax. - if S_MVAL == mode: - + if mode == S_MVAL: if not islist(parent): inj.errs.append('Invalid $CHILD as value') return UNDEF childtm = getprop(parent, 1) - if UNDEF == inj.dparent: + if inj.dparent == UNDEF: del parent[:] return UNDEF if not islist(inj.dparent): - msg = _invalidTypeMsg( - path[:-1], S_list, typify(inj.dparent), inj.dparent, 'V0230') + msg = _invalidTypeMsg(path[:-1], S_list, typify(inj.dparent), inj.dparent, 'V0230') inj.errs.append(msg) inj.keyI = size(parent) return inj.dparent for n in items(inj.dparent): setprop(parent, n[0], clone(childtm)) - del parent[len(inj.dparent):] + del parent[len(inj.dparent) :] inj.keyI = 0 out = getprop(inj.dparent, 0) @@ -2284,11 +2316,13 @@ def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF): parent = inj.parent keyI = inj.keyI - if S_MVAL == mode: - if not islist(parent) or 0 != keyI: - inj.errs.append('The $ONE validator at field ' + - pathify(inj.path, 1, 1) + - ' must be the first element of an array.') + if mode == S_MVAL: + if not islist(parent) or keyI != 0: + inj.errs.append( + 'The $ONE validator at field ' + + pathify(inj.path, 1, 1) + + ' must be the first element of an array.' + ) return None inj.keyI = size(inj.keys) @@ -2299,10 +2333,12 @@ def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF): inj.key = getelem(inj.path, -1) tvals = parent[1:] - if 0 == size(tvals): - inj.errs.append('The $ONE validator at field ' + - pathify(inj.path, 1, 1) + - ' must have at least one argument.') + if size(tvals) == 0: + inj.errs.append( + 'The $ONE validator at field ' + + pathify(inj.path, 1, 1) + + ' must have at least one argument.' + ) return None for tval in tvals: @@ -2311,24 +2347,33 @@ def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF): vstore = merge([{}, store], 1) vstore[S_DTOP] = inj.dparent - vcurrent = validate(inj.dparent, tval, { - 'extra': vstore, - 'errs': terrs, - 'meta': inj.meta, - }) + vcurrent = validate( + inj.dparent, + tval, + { + 'extra': vstore, + 'errs': terrs, + 'meta': inj.meta, + }, + ) inj.setval(vcurrent, -2) - if 0 == size(terrs): + if size(terrs) == 0: return None valdesc = ', '.join(stringify(n[1]) for n in items(tvals)) valdesc = re.sub(r'`\$([A-Z]+)`', lambda m: m.group(1).lower(), valdesc) - inj.errs.append(_invalidTypeMsg( - inj.path, - ('one of ' if 1 < size(tvals) else '') + valdesc, - typify(inj.dparent), inj.dparent, 'V0210')) + inj.errs.append( + _invalidTypeMsg( + inj.path, + ('one of ' if size(tvals) > 1 else '') + valdesc, + typify(inj.dparent), + inj.dparent, + 'V0210', + ) + ) def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): @@ -2337,11 +2382,13 @@ def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): key = inj.key keyI = inj.keyI - if S_MVAL == mode: - if not islist(parent) or 0 != keyI: - inj.errs.append('The $EXACT validator at field ' + - pathify(inj.path, 1, 1) + - ' must be the first element of an array.') + if mode == S_MVAL: + if not islist(parent) or keyI != 0: + inj.errs.append( + 'The $EXACT validator at field ' + + pathify(inj.path, 1, 1) + + ' must be the first element of an array.' + ) return None inj.keyI = size(inj.keys) @@ -2352,10 +2399,12 @@ def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): inj.key = getelem(inj.path, -1) tvals = parent[1:] - if 0 == size(tvals): - inj.errs.append('The $EXACT validator at field ' + - pathify(inj.path, 1, 1) + - ' must have at least one argument.') + if size(tvals) == 0: + inj.errs.append( + 'The $EXACT validator at field ' + + pathify(inj.path, 1, 1) + + ' must have at least one argument.' + ) return None currentstr = None @@ -2373,22 +2422,24 @@ def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF): valdesc = ', '.join(stringify(n[1]) for n in items(tvals)) valdesc = re.sub(r'`\$([A-Z]+)`', lambda m: m.group(1).lower(), valdesc) - inj.errs.append(_invalidTypeMsg( - inj.path, - ('' if 1 < size(inj.path) else 'value ') + - 'exactly equal to ' + ('' if 1 == size(tvals) else 'one of ') + valdesc, - typify(inj.dparent), inj.dparent, 'V0110')) + inj.errs.append( + _invalidTypeMsg( + inj.path, + ('' if size(inj.path) > 1 else 'value ') + + 'exactly equal to ' + + ('' if size(tvals) == 1 else 'one of ') + + valdesc, + typify(inj.dparent), + inj.dparent, + 'V0110', + ) + ) else: delprop(parent, key) - -def _validation( - pval, - key, - parent, - inj -): - if UNDEF == inj: + +def _validation(pval, key, parent, inj): + if inj == UNDEF: return if pval == SKIP: @@ -2400,17 +2451,17 @@ def _validation( # Current val to verify. cval = getprop(inj.dparent, key) - if UNDEF == inj or (not exact and UNDEF == cval): + if inj == UNDEF or (not exact and cval == UNDEF): return ptype = typify(pval) - if 0 < (T_string & ptype) and S_DS in str(pval): + if (T_string & ptype) > 0 and S_DS in str(pval): return ctype = typify(cval) - if ptype != ctype and UNDEF != pval: + if ptype != ctype and pval != UNDEF: inj.errs.append(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010')) return @@ -2423,13 +2474,15 @@ def _validation( pkeys = keysof(pval) # Empty spec object {} means object can be open (any keys). - if 0 < len(pkeys) and True != getprop(pval, '`$OPEN`'): + if len(pkeys) > 0 and getprop(pval, '`$OPEN`') is not True: badkeys = [] for ckey in ckeys: if not haskey(pval, ckey): badkeys.append(ckey) - if 0 < size(badkeys): - msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ') + if size(badkeys) > 0: + msg = ( + 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ') + ) inj.errs.append(msg) else: # Object is open, so merge in extra keys. @@ -2443,9 +2496,8 @@ def _validation( elif exact: if cval != pval: - pathmsg = 'at field ' + pathify(inj.path, 1) + ': ' if 1 < size(inj.path) else '' - inj.errs.append('Value ' + pathmsg + str(cval) + - ' should equal ' + str(pval) + '.') + pathmsg = 'at field ' + pathify(inj.path, 1) + ': ' if size(inj.path) > 1 else '' + inj.errs.append('Value ' + pathmsg + str(cval) + ' should equal ' + str(pval) + '.') else: # Spec value was a default, copy over data @@ -2469,80 +2521,84 @@ def validate(data, spec, injdef=UNDEF): collect = getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF errs = getprop(injdef, 'errs') if collect else [] - - store = merge([ - { - "$DELETE": None, - "$COPY": None, - "$KEY": None, - "$META": None, - "$MERGE": None, - "$EACH": None, - "$PACK": None, - - "$STRING": validate_STRING, - "$NUMBER": validate_TYPE, - "$INTEGER": validate_TYPE, - "$DECIMAL": validate_TYPE, - "$BOOLEAN": validate_TYPE, - "$NULL": validate_TYPE, - "$NIL": validate_TYPE, - "$MAP": validate_TYPE, - "$LIST": validate_TYPE, - "$FUNCTION": validate_TYPE, - "$INSTANCE": validate_TYPE, - "$ANY": validate_ANY, - "$CHILD": validate_CHILD, - "$ONE": validate_ONE, - "$EXACT": validate_EXACT, - }, - ({} if extra is UNDEF or extra is None else extra), - - { - "$ERRS": errs, - } - ], 1) + store = merge( + [ + { + '$DELETE': None, + '$COPY': None, + '$KEY': None, + '$META': None, + '$MERGE': None, + '$EACH': None, + '$PACK': None, + '$STRING': validate_STRING, + '$NUMBER': validate_TYPE, + '$INTEGER': validate_TYPE, + '$DECIMAL': validate_TYPE, + '$BOOLEAN': validate_TYPE, + '$NULL': validate_TYPE, + '$NIL': validate_TYPE, + '$MAP': validate_TYPE, + '$LIST': validate_TYPE, + '$FUNCTION': validate_TYPE, + '$INSTANCE': validate_TYPE, + '$ANY': validate_ANY, + '$CHILD': validate_CHILD, + '$ONE': validate_ONE, + '$EXACT': validate_EXACT, + }, + ({} if extra is UNDEF or extra is None else extra), + { + '$ERRS': errs, + }, + ], + 1, + ) meta = getprop(injdef, 'meta', {}) setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, False)) - out = transform(data, spec, { - 'meta': meta, - 'extra': store, - 'modify': _validation, - 'handler': _validatehandler, - 'errs': errs, - }) + out = transform( + data, + spec, + { + 'meta': meta, + 'extra': store, + 'modify': _validation, + 'handler': _validatehandler, + 'errs': errs, + }, + ) - generr = 0 < len(errs) and not collect + generr = len(errs) > 0 and not collect if generr: raise ValueError(' | '.join(errs)) return out - # Internal utilities # ================== + def _validatehandler(inj, val, ref, store): out = val - + m = R_META_PATH.match(ref) if ref else None ismetapath = m is not None - + if ismetapath: if m.group(2) == '=': inj.setval([S_BEXACT, val]) else: inj.setval(val) inj.keyI = -1 - + out = SKIP else: out = _injecthandler(inj, val, ref, store) - + return out @@ -2550,8 +2606,8 @@ def _validatehandler(inj, val, ref, store): # when needed by implementation language. def _setparentprop(state, val): setprop(state.parent, state.key, val) - - + + # Update all references to target in state.nodes. def _updateAncestors(_state, target, tkey, tval): # SetProp is sufficient in Python as target reference remains consistent even for lists. @@ -2572,53 +2628,52 @@ def _injectstr(val, store, inj=UNDEF): full_re = re.compile(r'^`(\$[A-Z]+|[^`]*)[0-9]*`$') part_re = re.compile(r'`([^`]*)`') - if not isinstance(val, str) or S_MT == val: + if not isinstance(val, str) or val == S_MT: return S_MT out = val - + # Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`" m = full_re.match(val) - + # Full string of the val is an injection. if m: - if UNDEF != inj: + if inj != UNDEF: inj.full = True pathref = m.group(1) # Special escapes inside injection. - if 3 < len(pathref): + if len(pathref) > 3: pathref = pathref.replace(r'$BT', S_BT).replace(r'$DS', S_DS) # Get the extracted path reference. out = getpath(store, pathref, inj) else: - # Check for injections within the string. def partial(mobj): ref = mobj.group(1) # Special escapes inside injection. - if 3 < len(ref): + if len(ref) > 3: ref = ref.replace(r'$BT', S_BT).replace(r'$DS', S_DS) - - if UNDEF != inj: + + if inj != UNDEF: inj.full = False found = getpath(store, ref, inj) - + # Ensure inject value is a string. - if UNDEF == found: + if found == UNDEF: return S_MT - + if isinstance(found, str): # Convert test NULL marker to JSON 'null' when injecting into strings if found == '__NULL__': return 'null' return found - + if isfunc(found): return found @@ -2631,7 +2686,7 @@ def partial(mobj): # Also call the inj handler on the entire string, providing the # option for custom injection. - if UNDEF != inj and isfunc(inj.handler): + if inj != UNDEF and isfunc(inj.handler): inj.full = True out = inj.handler(inj, out, val, store) @@ -2641,11 +2696,13 @@ def partial(mobj): def _invalidTypeMsg(path, needtype, vt, v, _whence=None): vs = 'no value' if v is None or v is UNDEF else stringify(v) return ( - 'Expected ' + - ('field ' + pathify(path, 1) + ' to be ' if 1 < size(path) else '') + - str(needtype) + ', but found ' + - (typename(vt) + S_VIZ if v is not None and v is not UNDEF else '') + vs + - '.' + 'Expected ' + + ('field ' + pathify(path, 1) + ' to be ' if size(path) > 1 else '') + + str(needtype) + + ', but found ' + + (typename(vt) + S_VIZ if v is not None and v is not UNDEF else '') + + vs + + '.' ) @@ -2673,8 +2730,6 @@ def __init__(self): self.items = items self.jm = jm self.jt = jt - self.jo = jo - self.ja = ja self.join = join self.joinurl = joinurl self.jsonify = jsonify @@ -2719,11 +2774,32 @@ def __init__(self): self.checkPlacement = checkPlacement self.injectorArgs = injectorArgs self.injectChild = injectChild - + __all__ = [ + 'DELETE', + 'MODENAME', + 'M_KEYPOST', + 'M_KEYPRE', + 'M_VAL', + 'SKIP', 'Injection', 'StructUtility', + 'T_any', + 'T_boolean', + 'T_decimal', + 'T_function', + 'T_instance', + 'T_integer', + 'T_list', + 'T_map', + 'T_node', + 'T_noval', + 'T_null', + 'T_number', + 'T_scalar', + 'T_string', + 'T_symbol', 'checkPlacement', 'clone', 'delprop', @@ -2746,9 +2822,7 @@ def __init__(self): 'ismap', 'isnode', 'items', - 'ja', 'jm', - 'jo', 'join', 'joinurl', 'jsonify', @@ -2770,26 +2844,4 @@ def __init__(self): 'typify', 'validate', 'walk', - 'SKIP', - 'DELETE', - 'T_any', - 'T_noval', - 'T_boolean', - 'T_decimal', - 'T_integer', - 'T_number', - 'T_string', - 'T_function', - 'T_symbol', - 'T_null', - 'T_list', - 'T_map', - 'T_instance', - 'T_scalar', - 'T_node', - 'M_KEYPRE', - 'M_KEYPOST', - 'M_VAL', - 'MODENAME', ] - diff --git a/rb/.rubocop.yml b/rb/.rubocop.yml new file mode 100644 index 00000000..4fa810dc --- /dev/null +++ b/rb/.rubocop.yml @@ -0,0 +1,91 @@ +# RuboCop configuration for the Ruby port. +# https://docs.rubocop.org/ +AllCops: + TargetRubyVersion: 2.7 + NewCops: enable + SuggestExtensions: false + Exclude: + - 'vendor/**/*' + - 'pkg/**/*' + +# This is a faithful port of the canonical TypeScript implementation, so a +# number of "make it more Ruby-ish" cops are intentionally relaxed to keep +# the code cross-language comparable. +Metrics: + Enabled: false + +Style/Documentation: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Naming/MethodParameterName: + Enabled: false + +Layout/LineLength: + Max: 120 + +# --- Cross-language naming parity ------------------------------------------- +# Variables (e.g. keyI, valI), constants (e.g. T_string, S_MT, M_VAL) and +# method names (e.g. getprop, isnode, checkPlacement) deliberately mirror the +# canonical TypeScript source / public API. Renaming them would break parity +# across the language ports, so the naming cops that flag them are disabled. +Naming/VariableName: + Enabled: false + +Naming/ConstantName: + Enabled: false + +Naming/MethodName: + Enabled: false + +Naming/BlockParameterName: + Enabled: false + +Naming/PredicateMethod: + # e.g. isnode/ismap/islist/isfunc/haskey/checkPlacement mirror the TS API and + # intentionally do not use the Ruby `?` suffix. + Enabled: false + +Lint/UnderscorePrefixedVariableName: + # `_`-prefixed names (e.g. _t scratch counter, callback params that mirror the + # TS signature) are used on purpose to mark them as "incidental"; keep as-is. + Enabled: false + +# --- Faithful-port structural patterns -------------------------------------- +Style/NestedTernaryOperator: + # Nested ternaries mirror nested conditional expressions in the TS source; the + # autocorrect also breaks syntax around `return`/modifier-if contexts here. + Enabled: false + +Lint/DuplicateBranch: + # Several multi-branch dispatch chains intentionally share return values to + # stay structurally aligned with the TS implementation. + Enabled: false + +Lint/SuppressedException: + # Empty `rescue StandardError` blocks mirror empty `try { } catch { }` blocks + # in the TS source (best-effort parsing/coercion). + Enabled: false + +Style/OptionalBooleanParameter: + # Trailing boolean params (e.g. slice(..., mutate = false)) mirror the TS API + # signatures; switching to keyword args would break cross-language parity. + Enabled: false + +# --- Test scaffolding ------------------------------------------------------- +Style/OpenStructUse: + # OpenStruct is used as a lightweight test double in the test files. + Exclude: + - 'test_*.rb' + +Style/OneClassPerFile: + # Test files define a dummy client class alongside the Minitest::Test class. + Exclude: + - 'test_*.rb' + +# printf-style format strings (e.g. in walk_bench.rb) use plain %s/%d tokens to +# match the equivalent format strings in the other language ports. +Style/FormatStringToken: + EnforcedStyle: unannotated diff --git a/rb/Gemfile b/rb/Gemfile index ec902c70..42487118 100644 --- a/rb/Gemfile +++ b/rb/Gemfile @@ -6,3 +6,8 @@ ruby '>= 2.7.0' # Minitest is used for the tests gem 'minitest', '~> 5.14' + +group :development do + # Code quality: RuboCop linter/formatter. + gem 'rubocop', '~> 1.60', require: false +end diff --git a/rb/Gemfile.lock b/rb/Gemfile.lock index 8ca88753..5bfbcde3 100644 --- a/rb/Gemfile.lock +++ b/rb/Gemfile.lock @@ -1,7 +1,37 @@ GEM remote: https://rubygems.org/ specs: - minitest (5.14.2) + ast (2.4.3) + json (2.19.5) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + minitest (5.27.0) + parallel (2.1.0) + parser (3.3.11.1) + ast (~> 2.4.1) + racc + prism (1.9.0) + racc (1.8.1) + rainbow (3.1.1) + regexp_parser (2.12.0) + rubocop (1.86.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) + ruby-progressbar (1.13.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) PLATFORMS ruby @@ -9,6 +39,7 @@ PLATFORMS DEPENDENCIES minitest (~> 5.14) + rubocop (~> 1.60) RUBY VERSION ruby 3.0.2p107 diff --git a/rb/Makefile b/rb/Makefile index 14c03447..86df133c 100644 --- a/rb/Makefile +++ b/rb/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test walk-bench clean reset +.PHONY: inspect build test lint walk-bench clean reset audit inspect: @echo "Ruby version:" @@ -13,6 +13,10 @@ build: test: ruby test_voxgig_struct.rb +# Code quality: RuboCop linter/formatter. +lint: + bundle exec rubocop + walk-bench: WALK_BENCH=1 ruby walk_bench.rb @@ -21,3 +25,8 @@ clean: reset: clean rm -rf vendor + +# Supply-chain: scan dependencies against the ruby-advisory-db. +# Requires: gem install bundler-audit +audit: + bundle-audit check --update diff --git a/rb/test_client.rb b/rb/test_client.rb index d4cf5fee..5f394843 100644 --- a/rb/test_client.rb +++ b/rb/test_client.rb @@ -13,7 +13,7 @@ def utility OpenStruct.new(struct: VoxgigStruct) end - def test(options = {}) + def test(_options = {}) self end end @@ -31,4 +31,4 @@ def setup def test_client_check_basic @runset.call(@spec['basic'], @subject) end -end \ No newline at end of file +end diff --git a/rb/test_voxgig_struct.rb b/rb/test_voxgig_struct.rb index 493ea8fb..d8cd38d4 100644 --- a/rb/test_voxgig_struct.rb +++ b/rb/test_voxgig_struct.rb @@ -1,7 +1,7 @@ require 'minitest/autorun' require 'json' -require_relative 'voxgig_struct' # Loads VoxgigStruct module -require_relative 'voxgig_runner' # Loads our runner module +require_relative 'voxgig_struct' # Loads VoxgigStruct module +require_relative 'voxgig_runner' # Loads our runner module # A helper for deep equality comparison using JSON round-trip. def deep_equal(a, b) @@ -18,7 +18,7 @@ def deep_equal(a, b) end } JSON.generate(normalize.call(a)) == JSON.generate(normalize.call(b)) -rescue +rescue StandardError a == b end @@ -32,7 +32,7 @@ def utility OpenStruct.new(struct: VoxgigStruct) end - def test(options = {}) + def test(_options = {}) self end end @@ -46,11 +46,11 @@ def setup @runset = @runpack[:runset] @runsetflags = @runpack[:runsetflags] @struct = @client.utility.struct - @minor_spec = @spec["minor"] - @walk_spec = @spec["walk"] - @merge_spec = @spec["merge"] - @getpath_spec = @spec["getpath"] - @inject_spec = @spec["inject"] + @minor_spec = @spec['minor'] + @walk_spec = @spec['walk'] + @merge_spec = @spec['merge'] + @getpath_spec = @spec['getpath'] + @inject_spec = @spec['inject'] end def test_exists @@ -68,91 +68,91 @@ def test_exists # --- Minor tests --- def test_minor_isnode - @runsetflags.call(@minor_spec["isnode"], {}, VoxgigStruct.method(:isnode)) + @runsetflags.call(@minor_spec['isnode'], {}, VoxgigStruct.method(:isnode)) end def test_minor_ismap - @runsetflags.call(@minor_spec["ismap"], {}, VoxgigStruct.method(:ismap)) + @runsetflags.call(@minor_spec['ismap'], {}, VoxgigStruct.method(:ismap)) end def test_minor_islist - @runsetflags.call(@minor_spec["islist"], {}, VoxgigStruct.method(:islist)) + @runsetflags.call(@minor_spec['islist'], {}, VoxgigStruct.method(:islist)) end def test_minor_iskey - @runsetflags.call(@minor_spec["iskey"], { "null" => false }, VoxgigStruct.method(:iskey)) + @runsetflags.call(@minor_spec['iskey'], { 'null' => false }, VoxgigStruct.method(:iskey)) end def test_minor_strkey - @runsetflags.call(@minor_spec["strkey"], { "null" => false }, VoxgigStruct.method(:strkey)) + @runsetflags.call(@minor_spec['strkey'], { 'null' => false }, VoxgigStruct.method(:strkey)) end def test_minor_isempty - @runsetflags.call(@minor_spec["isempty"], { "null" => false }, VoxgigStruct.method(:isempty)) + @runsetflags.call(@minor_spec['isempty'], { 'null' => false }, VoxgigStruct.method(:isempty)) end def test_minor_isfunc - @runsetflags.call(@minor_spec["isfunc"], {}, VoxgigStruct.method(:isfunc)) - f0 = -> { nil } + @runsetflags.call(@minor_spec['isfunc'], {}, VoxgigStruct.method(:isfunc)) + f0 = -> {} assert_equal true, VoxgigStruct.isfunc(f0) assert_equal false, VoxgigStruct.isfunc(123) end def test_minor_clone - @runsetflags.call(@minor_spec["clone"], { "null" => false }, VoxgigStruct.method(:clone)) - f0 = -> { nil } - result = VoxgigStruct.clone({ "a" => f0 }) - assert_equal true, deep_equal(result, { "a" => f0 }) + @runsetflags.call(@minor_spec['clone'], { 'null' => false }, VoxgigStruct.method(:clone)) + f0 = -> {} + result = VoxgigStruct.clone({ 'a' => f0 }) + assert_equal true, deep_equal(result, { 'a' => f0 }) end def test_minor_escre - @runsetflags.call(@minor_spec["escre"], {}, VoxgigStruct.method(:escre)) + @runsetflags.call(@minor_spec['escre'], {}, VoxgigStruct.method(:escre)) end def test_minor_escurl - @runsetflags.call(@minor_spec["escurl"], {}, VoxgigStruct.method(:escurl)) + @runsetflags.call(@minor_spec['escurl'], {}, VoxgigStruct.method(:escurl)) end def test_minor_stringify - @runsetflags.call(@minor_spec["stringify"], {}, lambda { |vin| - value = vin.key?("val") ? (vin["val"] == VoxgigRunner::NULLMARK ? "null" : vin["val"]) : "" - VoxgigStruct.stringify(value, vin["max"]) + @runsetflags.call(@minor_spec['stringify'], {}, lambda { |vin| + value = vin.key?('val') ? (vin['val'] == VoxgigRunner::NULLMARK ? 'null' : vin['val']) : '' + VoxgigStruct.stringify(value, vin['max']) }) end def test_minor_pathify - @runsetflags.call(@minor_spec["pathify"], { "null" => false }, lambda { |vin| - path = vin.key?("path") ? vin["path"] : VoxgigStruct::UNDEF - VoxgigStruct.pathify(path, vin["startin"] || vin["from"], vin["endin"]) + @runsetflags.call(@minor_spec['pathify'], { 'null' => false }, lambda { |vin| + path = vin.key?('path') ? vin['path'] : VoxgigStruct::UNDEF + VoxgigStruct.pathify(path, vin['startin'] || vin['from'], vin['endin']) }) end def test_minor_items - @runsetflags.call(@minor_spec["items"], {}, VoxgigStruct.method(:items)) + @runsetflags.call(@minor_spec['items'], {}, VoxgigStruct.method(:items)) end def test_minor_getprop - @runsetflags.call(@minor_spec["getprop"], { "null" => false }, lambda { |vin| - if vin["alt"].nil? - VoxgigStruct.getprop(vin["val"], vin["key"]) + @runsetflags.call(@minor_spec['getprop'], { 'null' => false }, lambda { |vin| + if vin['alt'].nil? + VoxgigStruct.getprop(vin['val'], vin['key']) else - VoxgigStruct.getprop(vin["val"], vin["key"], vin["alt"]) + VoxgigStruct.getprop(vin['val'], vin['key'], vin['alt']) end }) end def test_minor_getelem - @runsetflags.call(@minor_spec["getelem"], { "null" => false }, lambda { |vin| - if vin.key?("alt") - VoxgigStruct.getelem(vin["val"], vin["key"], vin["alt"]) + @runsetflags.call(@minor_spec['getelem'], { 'null' => false }, lambda { |vin| + if vin.key?('alt') + VoxgigStruct.getelem(vin['val'], vin['key'], vin['alt']) else - VoxgigStruct.getelem(vin["val"], vin["key"]) + VoxgigStruct.getelem(vin['val'], vin['key']) end }) end def test_minor_edge_getprop - strarr = ['a', 'b', 'c', 'd', 'e'] + strarr = %w[a b c d e] assert deep_equal(VoxgigStruct.getprop(strarr, 2), 'c') assert deep_equal(VoxgigStruct.getprop(strarr, '2'), 'c') intarr = [2, 3, 5, 7, 11] @@ -161,117 +161,116 @@ def test_minor_edge_getprop end def test_minor_setprop - @runsetflags.call(@minor_spec["setprop"], { "null" => false }, lambda { |vin| - if vin.key?("val") - VoxgigStruct.setprop(vin["parent"], vin["key"], vin["val"]) + @runsetflags.call(@minor_spec['setprop'], { 'null' => false }, lambda { |vin| + if vin.key?('val') + VoxgigStruct.setprop(vin['parent'], vin['key'], vin['val']) else - VoxgigStruct.setprop(vin["parent"], vin["key"]) + VoxgigStruct.setprop(vin['parent'], vin['key']) end }) end def test_minor_delprop - @runsetflags.call(@minor_spec["delprop"], {}, lambda { |vin| - VoxgigStruct.delprop(vin["parent"], vin["key"]) + @runsetflags.call(@minor_spec['delprop'], {}, lambda { |vin| + VoxgigStruct.delprop(vin['parent'], vin['key']) }) end def test_minor_edge_setprop - strarr0 = ['a', 'b', 'c', 'd', 'e'] - strarr1 = ['a', 'b', 'c', 'd', 'e'] - assert deep_equal(VoxgigStruct.setprop(strarr0, 2, 'C'), ['a', 'b', 'C', 'd', 'e']) - assert deep_equal(VoxgigStruct.setprop(strarr1, '2', 'CC'), ['a', 'b', 'CC', 'd', 'e']) + strarr0 = %w[a b c d e] + strarr1 = %w[a b c d e] + assert deep_equal(VoxgigStruct.setprop(strarr0, 2, 'C'), %w[a b C d e]) + assert deep_equal(VoxgigStruct.setprop(strarr1, '2', 'CC'), %w[a b CC d e]) end def test_minor_haskey - @runsetflags.call(@minor_spec["haskey"], { "null" => false }, lambda { |vin| - VoxgigStruct.haskey(vin["src"], vin["key"]) + @runsetflags.call(@minor_spec['haskey'], { 'null' => false }, lambda { |vin| + VoxgigStruct.haskey(vin['src'], vin['key']) }) end def test_minor_keysof - @runsetflags.call(@minor_spec["keysof"], {}, VoxgigStruct.method(:keysof)) + @runsetflags.call(@minor_spec['keysof'], {}, VoxgigStruct.method(:keysof)) end def test_minor_join - @runsetflags.call(@minor_spec["join"], { "null" => false }, lambda { |vin| - VoxgigStruct.join(vin["val"], vin["sep"], vin["url"]) + @runsetflags.call(@minor_spec['join'], { 'null' => false }, lambda { |vin| + VoxgigStruct.join(vin['val'], vin['sep'], vin['url']) }) end def test_minor_jsonify - @runsetflags.call(@minor_spec["jsonify"], { "null" => false }, lambda { |vin| - VoxgigStruct.jsonify(vin["val"], vin["flags"]) + @runsetflags.call(@minor_spec['jsonify'], { 'null' => false }, lambda { |vin| + VoxgigStruct.jsonify(vin['val'], vin['flags']) }) end def test_minor_flatten - @runsetflags.call(@minor_spec["flatten"], {}, lambda { |vin| - VoxgigStruct.flatten(vin["val"], vin["depth"]) + @runsetflags.call(@minor_spec['flatten'], {}, lambda { |vin| + VoxgigStruct.flatten(vin['val'], vin['depth']) }) end def test_minor_filter checks = { - "gt3" => lambda { |item| item[1].is_a?(Numeric) && item[1] > 3 }, - "lt3" => lambda { |item| item[1].is_a?(Numeric) && item[1] < 3 }, + 'gt3' => ->(item) { item[1].is_a?(Numeric) && item[1] > 3 }, + 'lt3' => ->(item) { item[1].is_a?(Numeric) && item[1] < 3 } } - @runsetflags.call(@minor_spec["filter"], {}, lambda { |vin| - check = checks[vin["check"]] || lambda { |_item| true } - VoxgigStruct.filter(vin["val"], check) + @runsetflags.call(@minor_spec['filter'], {}, lambda { |vin| + check = checks[vin['check']] || ->(_item) { true } + VoxgigStruct.filter(vin['val'], check) }) end def test_minor_typename - @runsetflags.call(@minor_spec["typename"], {}, VoxgigStruct.method(:typename)) + @runsetflags.call(@minor_spec['typename'], {}, VoxgigStruct.method(:typename)) end def test_minor_typify - @runsetflags.call(@minor_spec["typify"], { "null" => false }, VoxgigStruct.method(:typify)) + @runsetflags.call(@minor_spec['typify'], { 'null' => false }, VoxgigStruct.method(:typify)) end def test_minor_size - @runsetflags.call(@minor_spec["size"], { "null" => false }, VoxgigStruct.method(:size)) + @runsetflags.call(@minor_spec['size'], { 'null' => false }, VoxgigStruct.method(:size)) end def test_minor_slice - @runsetflags.call(@minor_spec["slice"], { "null" => false }, lambda { |vin| - VoxgigStruct.slice(vin["val"], vin["start"], vin["end"]) + @runsetflags.call(@minor_spec['slice'], { 'null' => false }, lambda { |vin| + VoxgigStruct.slice(vin['val'], vin['start'], vin['end']) }) end def test_minor_pad - @runsetflags.call(@minor_spec["pad"], {}, lambda { |vin| - VoxgigStruct.pad(vin["val"], vin["pad"], vin["char"]) + @runsetflags.call(@minor_spec['pad'], {}, lambda { |vin| + VoxgigStruct.pad(vin['val'], vin['pad'], vin['char']) }) end def test_minor_setpath - @runsetflags.call(@minor_spec["setpath"], {}, lambda { |vin| - VoxgigStruct.setpath(vin["store"], vin["path"], vin["val"]) + @runsetflags.call(@minor_spec['setpath'], {}, lambda { |vin| + VoxgigStruct.setpath(vin['store'], vin['path'], vin['val']) }) end def test_minor_getdef - f0 = -> { nil } assert_equal 1, VoxgigStruct.getdef(1, 2) assert_equal 2, VoxgigStruct.getdef(nil, 2) - assert_equal "a", VoxgigStruct.getdef("a", "b") - assert_equal "b", VoxgigStruct.getdef(nil, "b") + assert_equal 'a', VoxgigStruct.getdef('a', 'b') + assert_equal 'b', VoxgigStruct.getdef(nil, 'b') end # --- Walk tests --- def test_walk_log - spec_log = @walk_spec["log"] - test_input = VoxgigStruct.clone(spec_log["in"]) - expected_log = spec_log["out"] + spec_log = @walk_spec['log'] + test_input = VoxgigStruct.clone(spec_log['in']) + expected_log = spec_log['out'] log = [] walklog = lambda do |key, val, parent, path| - k_str = key.nil? ? "" : VoxgigStruct.stringify(key) - p_str = parent.nil? ? "" : VoxgigStruct.stringify(parent) + k_str = key.nil? ? '' : VoxgigStruct.stringify(key) + p_str = parent.nil? ? '' : VoxgigStruct.stringify(parent) v_str = VoxgigStruct.stringify(val) t_str = VoxgigStruct.pathify(path) log << "k=#{k_str}, v=#{v_str}, p=#{p_str}, t=#{t_str}" @@ -280,29 +279,29 @@ def test_walk_log # after only VoxgigStruct.walk(test_input, nil, walklog) - assert deep_equal(log, expected_log["after"]), - "Walk log (after) failed.\nExpected: #{expected_log["after"].inspect}\nGot: #{log.inspect}" + assert deep_equal(log, expected_log['after']), + "Walk log (after) failed.\nExpected: #{expected_log['after'].inspect}\nGot: #{log.inspect}" log = [] - test_input = VoxgigStruct.clone(spec_log["in"]) + test_input = VoxgigStruct.clone(spec_log['in']) # before only VoxgigStruct.walk(test_input, walklog) - assert deep_equal(log, expected_log["before"]), - "Walk log (before) failed.\nExpected: #{expected_log["before"].inspect}\nGot: #{log.inspect}" + assert deep_equal(log, expected_log['before']), + "Walk log (before) failed.\nExpected: #{expected_log['before'].inspect}\nGot: #{log.inspect}" log = [] - test_input = VoxgigStruct.clone(spec_log["in"]) + test_input = VoxgigStruct.clone(spec_log['in']) # both VoxgigStruct.walk(test_input, walklog, walklog) - assert deep_equal(log, expected_log["both"]), - "Walk log (both) failed.\nExpected: #{expected_log["both"].inspect}\nGot: #{log.inspect}" + assert deep_equal(log, expected_log['both']), + "Walk log (both) failed.\nExpected: #{expected_log['both'].inspect}\nGot: #{log.inspect}" end def test_walk_basic - spec_basic = @walk_spec["basic"] - spec_basic["set"].each do |tc| - input = tc["in"] - expected = tc["out"] + spec_basic = @walk_spec['basic'] + spec_basic['set'].each do |tc| + input = tc['in'] + expected = tc['out'] walkpath = lambda do |_key, val, _parent, path| val.is_a?(String) ? "#{val}~#{path.join('.')}" : val @@ -314,7 +313,7 @@ def test_walk_basic end def test_walk_depth - @runsetflags.call(@walk_spec["depth"], { "null" => false }, lambda { |vin| + @runsetflags.call(@walk_spec['depth'], { 'null' => false }, lambda { |vin| top = nil cur = nil copy = lambda { |key, val, _parent, _path| @@ -331,7 +330,7 @@ def test_walk_depth end val } - VoxgigStruct.walk(vin["src"], copy, nil, vin["maxdepth"]) + VoxgigStruct.walk(vin['src'], copy, nil, vin['maxdepth']) top }) end @@ -353,7 +352,7 @@ def test_walk_copy VoxgigStruct.setprop(cur[i - 1], key, v) val } - @runsetflags.call(@walk_spec["copy"], {}, lambda { |vin| + @runsetflags.call(@walk_spec['copy'], {}, lambda { |vin| VoxgigStruct.walk(vin, walkcopy) cur[0] }) @@ -362,181 +361,179 @@ def test_walk_copy # --- Merge tests --- def test_merge_basic - spec_merge = @merge_spec["basic"] - test_input = VoxgigStruct.clone(spec_merge["in"]) - expected_output = spec_merge["out"] + spec_merge = @merge_spec['basic'] + test_input = VoxgigStruct.clone(spec_merge['in']) + expected_output = spec_merge['out'] result = VoxgigStruct.merge(test_input) assert deep_equal(result, expected_output), - "Merge basic: expected #{expected_output.inspect}, got #{result.inspect}" + "Merge basic: expected #{expected_output.inspect}, got #{result.inspect}" end def test_merge_cases - @runsetflags.call(@merge_spec["cases"], {}, VoxgigStruct.method(:merge)) + @runsetflags.call(@merge_spec['cases'], {}, VoxgigStruct.method(:merge)) end def test_merge_array - @runsetflags.call(@merge_spec["array"], {}, VoxgigStruct.method(:merge)) + @runsetflags.call(@merge_spec['array'], {}, VoxgigStruct.method(:merge)) end def test_merge_integrity - @runsetflags.call(@merge_spec["integrity"], {}, VoxgigStruct.method(:merge)) + @runsetflags.call(@merge_spec['integrity'], {}, VoxgigStruct.method(:merge)) end def test_merge_depth - @runsetflags.call(@merge_spec["depth"], {}, lambda { |vin| - VoxgigStruct.merge(vin["val"], vin["depth"]) + @runsetflags.call(@merge_spec['depth'], {}, lambda { |vin| + VoxgigStruct.merge(vin['val'], vin['depth']) }) end def test_merge_special - f0 = -> { nil } + f0 = -> {} assert deep_equal(VoxgigStruct.merge([f0]), f0) assert deep_equal(VoxgigStruct.merge([nil, f0]), f0) - assert deep_equal(VoxgigStruct.merge([{ "a" => f0 }]), { "a" => f0 }) - assert deep_equal(VoxgigStruct.merge([{ "a" => { "b" => f0 } }]), { "a" => { "b" => f0 } }) + assert deep_equal(VoxgigStruct.merge([{ 'a' => f0 }]), { 'a' => f0 }) + assert deep_equal(VoxgigStruct.merge([{ 'a' => { 'b' => f0 } }]), { 'a' => { 'b' => f0 } }) end # --- getpath tests --- def test_getpath_basic - @runsetflags.call(@getpath_spec["basic"], { "null" => false }, lambda { |vin| - VoxgigStruct.getpath(vin["store"], vin["path"]) + @runsetflags.call(@getpath_spec['basic'], { 'null' => false }, lambda { |vin| + VoxgigStruct.getpath(vin['store'], vin['path']) }) end def test_getpath_relative - @runsetflags.call(@getpath_spec["relative"], { "null" => false }, lambda { |vin| + @runsetflags.call(@getpath_spec['relative'], { 'null' => false }, lambda { |vin| injdef = {} - injdef['dparent'] = vin["dparent"] if vin.key?("dparent") - injdef['dpath'] = vin["dpath"].split('.') if vin.key?("dpath") && vin["dpath"].is_a?(String) - injdef['dpath'] = vin["dpath"] if vin.key?("dpath") && vin["dpath"].is_a?(Array) - VoxgigStruct.getpath(vin["store"], vin["path"], injdef) + injdef['dparent'] = vin['dparent'] if vin.key?('dparent') + injdef['dpath'] = vin['dpath'].split('.') if vin.key?('dpath') && vin['dpath'].is_a?(String) + injdef['dpath'] = vin['dpath'] if vin.key?('dpath') && vin['dpath'].is_a?(Array) + VoxgigStruct.getpath(vin['store'], vin['path'], injdef) }) end def test_getpath_handler - @runsetflags.call(@getpath_spec["handler"], { "null" => false }, lambda { |vin| + @runsetflags.call(@getpath_spec['handler'], { 'null' => false }, lambda { |vin| store = { - '$TOP' => vin["store"], - '$FOO' => lambda { 'foo' } + '$TOP' => vin['store'], + '$FOO' => -> { 'foo' } } injdef = { - 'handler' => lambda { |inj, val, ref, _store| + 'handler' => lambda { |_inj, val, _ref, _store| val.respond_to?(:call) ? val.call : val } } - VoxgigStruct.getpath(store, vin["path"], injdef) + VoxgigStruct.getpath(store, vin['path'], injdef) }) end def test_getpath_special - @runsetflags.call(@getpath_spec["special"], { "null" => false }, lambda { |vin| - injdef = vin.key?("inj") ? vin["inj"] : nil - VoxgigStruct.getpath(vin["store"], vin["path"], injdef) + @runsetflags.call(@getpath_spec['special'], { 'null' => false }, lambda { |vin| + injdef = vin.key?('inj') ? vin['inj'] : nil + VoxgigStruct.getpath(vin['store'], vin['path'], injdef) }) end # --- inject tests --- def test_inject_basic - basic_spec = @inject_spec["basic"] - test_input = VoxgigStruct.clone(basic_spec["in"]) - result = VoxgigStruct.inject(test_input["val"], test_input["store"]) - expected = basic_spec["out"] + basic_spec = @inject_spec['basic'] + test_input = VoxgigStruct.clone(basic_spec['in']) + result = VoxgigStruct.inject(test_input['val'], test_input['store']) + expected = basic_spec['out'] assert deep_equal(result, expected), - "Inject basic: expected #{expected.inspect}, got #{result.inspect}" + "Inject basic: expected #{expected.inspect}, got #{result.inspect}" end def test_inject_string - testcases = @inject_spec["string"]["set"] + testcases = @inject_spec['string']['set'] testcases.each do |entry| - vin = VoxgigStruct.clone(entry["in"]) - expected = entry["out"] - result = VoxgigStruct.inject(vin["val"], vin["store"]) + vin = VoxgigStruct.clone(entry['in']) + expected = entry['out'] + result = VoxgigStruct.inject(vin['val'], vin['store']) assert deep_equal(result, expected), - "Inject string: expected #{expected.inspect}, got #{result.inspect}" + "Inject string: expected #{expected.inspect}, got #{result.inspect}" end end def test_inject_deep - testcases = @inject_spec["deep"]["set"] + testcases = @inject_spec['deep']['set'] testcases.each do |entry| - vin = VoxgigStruct.clone(entry["in"]) - expected = entry["out"] - result = VoxgigStruct.inject(vin["val"], vin["store"]) + vin = VoxgigStruct.clone(entry['in']) + expected = entry['out'] + result = VoxgigStruct.inject(vin['val'], vin['store']) assert deep_equal(result, expected), - "Inject deep: expected #{expected.inspect}, got #{result.inspect}" + "Inject deep: expected #{expected.inspect}, got #{result.inspect}" end end # --- transform tests --- def test_transform_basic - basic_spec = @spec["transform"]["basic"] - test_input = VoxgigStruct.clone(basic_spec["in"]) - expected = basic_spec["out"] - result = VoxgigStruct.transform(test_input["data"], test_input["spec"]) + basic_spec = @spec['transform']['basic'] + test_input = VoxgigStruct.clone(basic_spec['in']) + expected = basic_spec['out'] + result = VoxgigStruct.transform(test_input['data'], test_input['spec']) assert deep_equal(result, expected), - "Transform basic: expected #{expected.inspect}, got #{result.inspect}" + "Transform basic: expected #{expected.inspect}, got #{result.inspect}" end def test_transform_paths - @runsetflags.call(@spec["transform"]["paths"], {}, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['paths'], {}, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_cmds - @runsetflags.call(@spec["transform"]["cmds"], {}, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['cmds'], {}, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_each - @runsetflags.call(@spec["transform"]["each"], {}, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['each'], {}, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_pack - @runsetflags.call(@spec["transform"]["pack"], {}, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['pack'], {}, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_ref - @runsetflags.call(@spec["transform"]["ref"], {}, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['ref'], {}, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_format - @runsetflags.call(@spec["transform"]["format"], { "null" => false }, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['format'], { 'null' => false }, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_apply - @runsetflags.call(@spec["transform"]["apply"], {}, lambda { |vin| - VoxgigStruct.transform(vin["data"], vin["spec"]) + @runsetflags.call(@spec['transform']['apply'], {}, lambda { |vin| + VoxgigStruct.transform(vin['data'], vin['spec']) }) end def test_transform_edge_apply - result = VoxgigStruct.transform({}, ['`$APPLY`', lambda { |v, _s, _i| 1 + v }, 1]) + result = VoxgigStruct.transform({}, ['`$APPLY`', ->(v, _s, _i) { 1 + v }, 1]) assert_equal 2, result end def test_transform_modify - @runsetflags.call(@spec["transform"]["modify"], {}, lambda { |vin| + @runsetflags.call(@spec['transform']['modify'], {}, lambda { |vin| VoxgigStruct.transform( - vin["data"], - vin["spec"], + vin['data'], + vin['spec'], { 'modify' => lambda { |val, key, parent, *_rest| - if !key.nil? && !parent.nil? && val.is_a?(String) - parent[key.to_s] = '@' + val - end + parent[key.to_s] = "@#{val}" if !key.nil? && !parent.nil? && val.is_a?(String) } } ) @@ -545,136 +542,135 @@ def test_transform_modify def test_transform_extra result = VoxgigStruct.transform( - { "a" => 1 }, - { "x" => "`a`", "b" => "`$COPY`", "c" => "`$UPPER`" }, + { 'a' => 1 }, + { 'x' => '`a`', 'b' => '`$COPY`', 'c' => '`$UPPER`' }, { 'extra' => { - "b" => 2, - "$UPPER" => lambda { |inj, *_args| + 'b' => 2, + '$UPPER' => lambda { |inj, *_args| path = inj.path VoxgigStruct.getprop(path, path.length - 1).to_s.upcase } } } ) - expected = { "x" => 1, "b" => 2, "c" => "C" } + expected = { 'x' => 1, 'b' => 2, 'c' => 'C' } assert deep_equal(result, expected), - "Transform extra: expected #{expected.inspect}, got #{result.inspect}" + "Transform extra: expected #{expected.inspect}, got #{result.inspect}" end def test_transform_funcval f0 = -> { 99 } - assert deep_equal(VoxgigStruct.transform({}, { "x" => 1 }), { "x" => 1 }) - assert deep_equal(VoxgigStruct.transform({}, { "x" => f0 }), { "x" => f0 }) - assert deep_equal(VoxgigStruct.transform({ "a" => 1 }, { "x" => "`a`" }), { "x" => 1 }) - assert deep_equal(VoxgigStruct.transform({ "f0" => f0 }, { "x" => "`f0`" }), { "x" => f0 }) + assert deep_equal(VoxgigStruct.transform({}, { 'x' => 1 }), { 'x' => 1 }) + assert deep_equal(VoxgigStruct.transform({}, { 'x' => f0 }), { 'x' => f0 }) + assert deep_equal(VoxgigStruct.transform({ 'a' => 1 }, { 'x' => '`a`' }), { 'x' => 1 }) + assert deep_equal(VoxgigStruct.transform({ 'f0' => f0 }, { 'x' => '`f0`' }), { 'x' => f0 }) end # --- validate tests --- def test_validate_basic - @runsetflags.call(@spec["validate"]["basic"], { "null" => false }, lambda { |vin| - VoxgigStruct.validate(vin["data"], vin["spec"]) + @runsetflags.call(@spec['validate']['basic'], { 'null' => false }, lambda { |vin| + VoxgigStruct.validate(vin['data'], vin['spec']) }) end def test_validate_child - @runsetflags.call(@spec["validate"]["child"], {}, lambda { |vin| - VoxgigStruct.validate(vin["data"], vin["spec"]) + @runsetflags.call(@spec['validate']['child'], {}, lambda { |vin| + VoxgigStruct.validate(vin['data'], vin['spec']) }) end def test_validate_one - @runsetflags.call(@spec["validate"]["one"], {}, lambda { |vin| - VoxgigStruct.validate(vin["data"], vin["spec"]) + @runsetflags.call(@spec['validate']['one'], {}, lambda { |vin| + VoxgigStruct.validate(vin['data'], vin['spec']) }) end def test_validate_exact - @runsetflags.call(@spec["validate"]["exact"], {}, lambda { |vin| - VoxgigStruct.validate(vin["data"], vin["spec"]) + @runsetflags.call(@spec['validate']['exact'], {}, lambda { |vin| + VoxgigStruct.validate(vin['data'], vin['spec']) }) end def test_validate_invalid - @runsetflags.call(@spec["validate"]["invalid"], { "null" => false }, lambda { |vin| - VoxgigStruct.validate(vin["data"], vin["spec"]) + @runsetflags.call(@spec['validate']['invalid'], { 'null' => false }, lambda { |vin| + VoxgigStruct.validate(vin['data'], vin['spec']) }) end def test_validate_special - @runsetflags.call(@spec["validate"]["special"], {}, lambda { |vin| - injdef = vin.key?("inj") ? vin["inj"] : nil - VoxgigStruct.validate(vin["data"], vin["spec"], injdef) + @runsetflags.call(@spec['validate']['special'], {}, lambda { |vin| + injdef = vin.key?('inj') ? vin['inj'] : nil + VoxgigStruct.validate(vin['data'], vin['spec'], injdef) }) end def test_validate_edge errs = [] - VoxgigStruct.validate({ "x" => 1 }, { "x" => '`$INSTANCE`' }, { 'errs' => errs }) + VoxgigStruct.validate({ 'x' => 1 }, { 'x' => '`$INSTANCE`' }, { 'errs' => errs }) assert_equal 'Expected field x to be instance, but found integer: 1.', errs[0] end def test_validate_custom errs = [] extra = { - "$INTEGER" => lambda { |inj, *_args| + '$INTEGER' => lambda { |inj, *_args| key = inj.key out = VoxgigStruct.getprop(inj.dparent, key) t = VoxgigStruct.typify(out) - if 0 == (VoxgigStruct::T_integer & t) - inj.errs.push("Not an integer at #{inj.path[1..-1].join('.')}: #{out}") + if VoxgigStruct::T_integer.nobits?(t) + inj.errs.push("Not an integer at #{inj.path[1..].join('.')}: #{out}") return nil end out } } - shape = { "a" => '`$INTEGER`' } + shape = { 'a' => '`$INTEGER`' } - out = VoxgigStruct.validate({ "a" => 1 }, shape, { 'extra' => extra, 'errs' => errs }) - assert deep_equal(out, { "a" => 1 }) + out = VoxgigStruct.validate({ 'a' => 1 }, shape, { 'extra' => extra, 'errs' => errs }) + assert deep_equal(out, { 'a' => 1 }) assert_equal 0, errs.length - out = VoxgigStruct.validate({ "a" => "A" }, shape, { 'extra' => extra, 'errs' => errs }) - assert deep_equal(out, { "a" => "A" }) - assert deep_equal(errs, ["Not an integer at a: A"]) + out = VoxgigStruct.validate({ 'a' => 'A' }, shape, { 'extra' => extra, 'errs' => errs }) + assert deep_equal(out, { 'a' => 'A' }) + assert deep_equal(errs, ['Not an integer at a: A']) end # --- select tests --- def test_select_basic - @runsetflags.call(@spec["select"]["basic"], {}, lambda { |vin| - VoxgigStruct.select(vin["obj"], vin["query"]) + @runsetflags.call(@spec['select']['basic'], {}, lambda { |vin| + VoxgigStruct.select(vin['obj'], vin['query']) }) end def test_select_operators - @runsetflags.call(@spec["select"]["operators"], {}, lambda { |vin| - VoxgigStruct.select(vin["obj"], vin["query"]) + @runsetflags.call(@spec['select']['operators'], {}, lambda { |vin| + VoxgigStruct.select(vin['obj'], vin['query']) }) end def test_select_edge - @runsetflags.call(@spec["select"]["edge"], {}, lambda { |vin| - VoxgigStruct.select(vin["obj"], vin["query"]) + @runsetflags.call(@spec['select']['edge'], {}, lambda { |vin| + VoxgigStruct.select(vin['obj'], vin['query']) }) end def test_select_alts - @runsetflags.call(@spec["select"]["alts"], {}, lambda { |vin| - VoxgigStruct.select(vin["obj"], vin["query"]) + @runsetflags.call(@spec['select']['alts'], {}, lambda { |vin| + VoxgigStruct.select(vin['obj'], vin['query']) }) end # --- json-builder tests --- def test_json_builder - assert deep_equal(VoxgigStruct.jm("a", 1), { "a" => 1 }) - assert deep_equal(VoxgigStruct.jm("a", 1, "b", 2), { "a" => 1, "b" => 2 }) + assert deep_equal(VoxgigStruct.jm('a', 1), { 'a' => 1 }) + assert deep_equal(VoxgigStruct.jm('a', 1, 'b', 2), { 'a' => 1, 'b' => 2 }) assert deep_equal(VoxgigStruct.jt(1, 2, 3), [1, 2, 3]) - assert deep_equal(VoxgigStruct.jt(), []) + assert deep_equal(VoxgigStruct.jt, []) end - end diff --git a/rb/voxgig_runner.rb b/rb/voxgig_runner.rb index f1c34b0d..c34a2bc1 100644 --- a/rb/voxgig_runner.rb +++ b/rb/voxgig_runner.rb @@ -3,8 +3,8 @@ require 'pathname' module VoxgigRunner - NULLMARK = "__NULL__" # Represents a JSON null in tests - UNDEFMARK = "__UNDEF__" # Represents an undefined value + NULLMARK = '__NULL__'.freeze # Represents a JSON null in tests + UNDEFMARK = '__UNDEF__'.freeze # Represents an undefined value # make_runner(testfile, client) # Returns a lambda that accepts a name (e.g. "struct") and an optional store, @@ -29,27 +29,25 @@ def self.make_runner(testfile, client) subject = testsubject || subject flags = resolve_flags(flags) testspecmap = fix_json(testspec, flags) - testset = testspecmap["set"] || [] + testset = testspecmap['set'] || [] testset.each do |entry| - begin - entry = resolve_entry(entry, flags) - # Log the test entry details if DEBUG is enabled. - puts "DEBUG: Running test entry: in=#{entry['in'].inspect} expected=#{entry['out'].inspect}" if ENV['DEBUG'] - testpack = resolve_test_pack(name, entry, subject, client, clients) - args = resolve_args(entry, testpack, struct_utils) - # Log the arguments passed to subject. - puts "DEBUG: Arguments for subject: #{args.inspect}" if ENV['DEBUG'] - # In Ruby we assume the subject is a Proc/lambda or a callable object. - res = testpack[:subject].call(*args) - entry["args"] = args - res = fix_json(res, flags) - entry["res"] = res - # Log the result obtained. - puts "DEBUG: Result obtained: #{struct_utils.stringify(res)}" if ENV['DEBUG'] - check_result(entry, res, struct_utils) - rescue => err - handle_error(entry, err, struct_utils) - end + entry = resolve_entry(entry, flags) + # Log the test entry details if DEBUG is enabled. + puts "DEBUG: Running test entry: in=#{entry['in'].inspect} expected=#{entry['out'].inspect}" if ENV['DEBUG'] + testpack = resolve_test_pack(name, entry, subject, client, clients) + args = resolve_args(entry, testpack, struct_utils) + # Log the arguments passed to subject. + puts "DEBUG: Arguments for subject: #{args.inspect}" if ENV['DEBUG'] + # In Ruby we assume the subject is a Proc/lambda or a callable object. + res = testpack[:subject].call(*args) + entry['args'] = args + res = fix_json(res, flags) + entry['res'] = res + # Log the result obtained. + puts "DEBUG: Result obtained: #{struct_utils.stringify(res)}" if ENV['DEBUG'] + check_result(entry, res, struct_utils) + rescue StandardError => e + handle_error(entry, e, struct_utils) end end @@ -66,27 +64,24 @@ def self.make_runner(testfile, client) def self.resolve_spec(name, testfile) full_path = File.join(__dir__, testfile) all_tests = JSON.parse(File.read(full_path)) - if all_tests.key?("primary") && all_tests["primary"].key?(name) - spec = all_tests["primary"][name] + if all_tests.key?('primary') && all_tests['primary'].key?(name) + all_tests['primary'][name] elsif all_tests.key?(name) - spec = all_tests[name] + all_tests[name] else - spec = all_tests + all_tests end - spec end # If the spec contains a DEF section with client definitions, resolve them. # For each defined client, obtain its test instance via client.test(options). def self.resolve_clients(client, spec, store, struct_utils) clients = {} - if spec["DEF"] && spec["DEF"]["client"] - spec["DEF"]["client"].each do |cn, cdef| - copts = (cdef["test"] && cdef["test"]["options"]) || {} + if spec['DEF'] && spec['DEF']['client'] + spec['DEF']['client'].each do |cn, cdef| + copts = (cdef['test'] && cdef['test']['options']) || {} # If there is an injection method defined, apply it. - if store.is_a?(Hash) && struct_utils.respond_to?(:inject) - struct_utils.inject(copts, store) - end + struct_utils.inject(copts, store) if store.is_a?(Hash) && struct_utils.respond_to?(:inject) clients[cn] = client.test(copts) end end @@ -108,13 +103,13 @@ def self.resolve_subject(name, container, subject = nil) # Ensure flags is a hash and set "null" flag to true if not provided. def self.resolve_flags(flags) flags ||= {} - flags["null"] = true unless flags.key?("null") + flags['null'] = true unless flags.key?('null') flags end # If the entry's "out" field is nil and the flag "null" is true, substitute NULLMARK. def self.resolve_entry(entry, flags) - entry["out"] = (entry["out"].nil? && flags["null"]) ? NULLMARK : entry["out"] + entry['out'] = entry['out'].nil? && flags['null'] ? NULLMARK : entry['out'] entry end @@ -122,42 +117,39 @@ def self.resolve_entry(entry, flags) # Uses a deep equality check (via JSON round-trip) and may use a "match" clause. def self.check_result(entry, res, struct_utils) matched = false - if entry.key?("match") - result = { "in" => entry["in"], "out" => entry["res"], "ctx" => entry["ctx"], "args" => entry["args"] } - match(entry["match"], result, struct_utils) + if entry.key?('match') + result = { 'in' => entry['in'], 'out' => entry['res'], 'ctx' => entry['ctx'], 'args' => entry['args'] } + match(entry['match'], result, struct_utils) matched = true end # Log expected and actual values before comparison. - puts "DEBUG check_result: expected=#{struct_utils.stringify(entry['out'])} actual=#{struct_utils.stringify(res)}" if ENV['DEBUG'] - - if entry["out"] == res - return + if ENV['DEBUG'] + puts "DEBUG check_result: expected=#{struct_utils.stringify(entry['out'])} actual=#{struct_utils.stringify(res)}" end - if matched && (entry["out"] == NULLMARK || entry["out"].nil?) - return - end + return if entry['out'] == res - unless deep_equal?(res, entry["out"]) - raise "Mismatch: Expected #{struct_utils.stringify(entry['out'])} but got #{struct_utils.stringify(res)}" - end + return if matched && (entry['out'] == NULLMARK || entry['out'].nil?) + + return if deep_equal?(res, entry['out']) + + raise "Mismatch: Expected #{struct_utils.stringify(entry['out'])} but got #{struct_utils.stringify(res)}" end # In case of error during test execution, handle it. def self.handle_error(entry, err, struct_utils) - entry["thrown"] = err - if entry.key?("err") - if entry["err"] === true || matchval(entry["err"], err.message, struct_utils) - if entry.key?("match") - match(entry["match"], { "in" => entry["in"], "out" => entry["res"], "ctx" => entry["ctx"], "err" => err }, struct_utils) - end - return + entry['thrown'] = err + raise err unless entry.key?('err') + + if entry['err'] == true || matchval(entry['err'], err.message, struct_utils) + if entry.key?('match') + match(entry['match'], { 'in' => entry['in'], 'out' => entry['res'], 'ctx' => entry['ctx'], 'err' => err }, + struct_utils) end - raise "ERROR MATCH: [#{struct_utils.stringify(entry['err'])}] <=> [#{err.message}]" - else - raise err + return end + raise "ERROR MATCH: [#{struct_utils.stringify(entry['err'])}] <=> [#{err.message}]" end # Resolves arguments for the test subject. @@ -165,19 +157,19 @@ def self.handle_error(entry, err, struct_utils) # If entry["ctx"] or entry["args"] is provided, use that instead. # Also, if passing an object, inject client and utility. def self.resolve_args(entry, testpack, struct_utils) - args = entry.key?("in") ? [struct_utils.clone(entry["in"])] : [VoxgigStruct::UNDEF] - if entry.key?("ctx") - args = [entry["ctx"]] - elsif entry.key?("args") - args = entry["args"] + args = entry.key?('in') ? [struct_utils.clone(entry['in'])] : [VoxgigStruct::UNDEF] + if entry.key?('ctx') + args = [entry['ctx']] + elsif entry.key?('args') + args = entry['args'] end - if entry.key?("ctx") || entry.key?("args") + if entry.key?('ctx') || entry.key?('args') first = args[0] if first.is_a?(Hash) && !first.nil? - entry["ctx"] = struct_utils.clone(first) - first["client"] = testpack[:client] - first["utility"] = testpack[:utility] + entry['ctx'] = struct_utils.clone(first) + first['client'] = testpack[:client] + first['utility'] = testpack[:utility] args[0] = first end end @@ -188,8 +180,8 @@ def self.resolve_args(entry, testpack, struct_utils) # If the entry specifies a client override, use that client's utility and subject. def self.resolve_test_pack(name, entry, subject, client, clients) testpack = { client: client, subject: subject, utility: client.utility } - if entry.key?("client") - testpack[:client] = clients[entry["client"]] + if entry.key?('client') + testpack[:client] = clients[entry['client']] testpack[:utility] = testpack[:client].utility testpack[:subject] = resolve_subject(name, testpack[:utility]) end @@ -241,8 +233,8 @@ def self.matchval(check, base, struct_utils) unless pass if check.is_a?(String) basestr = struct_utils.stringify(base) - if check =~ /^\/(.+)\/$/ - regex = Regexp.new($1) + if check =~ %r{^/(.+)/$} + regex = Regexp.new(::Regexp.last_match(1)) pass = regex.match?(basestr) else pass = basestr.downcase.include?(struct_utils.stringify(check).downcase) @@ -269,25 +261,26 @@ def self.deep_equal?(a, b) end } JSON.generate(normalize.call(a)) == JSON.generate(normalize.call(b)) - rescue + rescue StandardError a == b end # Returns a deep copy of a value via JSON round-trip. def self.fix_json(val, flags) - return flags["null"] ? NULLMARK : val if val.nil? - return flags["null"] ? NULLMARK : val if val.equal?(VoxgigStruct::UNDEF) + return flags['null'] ? NULLMARK : val if val.nil? + return flags['null'] ? NULLMARK : val if val.equal?(VoxgigStruct::UNDEF) + JSON.parse(JSON.generate(val)) - rescue + rescue StandardError val end # Applies a null modifier: if a value is "__NULL__", it replaces it with nil. def self.null_modifier(val, key, parent) - if val == "__NULL__" + if val == '__NULL__' parent[key] = nil elsif val.is_a?(String) - parent[key] = val.gsub("__NULL__", "null") + parent[key] = val.gsub('__NULL__', 'null') end end end diff --git a/rb/voxgig_struct.rb b/rb/voxgig_struct.rb index 1bf1e5bb..aaddedd9 100644 --- a/rb/voxgig_struct.rb +++ b/rb/voxgig_struct.rb @@ -4,7 +4,7 @@ module VoxgigStruct # --- Debug Logging Configuration --- DEBUG = false - + def self.log(msg) puts "[DEBUG] #{msg}" if DEBUG end @@ -15,58 +15,72 @@ def self.conv(val) end # --- Constants --- - S_MKEYPRE = 'key:pre' - S_MKEYPOST = 'key:post' - S_MVAL = 'val' - S_MKEY = 'key' - - S_DKEY = '`$KEY`' - S_DMETA = '`$META`' - S_DTOP = '$TOP' - S_DERRS = '$ERRS' - - S_any = 'any' - S_array = 'array' - S_boolean = 'boolean' - S_decimal = 'decimal' - S_function = 'function' - S_instance = 'instance' - S_integer = 'integer' - S_list = 'list' - S_map = 'map' - S_nil = 'nil' - S_node = 'node' - S_number = 'number' - S_null = 'null' - S_object = 'object' - S_scalar = 'scalar' - S_string = 'string' - S_symbol = 'symbol' - S_MT = '' # empty string constant (used as a prefix) - S_BT = '`' - S_DS = '$' - S_DT = '.' # delimiter for key paths - S_CN = ':' # colon for unknown paths - S_SP = ' ' - S_VIZ = ': ' - S_KEY = 'KEY' + S_MKEYPRE = 'key:pre'.freeze + S_MKEYPOST = 'key:post'.freeze + S_MVAL = 'val'.freeze + S_MKEY = 'key'.freeze + + S_DKEY = '`$KEY`'.freeze + S_DMETA = '`$META`'.freeze + S_DTOP = '$TOP'.freeze + S_DERRS = '$ERRS'.freeze + + S_any = 'any'.freeze + S_array = 'array'.freeze + S_boolean = 'boolean'.freeze + S_decimal = 'decimal'.freeze + S_function = 'function'.freeze + S_instance = 'instance'.freeze + S_integer = 'integer'.freeze + S_list = 'list'.freeze + S_map = 'map'.freeze + S_nil = 'nil'.freeze + S_node = 'node'.freeze + S_number = 'number'.freeze + S_null = 'null'.freeze + S_object = 'object'.freeze + S_scalar = 'scalar'.freeze + S_string = 'string'.freeze + S_symbol = 'symbol'.freeze + S_MT = ''.freeze # empty string constant (used as a prefix) + S_BT = '`'.freeze + S_DS = '$'.freeze + S_DT = '.'.freeze # delimiter for key paths + S_CN = ':'.freeze # colon for unknown paths + S_SP = ' '.freeze + S_VIZ = ': '.freeze + S_KEY = 'KEY'.freeze # Types - bitfield integers matching TypeScript canonical _t = 31 - T_any = (1 << _t) - 1; _t -= 1 - T_noval = 1 << _t; _t -= 1 - T_boolean = 1 << _t; _t -= 1 - T_decimal = 1 << _t; _t -= 1 - T_integer = 1 << _t; _t -= 1 - T_number = 1 << _t; _t -= 1 - T_string = 1 << _t; _t -= 1 - T_function = 1 << _t; _t -= 1 - T_symbol = 1 << _t; _t -= 1 - T_null = 1 << _t; _t -= 8 - T_list = 1 << _t; _t -= 1 - T_map = 1 << _t; _t -= 1 - T_instance = 1 << _t; _t -= 5 - T_scalar = 1 << _t; _t -= 1 + T_any = (1 << _t) - 1 + _t -= 1 + T_noval = 1 << _t + _t -= 1 + T_boolean = 1 << _t + _t -= 1 + T_decimal = 1 << _t + _t -= 1 + T_integer = 1 << _t + _t -= 1 + T_number = 1 << _t + _t -= 1 + T_string = 1 << _t + _t -= 1 + T_function = 1 << _t + _t -= 1 + T_symbol = 1 << _t + _t -= 1 + T_null = 1 << _t + _t -= 8 + T_list = 1 << _t + _t -= 1 + T_map = 1 << _t + _t -= 1 + T_instance = 1 << _t + _t -= 5 + T_scalar = 1 << _t + _t -= 1 T_node = 1 << _t TYPENAME = [ @@ -75,11 +89,11 @@ def self.conv(val) '', '', '', '', '', '', '', S_list, S_map, S_instance, '', '', '', '', - S_scalar, S_node, - ] + S_scalar, S_node + ].freeze - SKIP = { '`$SKIP`' => true } - DELETE = { '`$DELETE`' => true } + SKIP = { '`$SKIP`' => true }.freeze + DELETE = { '`$DELETE`' => true }.freeze # Unique undefined marker. UNDEF = Object.new.freeze @@ -111,6 +125,7 @@ def self.sorted(val) def self.clone(val) return nil if val.nil? || val.equal?(UNDEF) + if isfunc(val) val elsif islist(val) @@ -125,13 +140,13 @@ def self.clone(val) end def self.escre(s) - s = s.nil? ? "" : s + s = '' if s.nil? Regexp.escape(s) end def self.escurl(s) - s = s.nil? ? "" : s - URI::DEFAULT_PARSER.escape(s, /[^A-Za-z0-9\-\.\_\~]/) + s = '' if s.nil? + URI::DEFAULT_PARSER.escape(s, /[^A-Za-z0-9\-._~]/) end # --- Internal getprop --- @@ -139,31 +154,32 @@ def self.escurl(s) def self._getprop(val, key, alt = UNDEF) log("(_getprop) called with val=#{val.inspect} and key=#{key.inspect}") return alt if val.nil? || key.nil? + if islist(val) - key = (key.to_s =~ /\A\d+\z/) ? key.to_i : key + key = key.to_i if key.to_s =~ /\A\d+\z/ unless key.is_a?(Numeric) && key >= 0 && key < val.size log("(_getprop) index #{key.inspect} out of bounds; returning alt") return alt end result = val[key] log("(_getprop) returning #{result.inspect} from array for key #{key}") - return result + result elsif ismap(val) key_str = key.to_s if val.key?(key_str) result = val[key_str] log("(_getprop) found key #{key_str.inspect} in hash, returning #{result.inspect}") - return result + result elsif key.is_a?(String) && val.key?(key.to_sym) result = val[key.to_sym] log("(_getprop) found symbol key #{key.to_sym.inspect} in hash, returning #{result.inspect}") - return result + result else log("(_getprop) key #{key.inspect} not found; returning alt") - return alt + alt end else - log("(_getprop) value is not a node; returning alt") + log('(_getprop) value is not a node; returning alt') alt end end @@ -176,9 +192,10 @@ def self.getprop(val, key, alt = nil) end def self.isempty(val) - return true if val.nil? || val.equal?(UNDEF) || val == "" + return true if val.nil? || val.equal?(UNDEF) || val == '' return true if islist(val) && val.empty? return true if ismap(val) && val.empty? + false end @@ -212,6 +229,7 @@ def self.items(val, apply = nil) def self.setprop(parent, key, val = :no_val_provided) log(">>> setprop called with parent=#{parent.inspect}, key=#{key.inspect}, val=#{val.inspect}") return parent unless iskey(key) + if ismap(parent) key_str = key.to_s if val == :no_val_provided @@ -227,20 +245,18 @@ def self.setprop(parent, key, val = :no_val_provided) end if val == :no_val_provided parent.delete_at(key_i) if key_i >= 0 && key_i < parent.length + elsif key_i >= 0 + index = [key_i, parent.length].min + parent[index] = val else - if key_i >= 0 - index = key_i >= parent.length ? parent.length : key_i - parent[index] = val - else - parent.unshift(val) - end + parent.unshift(val) end end log("<<< setprop result: #{parent.inspect}") parent end - def self.stringify(val, maxlen = nil, pretty = nil) + def self.stringify(val, maxlen = nil, _pretty = nil) return '' if val.equal?(UNDEF) return 'null' if val.nil? @@ -256,11 +272,7 @@ def self.stringify(val, maxlen = nil, pretty = nil) end end - if !maxlen.nil? && maxlen >= 0 - if valstr.length > maxlen - valstr = valstr[0, maxlen - 3] + '...' - end - end + valstr = "#{valstr[0, maxlen - 3]}..." if !maxlen.nil? && maxlen >= 0 && (valstr.length > maxlen) valstr end @@ -269,48 +281,47 @@ def self.pathify(val, startin = nil, endin = nil) pathstr = nil path = if islist(val) - val - elsif val.is_a?(String) - [val] - elsif val.is_a?(Numeric) - [val] - else - nil - end + val + elsif val.is_a?(String) + [val] + elsif val.is_a?(Numeric) + [val] + end - start = startin.nil? ? 0 : startin < 0 ? 0 : startin - end_idx = endin.nil? ? 0 : endin < 0 ? 0 : endin + start = startin.nil? ? 0 : startin.negative? ? 0 : startin + end_idx = endin.nil? ? 0 : endin.negative? ? 0 : endin if path && start >= 0 - path = path[start..-end_idx-1] || [] - if path.empty? - pathstr = '' - else - pathstr = path - .select { |p| iskey(p) } - .map { |p| - if p.is_a?(Numeric) - S_MT + p.floor.to_s - else - p.gsub('.', S_MT) - end - } - .join(S_DT) - end + path = path[start..(-end_idx - 1)] || [] + pathstr = if path.empty? + '' + else + path + .select { |p| iskey(p) } + .map do |p| + if p.is_a?(Numeric) + S_MT + p.floor.to_s + else + p.gsub('.', S_MT) + end + end + .join(S_DT) + end end if pathstr.nil? - pathstr = '' + pathstr = "" end pathstr end def self.strkey(key = nil) - return "" if key.nil? + return '' if key.nil? return key if key.is_a?(String) return key.floor.to_s if key.is_a?(Numeric) - "" + + '' end def self.isfunc(val) @@ -325,8 +336,9 @@ def self.size(val) return 0 if val.nil? || val.equal?(UNDEF) return val.length if val.is_a?(String) || islist(val) return val.keys.length if ismap(val) - return (val == true ? 1 : 0) if val == true || val == false + return (val == true ? 1 : 0) if [true, false].include?(val) return val.to_i if val.is_a?(Numeric) + 0 end @@ -336,25 +348,26 @@ def self.slice(val, start_idx = nil, end_idx = nil, mutate = false) if val.is_a?(Numeric) && !val.is_a?(TrueClass) && !val.is_a?(FalseClass) s = start_idx.nil? ? (-Float::INFINITY) : start_idx e = end_idx.nil? ? Float::INFINITY : (end_idx - 1) - return [[val, s].max, e].min + # Not Comparable#clamp: that raises when s > e, but here we want e returned. + return [[val, s].max, e].min # rubocop:disable Style/ComparableClamp end vlen = size(val) start_idx = 0 if !end_idx.nil? && start_idx.nil? - if !start_idx.nil? + unless start_idx.nil? s = start_idx e = end_idx - if s < 0 + if s.negative? e = vlen + s - e = 0 if e < 0 + e = 0 if e.negative? s = 0 elsif !e.nil? - if e < 0 + if e.negative? e = vlen + e - e = 0 if e < 0 + e = 0 if e.negative? elsif vlen < e e = vlen end @@ -380,9 +393,9 @@ def self.slice(val, start_idx = nil, end_idx = nil, mutate = false) end def self.pad(str, padding = nil, padchar = nil) - str = str.is_a?(String) ? str : stringify(str) - padding = padding.nil? ? 44 : padding - padchar = padchar.nil? ? ' ' : (padchar.to_s + ' ')[0] + str = stringify(str) unless str.is_a?(String) + padding = 44 if padding.nil? + padchar = padchar.nil? ? ' ' : "#{padchar} "[0] if padding >= 0 str.ljust(padding, padchar) else @@ -396,24 +409,26 @@ def self.getelem(val, key, alt = UNDEF) begin nkey = key.to_i if key.to_s.strip.match?(/\A-?\d+\z/) - nkey = val.length + nkey if nkey < 0 - out = (0 <= nkey && nkey < val.length) ? val[nkey] : UNDEF + nkey = val.length + nkey if nkey.negative? + out = nkey >= 0 && nkey < val.length ? val[nkey] : UNDEF end - rescue + rescue StandardError end end if out.equal?(UNDEF) return isfunc(alt) ? alt.call : (alt.equal?(UNDEF) ? nil : alt) end + out end def self.flatten(lst, depth = nil) depth = 1 if depth.nil? return lst unless islist(lst) + out = [] lst.each do |item| - if islist(item) && depth > 0 + if islist(item) && depth.positive? out.concat(flatten(item, depth - 1)) else out << item unless item.nil? || item.equal?(UNDEF) @@ -424,22 +439,23 @@ def self.flatten(lst, depth = nil) def self.filter(val, check) return [] unless isnode(val) + items(val).select { |item| check.call(item) }.map { |item| item[1] } end def self.delprop(parent, key) return parent unless iskey(key) + if ismap(parent) ks = strkey(key) parent.delete(ks) elsif islist(parent) return parent unless key.to_s.match?(/\A-?\d+\z/) + begin ki = key.to_i - if 0 <= ki && ki < parent.length - parent.delete_at(ki) - end - rescue + parent.delete_at(ki) if ki >= 0 && ki < parent.length + rescue StandardError end end parent @@ -447,25 +463,26 @@ def self.delprop(parent, key) def self.join(arr, sep = nil, url = nil) return '' unless islist(arr) + sepdef = sep.nil? ? ',' : sep.to_s - sepre = (sepdef.length == 1) ? Regexp.escape(sepdef) : nil + sepre = sepdef.length == 1 ? Regexp.escape(sepdef) : nil # Filter to non-empty strings only parts = arr.select { |n| n.is_a?(String) && n != '' } - parts = parts.map.with_index { |s, i| + parts = parts.map.with_index do |s, i| if sepre - if url && i == 0 + if url && i.zero? s = s.sub(/#{sepre}+$/, '') next s end - s = s.sub(/^#{sepre}+/, '') if i > 0 + s = s.sub(/^#{sepre}+/, '') if i.positive? s = s.sub(/#{sepre}+$/, '') if i < parts.length - 1 || !url # Collapse internal duplicate separators s = s.gsub(/([^#{sepre}])#{sepre}+([^#{sepre}])/, "\\1#{sepdef}\\2") end s - }.reject(&:empty?) + end.reject(&:empty?) parts.join(sepdef) end @@ -476,22 +493,20 @@ def self.joinurl(sarr) def self.jsonify(val, flags = nil) str = 'null' - if !val.nil? + unless val.nil? begin indent = (flags.is_a?(Hash) ? (flags['indent'] || flags[:indent]) : nil) || 2 str = _json_stringify(val, indent, 0) - if str.nil? - str = 'null' - end + str = 'null' if str.nil? offset = (flags.is_a?(Hash) ? (flags['offset'] || flags[:offset]) : nil) || 0 - if offset > 0 + if offset.positive? lines = str.split("\n") - first = lines[0] || '' - rest = lines[1..-1] || [] + lines[0] || '' + rest = lines[1..] || [] rest_indented = rest.map { |l| (' ' * offset) + l } - str = "{\n" + rest_indented.join("\n") + str = "{\n#{rest_indented.join("\n")}" end - rescue => e + rescue StandardError str = '__JSONIFY_FAILED__' end end @@ -501,8 +516,8 @@ def self.jsonify(val, flags = nil) # Mimic JSON.stringify(val, null, indent) from JavaScript def self._json_stringify(val, indent, depth) return 'null' if val.nil? - return val.to_s if val == true || val == false - return val.is_a?(Float) ? val.to_s : val.to_s if val.is_a?(Numeric) + return val.to_s if [true, false].include?(val) + return val.to_s if val.is_a?(Numeric) return JSON.generate(val) if val.is_a?(String) ind = ' ' * indent @@ -511,14 +526,16 @@ def self._json_stringify(val, indent, depth) if islist(val) return '[]' if val.empty? + items_str = val.map { |v| current_indent + _json_stringify(v, indent, depth + 1) } - "[\n" + items_str.join(",\n") + "\n" + closing_indent + "]" + "[\n#{items_str.join(",\n")}\n#{closing_indent}]" elsif ismap(val) return '{}' if val.empty? - pairs = val.keys.sort.map { |k| - current_indent + JSON.generate(k) + ': ' + _json_stringify(val[k], indent, depth + 1) - } - "{\n" + pairs.join(",\n") + "\n" + closing_indent + "}" + + pairs = val.keys.sort.map do |k| + "#{current_indent}#{JSON.generate(k)}: #{_json_stringify(val[k], indent, depth + 1)}" + end + "{\n#{pairs.join(",\n")}\n#{closing_indent}}" elsif isfunc(val) 'null' else @@ -542,6 +559,7 @@ def self.jt(*v) def self.replace(s, from, to) return s.to_s unless s.is_a?(String) + if from.is_a?(Regexp) s.gsub(from, to.to_s) else @@ -551,6 +569,7 @@ def self.replace(s, from, to) def self.keysof(val) return [] unless isnode(val) + if ismap(val) val.keys.sort elsif islist(val) @@ -565,29 +584,33 @@ def self.haskey(val = UNDEF, key = UNDEF) _getprop(val, key, UNDEF) != UNDEF end - def self.joinurl(parts) + # NOTE: this is a second, simpler joinurl definition that intentionally + # overrides the join-based one above; kept for cross-language source parity. + def self.joinurl(parts) # rubocop:disable Lint/DuplicateMethods parts.compact.map.with_index do |s, i| s = s.to_s if i.zero? - s.sub(/\/+$/, '') + s.sub(%r{/+$}, '') else - s.sub(/([^\/])\/+/, '\1/').sub(/^\/+/, '').sub(/\/+$/, '') + s.sub(%r{([^/])/+}, '\1/').sub(%r{^/+}, '').sub(%r{/+$}, '') end - end.reject { |s| s.empty? }.join('/') + end.reject(&:empty?).join('/') end # Get type name string from type bitfield value. def self._clz32(n) return 32 if n <= 0 + 31 - (n.bit_length - 1) end def self.typename(t) t = t.to_i idx = _clz32(t) - return TYPENAME[0] if idx < 0 || idx >= TYPENAME.length + return TYPENAME[0] if idx.negative? || idx >= TYPENAME.length + r = TYPENAME[idx] - (r.nil? || r == S_MT) ? TYPENAME[0] : r + r.nil? || r == S_MT ? TYPENAME[0] : r end # Determine the type of a value as a bitfield integer. @@ -595,37 +618,23 @@ def self.typify(value = UNDEF) return T_noval if value.equal?(UNDEF) return T_scalar | T_null if value.nil? - if value == true || value == false - return T_scalar | T_boolean - end + return T_scalar | T_boolean if [true, false].include?(value) - if isfunc(value) - return T_scalar | T_function - end + return T_scalar | T_function if isfunc(value) - if value.is_a?(Integer) - return T_scalar | T_number | T_integer - end + return T_scalar | T_number | T_integer if value.is_a?(Integer) if value.is_a?(Float) return value.nan? ? T_noval : (T_scalar | T_number | T_decimal) end - if value.is_a?(String) - return T_scalar | T_string - end + return T_scalar | T_string if value.is_a?(String) - if value.is_a?(Symbol) - return T_scalar | T_symbol - end + return T_scalar | T_symbol if value.is_a?(Symbol) - if islist(value) - return T_node | T_list - end + return T_node | T_list if islist(value) - if ismap(value) - return T_node | T_map - end + return T_node | T_map if ismap(value) T_any end @@ -637,12 +646,8 @@ def self.typify(value = UNDEF) # path MUST clone it (e.g. `path.dup`); the contents will otherwise be # overwritten by subsequent visits. def self.walk(val, before = nil, after = nil, maxdepth = nil, key: nil, parent: nil, path: nil, pool: nil) - if pool.nil? - pool = [[]] - end - if path.nil? - path = pool[0] - end + pool = [[]] if pool.nil? + path = pool[0] if path.nil? depth = path.length @@ -651,10 +656,8 @@ def self.walk(val, before = nil, after = nil, maxdepth = nil, key: nil, parent: out = _before.nil? ? val : _before.call(key, val, parent, path) - md = (maxdepth.is_a?(Numeric) && maxdepth >= 0) ? maxdepth : MAXDEPTH - if md == 0 || (md > 0 && md <= depth) - return out - end + md = maxdepth.is_a?(Numeric) && maxdepth >= 0 ? maxdepth : MAXDEPTH + return out if md.zero? || (md.positive? && md <= depth) if isnode(out) child_depth = depth + 1 @@ -698,24 +701,24 @@ def self.deep_merge(a, b) if ismap(a) && ismap(b) merged = a.dup b.each do |k, v| - if merged.key?(k) - merged[k] = deep_merge(merged[k], v) - else - merged[k] = v - end + merged[k] = if merged.key?(k) + deep_merge(merged[k], v) + else + v + end end merged elsif islist(a) && islist(b) max_len = [a.size, b.size].max merged = [] (0...max_len).each do |i| - if i < a.size && i < b.size - merged[i] = deep_merge(a[i], b[i]) - elsif i < b.size - merged[i] = b[i] - else - merged[i] = a[i] - end + merged[i] = if i < a.size && i < b.size + deep_merge(a[i], b[i]) + elsif i < b.size + b[i] + else + a[i] + end end merged else @@ -733,7 +736,7 @@ def self.merge(val, maxdepth = nil) return val unless islist(val) lenlist = val.length - return nil if lenlist == 0 + return nil if lenlist.zero? return val[0] if lenlist == 1 out = getprop(val, 0, {}) @@ -741,10 +744,7 @@ def self.merge(val, maxdepth = nil) (1...lenlist).each do |oI| obj = val[oI] - if !isnode(obj) - # Non-nodes (including nil) override directly - out = obj - else + if isnode(obj) cur = [out] dst = [out] @@ -752,18 +752,18 @@ def self.merge(val, maxdepth = nil) pI = path.length if md <= pI - while cur.length <= pI; cur << nil; end + cur << nil while cur.length <= pI cur[pI] = v - setprop(cur[pI - 1], key, v) if pI > 0 && pI - 1 < cur.length - next nil # stop descending + setprop(cur[pI - 1], key, v) if pI.positive? && pI - 1 < cur.length + next nil # stop descending elsif !isnode(v) cur[pI] = v else # Extend arrays as needed - while dst.length <= pI; dst << nil; end - while cur.length <= pI; cur << nil; end + dst << nil while dst.length <= pI + cur << nil while cur.length <= pI - dst[pI] = pI > 0 ? getprop(dst[pI - 1], key) : dst[pI] + dst[pI] = pI.positive? ? getprop(dst[pI - 1], key) : dst[pI] tval = dst[pI] if tval.nil? @@ -772,7 +772,7 @@ def self.merge(val, maxdepth = nil) cur[pI] = tval else cur[pI] = v - v = nil # stop descending + v = nil # stop descending end end @@ -782,21 +782,24 @@ def self.merge(val, maxdepth = nil) after_fn = lambda { |key, _v, _parent, path| cI = path.length if cI < 1 - next (cur.length > 0 ? cur[0] : _v) + next (cur.length.positive? ? cur[0] : _v) end - target = (cI - 1 < cur.length) ? cur[cI - 1] : nil - value = (cI < cur.length) ? cur[cI] : nil + target = cI - 1 < cur.length ? cur[cI - 1] : nil + value = cI < cur.length ? cur[cI] : nil setprop(target, key, value) if target value } out = walk(obj, before_fn, after_fn) + else + # Non-nodes (including nil) override directly + out = obj end end - if md == 0 + if md.zero? out = getelem(val, -1) out = islist(out) ? [] : ismap(out) ? {} : out end @@ -836,22 +839,25 @@ def self.getpath(store, path, injdef = nil) dpath = injdef.dpath handler = injdef.handler else - base = nil; dparent = nil; inj_meta = nil; inj_key = nil; dpath = nil; handler = nil + base = nil + dparent = nil + inj_meta = nil + inj_key = nil + dpath = nil + handler = nil end src = base ? _getprop(store, base, store) : store numparts = parts.length # An empty path (incl empty string) just finds the src. - if path.nil? || store.nil? || (numparts == 1 && parts[0] == S_MT) || numparts == 0 + if path.nil? || store.nil? || (numparts == 1 && parts[0] == S_MT) || numparts.zero? val = src - elsif numparts > 0 + elsif numparts.positive? # Check for $ACTIONs - if numparts == 1 - val = _getprop(store, parts[0], UNDEF) - end + val = _getprop(store, parts[0], UNDEF) if numparts == 1 - if !isfunc(val) + unless isfunc(val) val = src # Check for meta path syntax @@ -884,17 +890,17 @@ def self.getpath(store, path, injdef = nil) pI += 1 end - if injdef && ascends > 0 + if injdef && ascends.positive? ascends -= 1 if pI == parts.length - 1 - if ascends == 0 + if ascends.zero? val = dparent else - fullpath = flatten([slice(dpath, 0 - ascends), parts[(pI + 1)..-1]]) - if dpath.is_a?(Array) && ascends <= dpath.length - val = getpath(store, fullpath) - else - val = UNDEF - end + fullpath = flatten([slice(dpath, 0 - ascends), parts[(pI + 1)..]]) + val = if dpath.is_a?(Array) && ascends <= dpath.length + getpath(store, fullpath) + else + UNDEF + end break end else @@ -917,17 +923,16 @@ def self.getpath(store, path, injdef = nil) val.equal?(UNDEF) ? nil : val end + S_BKEY = '`$KEY`'.freeze + S_BANNO = '`$ANNO`'.freeze + S_BEXACT = '`$EXACT`'.freeze + S_BVAL = '`$VAL`'.freeze + S_DSPEC = '$SPEC'.freeze - S_BKEY = '`$KEY`' - S_BANNO = '`$ANNO`' - S_BEXACT = '`$EXACT`' - S_BVAL = '`$VAL`' - S_DSPEC = '$SPEC' - - R_FULL_INJECT = /\A`(\$[A-Z]+|[^`]*)[0-9]*`\z/ - R_PART_INJECT = /`([^`]*)`/ - R_META_PATH = /\A([^$]+)\$([=~])(.+)\z/ - R_DOUBLE_DOLLAR = /\$\$/ + R_FULL_INJECT = /\A`(\$[A-Z]+|[^`]*)[0-9]*`\z/.freeze + R_PART_INJECT = /`([^`]*)`/.freeze + R_META_PATH = /\A([^$]+)\$([=~])(.+)\z/.freeze + R_DOUBLE_DOLLAR = /\$\$/.freeze # --- _injectstr: Resolve backtick expressions in strings --- def self._injectstr(val, store, inj = nil) @@ -941,19 +946,15 @@ def self._injectstr(val, store, inj = nil) inj.full = true if inj pathref = m[1] - if pathref.length > 3 - pathref = pathref.gsub('$BT', S_BT).gsub('$DS', S_DS) - end + pathref = pathref.gsub('$BT', S_BT).gsub('$DS', S_DS) if pathref.length > 3 out = getpath(store, pathref, inj) else # Partial string injection: "prefix`ref`suffix" out = val.gsub(R_PART_INJECT) do |_match| - ref = $1 - if ref.length > 3 - ref = ref.gsub('$BT', S_BT).gsub('$DS', S_DS) - end + ref = ::Regexp.last_match(1) + ref = ref.gsub('$BT', S_BT).gsub('$DS', S_DS) if ref.length > 3 inj.full = false if inj @@ -972,7 +973,7 @@ def self._injectstr(val, store, inj = nil) else begin JSON.generate(found) - rescue + rescue StandardError stringify(found) end end @@ -1021,7 +1022,7 @@ def self.inject(val, store, injdef = nil) # Descend into node. if isnode(val) if ismap(val) - normal = val.keys.select { |k| !k.include?(S_DS) }.sort + normal = val.keys.reject { |k| k.include?(S_DS) }.sort transforms = val.keys.select { |k| k.include?(S_DS) }.sort nodekeys = normal + transforms else @@ -1039,7 +1040,7 @@ def self.inject(val, store, injdef = nil) nkI = childinj.keyI nodekeys = childinj.keys - if !prekey.nil? + unless prekey.nil? childinj.val = getprop(val, prekey) childinj.mode = S_MVAL @@ -1074,24 +1075,20 @@ def self.inject(val, store, injdef = nil) inj.val = val - if inj.prior.nil? && inj.root && haskey(inj.root, S_DTOP) - return getprop(inj.root, S_DTOP) - end - if inj.key == S_DTOP && inj.parent && haskey(inj.parent, S_DTOP) - return getprop(inj.parent, S_DTOP) - end + return getprop(inj.root, S_DTOP) if inj.prior.nil? && inj.root && haskey(inj.root, S_DTOP) + return getprop(inj.parent, S_DTOP) if inj.key == S_DTOP && inj.parent && haskey(inj.parent, S_DTOP) + val end # Helper to read a property from injdef (Hash or object) def self._injdef_prop(injdef, key) return nil if injdef.nil? + if injdef.is_a?(Hash) injdef[key] || injdef[key.to_sym] elsif injdef.respond_to?(key.to_sym) injdef.send(key.to_sym) - else - nil end end @@ -1124,11 +1121,11 @@ def self.transform_COPY(inj, _val, _ref, _store) if mode.start_with?('key') out = key else - if !isnode(inj.dparent) - out = (inj.path.length != 2) ? inj.dparent : nil - else - out = getprop(inj.dparent, key) - end + out = if isnode(inj.dparent) + getprop(inj.dparent, key) + else + inj.path.length == 2 ? nil : inj.dparent + end inj.setval(out) end out @@ -1148,9 +1145,7 @@ def self.transform_KEY(inj, _val, _ref, _store) return getprop(inj.dparent, keyspec) end - if ismap(inj.dparent) && inj.key && haskey(inj.dparent, inj.key) - return getprop(inj.dparent, inj.key) - end + return getprop(inj.dparent, inj.key) if ismap(inj.dparent) && inj.key && haskey(inj.dparent, inj.key) meta = getprop(parent, S_BANNO) getprop(meta, S_KEY, getprop(path, path.length - 2)) @@ -1175,19 +1170,19 @@ def self.transform_MERGE(inj, _val, _ref, _store) return key elsif mode == S_MKEYPOST args = getprop(parent, key) - args = islist(args) ? args : [args] + args = [args] unless islist(args) inj.setval(nil) mergelist = [parent] + args + [clone(parent)] merge(mergelist) return key elsif mode == S_MVAL && islist(parent) - if strkey(inj.key) == '0' && size(parent) > 0 - parent.delete_at(0) - return getprop(parent, 0) - else - return getprop(parent, inj.key) - end + return getprop(parent, inj.key) unless strkey(inj.key) == '0' && size(parent).positive? + + parent.delete_at(0) + return getprop(parent, 0) + end + nil end @@ -1198,7 +1193,7 @@ def self.transform_EACH(inj, _val, _ref, store) parent = inj.parent nodes_ = inj.nodes - keys_.replace(keys_[0, 1]) if keys_ + keys_&.replace(keys_[0, 1]) return nil if mode != S_MVAL || !path || !nodes_ @@ -1218,7 +1213,7 @@ def self.transform_EACH(inj, _val, _ref, store) tval = src.map { clone(child_template) } else tval = [] - src.each do |k, v| + src.each_key do |k| cc = clone(child_template) setprop(cc, S_BANNO, { S_KEY => k }) if ismap(cc) tval << cc @@ -1226,7 +1221,7 @@ def self.transform_EACH(inj, _val, _ref, store) end tcurrent = ismap(src) ? src.values : src - if size(tval) > 0 + if size(tval).positive? ckey = getelem(path, -2) tpath = path[0...-1] @@ -1234,19 +1229,19 @@ def self.transform_EACH(inj, _val, _ref, store) if srcpath.is_a?(String) && !srcpath.empty? srcpath.split(S_DT).each { |p| dpath << p if p != S_MT } end - dpath << ('$:' + ckey.to_s) if ckey + dpath << "$:#{ckey}" if ckey tcur = { ckey => tcurrent } if size(tpath) > 1 pkey = getelem(path, -3, S_DTOP) tcur = { pkey => tcur } - dpath << ('$:' + pkey.to_s) + dpath << "$:#{pkey}" end tinj = inj.child(0, ckey ? [ckey] : []) tinj.path = tpath - tinj.nodes = nodes_.length > 0 ? nodes_[0...-1] : [] + tinj.nodes = nodes_.length.positive? ? nodes_[0...-1] : [] tinj.parent = getelem(tinj.nodes, -1) setprop(tinj.parent, ckey, tval) if ckey && tinj.parent tinj.val = tval @@ -1259,7 +1254,7 @@ def self.transform_EACH(inj, _val, _ref, store) end setprop(target, tkey, rval) - islist(rval) && size(rval) > 0 ? rval[0] : nil + islist(rval) && size(rval).positive? ? rval[0] : nil end def self.transform_PACK(inj, _val, _ref, store) @@ -1279,12 +1274,12 @@ def self.transform_PACK(inj, _val, _ref, store) tkey = getelem(path, -2) pathsize = size(path) - target = getelem(nodes_, pathsize - 2, lambda { getelem(nodes_, pathsize - 1) }) + target = getelem(nodes_, pathsize - 2, -> { getelem(nodes_, pathsize - 1) }) srcstore = getprop(store, inj.base, store) src = getpath(srcstore, srcpath, inj) - if !islist(src) + unless islist(src) if ismap(src) new_src = [] items(src).each do |item| @@ -1310,11 +1305,11 @@ def self.transform_PACK(inj, _val, _ref, store) k = srckey if keypath - if keypath.is_a?(String) && keypath.start_with?(S_BT) - k = inject(keypath, merge([{}, store, { S_DTOP => srcnode }], 1)) - else - k = getpath(srcnode, keypath, inj) - end + k = if keypath.is_a?(String) && keypath.start_with?(S_BT) + inject(keypath, merge([{}, store, { S_DTOP => srcnode }], 1)) + else + getpath(srcnode, keypath, inj) + end end tchild = clone(child) @@ -1330,28 +1325,28 @@ def self.transform_PACK(inj, _val, _ref, store) rval = {} - if !isempty(tval) + unless isempty(tval) tsrc = {} src.each_with_index do |n, i| - if keypath.nil? - kn = i - elsif keypath.is_a?(String) && keypath.start_with?(S_BT) - kn = inject(keypath, merge([{}, store, { S_DTOP => n }], 1)) - else - kn = getpath(n, keypath, inj) - end + kn = if keypath.nil? + i + elsif keypath.is_a?(String) && keypath.start_with?(S_BT) + inject(keypath, merge([{}, store, { S_DTOP => n }], 1)) + else + getpath(n, keypath, inj) + end setprop(tsrc, kn, n) end tpath = slice(inj.path, -1) ckey = getelem(inj.path, -2) - dpath = flatten([S_DTOP, srcpath.to_s.split(S_DT), '$:' + ckey.to_s]) + dpath = flatten([S_DTOP, srcpath.to_s.split(S_DT), "$:#{ckey}"]) tcur = { ckey => tsrc } if size(tpath) > 1 pkey = getelem(inj.path, -3, S_DTOP) tcur = { pkey => tcur } - dpath << ('$:' + pkey.to_s) + dpath << "$:#{pkey}" end tinj = inj.child(0, [ckey]) @@ -1372,7 +1367,7 @@ def self.transform_PACK(inj, _val, _ref, store) def self.transform_REF(inj, _val, _ref, store) nodes_ = inj.nodes - return nil if S_MVAL != inj.mode + return nil if inj.mode != S_MVAL refpath = getprop(inj.parent, 1) inj.keyI = size(inj.keys) @@ -1382,9 +1377,9 @@ def self.transform_REF(inj, _val, _ref, store) dpath = slice(inj.path, 1) ref = getpath(spec, refpath, { - 'dpath' => dpath, - 'dparent' => getpath(spec, dpath), - }) + 'dpath' => dpath, + 'dparent' => getpath(spec, dpath) + }) tref = clone(ref) @@ -1409,30 +1404,32 @@ def self.transform_REF(inj, _val, _ref, store) end tkey = getelem(inj.path, -2) - target = getelem(nodes_, -2, lambda { getelem(nodes_, -1) }) + target = getelem(nodes_, -2, -> { getelem(nodes_, -1) }) if rval.nil? delprop(target, tkey) else setprop(target, tkey, rval) end - if islist(target) && inj.prior - inj.prior.keyI -= 1 - end + inj.prior.keyI -= 1 if islist(target) && inj.prior _val end FORMATTER = { - 'identity' => lambda { |_k, v, *_a| v }, - 'upper' => lambda { |_k, v, *_a| isnode(v) ? v : (v.nil? ? 'null' : '' + v.to_s).upcase }, - 'lower' => lambda { |_k, v, *_a| isnode(v) ? v : (v.nil? ? 'null' : '' + v.to_s).downcase }, - 'string' => lambda { |_k, v, *_a| isnode(v) ? v : (v.nil? ? 'null' : '' + v.to_s) }, + 'identity' => ->(_k, v, *_a) { v }, + 'upper' => ->(_k, v, *_a) { isnode(v) ? v : (v.nil? ? 'null' : v.to_s).upcase }, + 'lower' => ->(_k, v, *_a) { isnode(v) ? v : (v.nil? ? 'null' : v.to_s).downcase }, + 'string' => ->(_k, v, *_a) { isnode(v) ? v : (v.nil? ? 'null' : v.to_s) }, 'number' => lambda { |_k, v, *_a| if isnode(v) v else - n = Float(v) rescue 0 + n = begin + Float(v) + rescue StandardError + 0 + end n end }, @@ -1440,36 +1437,40 @@ def self.transform_REF(inj, _val, _ref, store) if isnode(v) v else - n = Integer(Float(v)) rescue 0 + n = begin + Integer(Float(v)) + rescue StandardError + 0 + end n end }, 'concat' => lambda { |k, v, *_a| if k.nil? && islist(v) - items(v, lambda { |n| isnode(n[1]) ? '' : (n[1].nil? ? 'null' : '' + n[1].to_s) }).join('') + items(v, ->(n) { isnode(n[1]) ? '' : (n[1].nil? ? 'null' : n[1].to_s) }).join else v end - }, - } + } + }.freeze def self.transform_FORMAT(inj, _val, _ref, store) slice(inj.keys, 0, 1, true) - return nil if S_MVAL != inj.mode + return nil if inj.mode != S_MVAL name = getprop(inj.parent, 1) child = getprop(inj.parent, 2) tkey = getelem(inj.path, -2) - target = getelem(inj.nodes, -2, lambda { getelem(inj.nodes, -1) }) + target = getelem(inj.nodes, -2, -> { getelem(inj.nodes, -1) }) cinj = injectChild(child, store, inj) resolved = cinj.val - formatter = (0 < (T_function & typify(name))) ? name : FORMATTER[name] + formatter = T_function.anybits?(typify(name)) ? name : FORMATTER[name] if formatter.nil? - inj.errs << ('$FORMAT: unknown format: ' + name.to_s + '.') + inj.errs << "$FORMAT: unknown format: #{name}." return nil end @@ -1486,12 +1487,12 @@ def self.transform_APPLY(inj, _val, _ref, store) args_list = islist(args) ? args : [] err, apply, child = injectorArgs([T_function, T_any], args_list) if err - inj.errs << ('$' + ijname + ': ' + err) + inj.errs << "$#{ijname}: #{err}" return nil end tkey = getelem(inj.path, -2) - target = getelem(inj.nodes, -2, lambda { getelem(inj.nodes, -1) }) + target = getelem(inj.nodes, -2, -> { getelem(inj.nodes, -1) }) cinj = injectChild(child, store, inj) resolved = cinj.val @@ -1504,16 +1505,16 @@ def self.transform_APPLY(inj, _val, _ref, store) def self.checkPlacement(modes, ijname, parentTypes, inj) mode_num = { S_MKEYPRE => M_KEYPRE, S_MKEYPOST => M_KEYPOST, S_MVAL => M_VAL } mode_int = mode_num[inj.mode] || 0 - if 0 == (modes & mode_int) - inj.errs << '$' + ijname + ': invalid placement as ' + (PLACEMENT[mode_int] || '') + - ', expected: ' + [M_KEYPRE, M_KEYPOST, M_VAL].select { |m| modes & m != 0 }.map { |m| PLACEMENT[m] }.join(',') + '.' + if modes.nobits?(mode_int) + expected = [M_KEYPRE, M_KEYPOST, M_VAL].reject { |m| modes.nobits?(m) } + expected = expected.map { |m| PLACEMENT[m] }.join(',') + inj.errs << "$#{ijname}: invalid placement as #{PLACEMENT[mode_int] || ''}, expected: #{expected}." return false end - if !isempty(parentTypes) + unless isempty(parentTypes) ptype = typify(inj.parent) - if 0 == (parentTypes & ptype) - inj.errs << '$' + ijname + ': invalid placement in parent ' + typename(ptype) + - ', expected: ' + typename(parentTypes) + '.' + if parentTypes.nobits?(ptype) + inj.errs << "$#{ijname}: invalid placement in parent #{typename(ptype)}, expected: #{typename(parentTypes)}." return false end end @@ -1527,10 +1528,10 @@ def self.injectorArgs(argTypes, args) (0...numargs).each do |argI| arg = args[argI] argType = typify(arg) - if 0 == (argTypes[argI] & argType) - found[0] = 'invalid argument: ' + stringify(arg, 22) + - ' (' + typename(argType) + ' at position ' + (1 + argI).to_s + - ') is not of type: ' + typename(argTypes[argI]) + '.' + if argTypes[argI].nobits?(argType) + found[0] = + "invalid argument: #{stringify(arg, 22)} (#{typename(argType)} at position #{1 + argI}) " \ + "is not of type: #{typename(argTypes[argI])}." break end found[1 + argI] = arg @@ -1579,16 +1580,16 @@ def self.transform(data, spec, injdef = nil) end data_clone = merge([ - isempty(extraData) ? nil : clone(extraData), - clone(data) - ]) + isempty(extraData) ? nil : clone(extraData), + clone(data) + ]) store = { S_DTOP => data_clone, - S_DSPEC => lambda { origspec }, - '$BT' => lambda { |*_a| S_BT }, - '$DS' => lambda { |*_a| S_DS }, - '$WHEN' => lambda { |*_a| Time.now.iso8601 }, + S_DSPEC => -> { origspec }, + '$BT' => ->(*_a) { S_BT }, + '$DS' => ->(*_a) { S_DS }, + '$WHEN' => ->(*_a) { Time.now.iso8601 }, '$DELETE' => method(:transform_DELETE), '$COPY' => method(:transform_COPY), '$KEY' => method(:transform_KEY), @@ -1599,7 +1600,7 @@ def self.transform(data, spec, injdef = nil) '$PACK' => method(:transform_PACK), '$REF' => method(:transform_REF), '$FORMAT' => method(:transform_FORMAT), - '$APPLY' => method(:transform_APPLY), + '$APPLY' => method(:transform_APPLY) } extraTransforms.each { |k, v| store[k] = v } store[S_DERRS] = errs @@ -1610,9 +1611,7 @@ def self.transform(data, spec, injdef = nil) out = inject(spec, store, injdef) - if !errs.empty? && !collect - raise errs.join(' | ') - end + raise errs.join(' | ') if !errs.empty? && !collect out end @@ -1620,53 +1619,53 @@ def self.transform(data, spec, injdef = nil) # --- Validators --- def self._invalidTypeMsg(path, needtype, vt, v, _whence = nil) - vs = (v.nil? || v.equal?(UNDEF)) ? 'no value' : stringify(v) - 'Expected ' + - (size(path) > 1 ? ('field ' + pathify(path, 1) + ' to be ') : '') + - needtype.to_s + ', but found ' + - ((v.nil? || v.equal?(UNDEF)) ? '' : typename(vt) + S_VIZ) + vs + '.' + vs = v.nil? || v.equal?(UNDEF) ? 'no value' : stringify(v) + "Expected #{if size(path) > 1 + "field #{pathify(path, + 1)} to be " + end}#{needtype}, but found #{typename(vt) + S_VIZ unless v.nil? || v.equal?(UNDEF)}#{vs}." end def self.validate_STRING(inj, _val = nil, _ref = nil, _store = nil) out = getprop(inj.dparent, inj.key) t = typify(out) - if 0 == (T_string & t) + if T_string.nobits?(t) inj.errs << _invalidTypeMsg(inj.path, S_string, t, out, 'V1010') return nil end if out == S_MT - inj.errs << ('Empty string at ' + pathify(inj.path, 1)) + inj.errs << "Empty string at #{pathify(inj.path, 1)}" return nil end out end TYPE_CHECKS = { - S_number => lambda { |v| v.is_a?(Numeric) && !(v == true || v == false) }, - S_integer => lambda { |v| v.is_a?(Integer) && !(v == true || v == false) }, - S_decimal => lambda { |v| v.is_a?(Float) }, - S_boolean => lambda { |v| v == true || v == false }, - S_null => lambda { |v| v.nil? }, - S_nil => lambda { |v| v.equal?(UNDEF) }, - S_map => lambda { |v| v.is_a?(Hash) }, - S_list => lambda { |v| v.is_a?(Array) }, - S_function => lambda { |v| v.respond_to?(:call) }, + S_number => ->(v) { v.is_a?(Numeric) && ![true, false].include?(v) }, + S_integer => ->(v) { v.is_a?(Integer) && ![true, false].include?(v) }, + S_decimal => ->(v) { v.is_a?(Float) }, + S_boolean => ->(v) { [true, false].include?(v) }, + S_null => lambda(&:nil?), + S_nil => ->(v) { v.equal?(UNDEF) }, + S_map => ->(v) { v.is_a?(Hash) }, + S_list => ->(v) { v.is_a?(Array) }, + S_function => ->(v) { v.respond_to?(:call) }, S_instance => lambda { |v| !v.is_a?(Hash) && !v.is_a?(Array) && !v.is_a?(String) && - !v.is_a?(Numeric) && !(v == true || v == false) && !v.nil? && !v.equal?(UNDEF) - }, - } + !v.is_a?(Numeric) && ![true, false].include?(v) && !v.nil? && !v.equal?(UNDEF) + } + }.freeze def self.validate_TYPE(inj, _val = nil, ref = nil, _store = nil) - tname = (ref.is_a?(String) && ref.length > 1) ? ref[1..-1].downcase : S_any + tname = ref.is_a?(String) && ref.length > 1 ? ref[1..].downcase : S_any idx = TYPENAME.index(tname) typev = idx ? (1 << (31 - idx)) : 0 - typev = typev | T_null if tname == S_nil + typev |= T_null if tname == S_nil out = getprop(inj.dparent, inj.key) t = typify(out) - if 0 == (t & typev) + if t.nobits?(typev) inj.errs << _invalidTypeMsg(inj.path, tname, t, out, 'V1001') return nil end @@ -1684,7 +1683,7 @@ def self.validate_CHILD(inj, _val = nil, _ref = nil, _store = nil) path = inj.path keys = inj.keys - if S_MKEYPRE == mode + if mode == S_MKEYPRE childtm = getprop(parent, key) pkey = getelem(path, -2) tval = getprop(inj.dparent, pkey) @@ -1705,8 +1704,8 @@ def self.validate_CHILD(inj, _val = nil, _ref = nil, _store = nil) return nil end - if S_MVAL == mode - if !islist(parent) + if mode == S_MVAL + unless islist(parent) inj.errs << 'Invalid $CHILD as value' return nil end @@ -1718,7 +1717,7 @@ def self.validate_CHILD(inj, _val = nil, _ref = nil, _store = nil) return nil end - if !islist(inj.dparent) + unless islist(inj.dparent) inj.errs << _invalidTypeMsg(path[0...-1], S_list, typify(inj.dparent), inj.dparent, 'V0230') inj.keyI = size(parent) return inj.dparent @@ -1740,48 +1739,47 @@ def self.validate_ONE(inj, _val = nil, _ref = nil, store = nil) parent = inj.parent keyI = inj.keyI - if S_MVAL == mode - if !islist(parent) || 0 != keyI - inj.errs << ('The $ONE validator at field ' + pathify(inj.path, 1, 1) + - ' must be the first element of an array.') - return nil - end - - inj.keyI = size(inj.keys) - inj.setval(inj.dparent, 2) - inj.path = inj.path[0...-1] - inj.key = getelem(inj.path, -1) + return unless mode == S_MVAL - tvals = parent[1..-1] - if size(tvals) == 0 - inj.errs << ('The $ONE validator at field ' + pathify(inj.path, 1, 1) + - ' must have at least one argument.') - return nil - end + if !islist(parent) || keyI != 0 + inj.errs << "The $ONE validator at field #{pathify(inj.path, 1, 1)} must be the first element of an array." + return nil + end - tvals.each do |tval| - terrs = [] - vstore = merge([{}, store], 1) - vstore[S_DTOP] = inj.dparent + inj.keyI = size(inj.keys) + inj.setval(inj.dparent, 2) + inj.path = inj.path[0...-1] + inj.key = getelem(inj.path, -1) - vcurrent = validate(inj.dparent, tval, { - 'extra' => vstore, - 'errs' => terrs, - 'meta' => inj.meta, - }) + tvals = parent[1..] + if size(tvals).zero? + inj.errs << "The $ONE validator at field #{pathify(inj.path, 1, 1)} must have at least one argument." + return nil + end - inj.setval(vcurrent, -2) - return nil if size(terrs) == 0 - end + tvals.each do |tval| + terrs = [] + vstore = merge([{}, store], 1) + vstore[S_DTOP] = inj.dparent - valdesc = items(tvals).map { |n| stringify(n[1]) }.join(', ') - valdesc = valdesc.gsub(/`\$([A-Z]+)`/) { $1.downcase } + vcurrent = validate(inj.dparent, tval, { + 'extra' => vstore, + 'errs' => terrs, + 'meta' => inj.meta + }) - inj.errs << _invalidTypeMsg( - inj.path, - (size(tvals) > 1 ? 'one of ' : '') + valdesc, - typify(inj.dparent), inj.dparent, 'V0210') + inj.setval(vcurrent, -2) + return nil if size(terrs).zero? end + + valdesc = items(tvals).map { |n| stringify(n[1]) }.join(', ') + valdesc = valdesc.gsub(/`\$([A-Z]+)`/) { ::Regexp.last_match(1).downcase } + + inj.errs << _invalidTypeMsg( + inj.path, + (size(tvals) > 1 ? 'one of ' : '') + valdesc, + typify(inj.dparent), inj.dparent, 'V0210' + ) end def self.validate_EXACT(inj, _val = nil, _ref = nil, _store = nil) @@ -1790,10 +1788,9 @@ def self.validate_EXACT(inj, _val = nil, _ref = nil, _store = nil) key = inj.key keyI = inj.keyI - if S_MVAL == mode - if !islist(parent) || 0 != keyI - inj.errs << ('The $EXACT validator at field ' + pathify(inj.path, 1, 1) + - ' must be the first element of an array.') + if mode == S_MVAL + if !islist(parent) || keyI != 0 + inj.errs << "The $EXACT validator at field #{pathify(inj.path, 1, 1)} must be the first element of an array." return nil end @@ -1802,10 +1799,9 @@ def self.validate_EXACT(inj, _val = nil, _ref = nil, _store = nil) inj.path = inj.path[0...-1] inj.key = getelem(inj.path, -1) - tvals = parent[1..-1] - if size(tvals) == 0 - inj.errs << ('The $EXACT validator at field ' + pathify(inj.path, 1, 1) + - ' must have at least one argument.') + tvals = parent[1..] + if size(tvals).zero? + inj.errs << "The $EXACT validator at field #{pathify(inj.path, 1, 1)} must have at least one argument." return nil end @@ -1820,13 +1816,13 @@ def self.validate_EXACT(inj, _val = nil, _ref = nil, _store = nil) end valdesc = items(tvals).map { |n| stringify(n[1]) }.join(', ') - valdesc = valdesc.gsub(/`\$([A-Z]+)`/) { $1.downcase } + valdesc = valdesc.gsub(/`\$([A-Z]+)`/) { ::Regexp.last_match(1).downcase } inj.errs << _invalidTypeMsg( inj.path, - (size(inj.path) > 1 ? '' : 'value ') + - 'exactly equal to ' + (size(tvals) == 1 ? '' : 'one of ') + valdesc, - typify(inj.dparent), inj.dparent, 'V0110') + "#{'value ' unless size(inj.path) > 1}exactly equal to #{'one of ' unless size(tvals) == 1}#{valdesc}", + typify(inj.dparent), inj.dparent, 'V0110' + ) else delprop(parent, key) end @@ -1843,7 +1839,7 @@ def self._validation(pval, key, parent, inj) return if !exact && cval.nil? ptype = typify(pval) - return if 0 < (T_string & ptype) && pval.is_a?(String) && pval.include?(S_DS) + return if T_string.anybits?(ptype) && pval.is_a?(String) && pval.include?(S_DS) ctype = typify(cval) @@ -1853,7 +1849,7 @@ def self._validation(pval, key, parent, inj) end if ismap(cval) - if !ismap(pval) + unless ismap(pval) inj.errs << _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020') return end @@ -1861,10 +1857,10 @@ def self._validation(pval, key, parent, inj) ckeys = keysof(cval) pkeys = keysof(pval) - if pkeys.length > 0 && getprop(pval, '`$OPEN`') != true - badkeys = ckeys.select { |ck| !haskey(pval, ck) } - if badkeys.length > 0 - inj.errs << ('Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')) + if pkeys.length.positive? && getprop(pval, '`$OPEN`') != true + badkeys = ckeys.reject { |ck| haskey(pval, ck) } + if badkeys.length.positive? + inj.errs << "Unexpected keys at field #{pathify(inj.path, 1)}#{S_VIZ}#{join(badkeys, ', ')}" end else merge([pval, cval]) @@ -1872,20 +1868,18 @@ def self._validation(pval, key, parent, inj) end elsif islist(cval) - if !islist(pval) - inj.errs << _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030') - end + inj.errs << _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030') unless islist(pval) elsif exact # In exact mode, check key existence for nil values if cval.nil? && pval.nil? # Both nil: only match if key actually exists in data if ismap(inj.dparent) && !inj.dparent.key?(key.to_s) - inj.errs << ('Value at field ' + pathify(inj.path, 1) + ': key not present.') + inj.errs << "Value at field #{pathify(inj.path, 1)}: key not present." end elsif cval != pval - pathmsg = size(inj.path) > 1 ? ('at field ' + pathify(inj.path, 1) + ': ') : '' - inj.errs << ('Value ' + pathmsg + cval.to_s + ' should equal ' + pval.to_s + '.') + pathmsg = size(inj.path) > 1 ? "at field #{pathify(inj.path, 1)}: " : '' + inj.errs << "Value #{pathmsg}#{cval} should equal #{pval}." end else @@ -1919,44 +1913,42 @@ def self.validate(data, spec, injdef = nil) errs = collect ? _injdef_prop(injdef, 'errs') : [] store = merge([ - { - '$DELETE' => nil, '$COPY' => nil, '$KEY' => nil, '$META' => nil, - '$MERGE' => nil, '$EACH' => nil, '$PACK' => nil, - - '$STRING' => method(:validate_STRING), - '$NUMBER' => method(:validate_TYPE), - '$INTEGER' => method(:validate_TYPE), - '$DECIMAL' => method(:validate_TYPE), - '$BOOLEAN' => method(:validate_TYPE), - '$NULL' => method(:validate_TYPE), - '$NIL' => method(:validate_TYPE), - '$MAP' => method(:validate_TYPE), - '$LIST' => method(:validate_TYPE), - '$FUNCTION' => method(:validate_TYPE), - '$INSTANCE' => method(:validate_TYPE), - '$ANY' => method(:validate_ANY), - '$CHILD' => method(:validate_CHILD), - '$ONE' => method(:validate_ONE), - '$EXACT' => method(:validate_EXACT), - }, - (extra.nil? ? {} : extra), - { S_DERRS => errs }, - ], 1) + { + '$DELETE' => nil, '$COPY' => nil, '$KEY' => nil, '$META' => nil, + '$MERGE' => nil, '$EACH' => nil, '$PACK' => nil, + + '$STRING' => method(:validate_STRING), + '$NUMBER' => method(:validate_TYPE), + '$INTEGER' => method(:validate_TYPE), + '$DECIMAL' => method(:validate_TYPE), + '$BOOLEAN' => method(:validate_TYPE), + '$NULL' => method(:validate_TYPE), + '$NIL' => method(:validate_TYPE), + '$MAP' => method(:validate_TYPE), + '$LIST' => method(:validate_TYPE), + '$FUNCTION' => method(:validate_TYPE), + '$INSTANCE' => method(:validate_TYPE), + '$ANY' => method(:validate_ANY), + '$CHILD' => method(:validate_CHILD), + '$ONE' => method(:validate_ONE), + '$EXACT' => method(:validate_EXACT) + }, + (extra.nil? ? {} : extra), + { S_DERRS => errs } + ], 1) meta = _injdef_prop(injdef, 'meta') || {} setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false)) if ismap(meta) out = transform(data, spec, { - 'meta' => meta, - 'extra' => store, - 'modify' => method(:_validation), - 'handler' => method(:_validatehandler), - 'errs' => errs, - }) + 'meta' => meta, + 'extra' => store, + 'modify' => method(:_validation), + 'handler' => method(:_validatehandler), + 'errs' => errs + }) - if !errs.empty? && !collect - raise errs.join(' | ') - end + raise errs.join(' | ') if !errs.empty? && !collect out end @@ -1964,7 +1956,7 @@ def self.validate(data, spec, injdef = nil) # --- Select operators --- def self.select_AND(inj, _val, _ref, store) - if S_MKEYPRE == inj.mode + if inj.mode == S_MKEYPRE terms = getprop(inj.parent, inj.key) ppath = slice(inj.path, -1) point = getpath(store, ppath) @@ -1975,14 +1967,11 @@ def self.select_AND(inj, _val, _ref, store) terms.each do |term| terrs = [] validate(point, term, { - 'extra' => vstore, - 'errs' => terrs, - 'meta' => inj.meta, - }) - if !terrs.empty? - inj.errs << ('AND:' + pathify(ppath) + "\u2A2F" + stringify(point) + - ' fail:' + stringify(terms)) - end + 'extra' => vstore, + 'errs' => terrs, + 'meta' => inj.meta + }) + inj.errs << "AND:#{pathify(ppath)}\u2A2F#{stringify(point)} fail:#{stringify(terms)}" unless terrs.empty? end gkey = getelem(inj.path, -2) @@ -1993,7 +1982,7 @@ def self.select_AND(inj, _val, _ref, store) end def self.select_OR(inj, _val, _ref, store) - if S_MKEYPRE == inj.mode + if inj.mode == S_MKEYPRE terms = getprop(inj.parent, inj.key) ppath = slice(inj.path, -1) point = getpath(store, ppath) @@ -2004,26 +1993,25 @@ def self.select_OR(inj, _val, _ref, store) terms.each do |term| terrs = [] validate(point, term, { - 'extra' => vstore, - 'errs' => terrs, - 'meta' => inj.meta, - }) - if terrs.empty? - gkey = getelem(inj.path, -2) - gp = getelem(inj.nodes, -2) - setprop(gp, gkey, point) - return nil - end + 'extra' => vstore, + 'errs' => terrs, + 'meta' => inj.meta + }) + next unless terrs.empty? + + gkey = getelem(inj.path, -2) + gp = getelem(inj.nodes, -2) + setprop(gp, gkey, point) + return nil end - inj.errs << ('OR:' + pathify(ppath) + "\u2A2F" + stringify(point) + - ' fail:' + stringify(terms)) + inj.errs << "OR:#{pathify(ppath)}\u2A2F#{stringify(point)} fail:#{stringify(terms)}" end nil end def self.select_NOT(inj, _val, _ref, store) - if S_MKEYPRE == inj.mode + if inj.mode == S_MKEYPRE term = getprop(inj.parent, inj.key) ppath = slice(inj.path, -1) point = getpath(store, ppath) @@ -2033,15 +2021,12 @@ def self.select_NOT(inj, _val, _ref, store) terrs = [] validate(point, term, { - 'extra' => vstore, - 'errs' => terrs, - 'meta' => inj.meta, - }) - - if terrs.empty? - inj.errs << ('NOT:' + pathify(ppath) + "\u2A2F" + stringify(point) + - ' fail:' + stringify(term)) - end + 'extra' => vstore, + 'errs' => terrs, + 'meta' => inj.meta + }) + + inj.errs << "NOT:#{pathify(ppath)}\u2A2F#{stringify(point)} fail:#{stringify(term)}" if terrs.empty? gkey = getelem(inj.path, -2) gp = getelem(inj.nodes, -2) @@ -2051,7 +2036,7 @@ def self.select_NOT(inj, _val, _ref, store) end def self.select_CMP(inj, _val, ref, store) - if S_MKEYPRE == inj.mode + if inj.mode == S_MKEYPRE term = getprop(inj.parent, inj.key) gkey = getelem(inj.path, -2) ppath = slice(inj.path, -1) @@ -2060,26 +2045,25 @@ def self.select_CMP(inj, _val, ref, store) pass_test = false begin - if '$GT' == ref && point > term + if ref == '$GT' && point > term pass_test = true - elsif '$LT' == ref && point < term + elsif ref == '$LT' && point < term pass_test = true - elsif '$GTE' == ref && point >= term + elsif ref == '$GTE' && point >= term pass_test = true - elsif '$LTE' == ref && point <= term + elsif ref == '$LTE' && point <= term pass_test = true - elsif '$LIKE' == ref + elsif ref == '$LIKE' pass_test = true if stringify(point).match?(Regexp.new(term.to_s)) end - rescue + rescue StandardError end if pass_test gp = getelem(inj.nodes, -2) setprop(gp, gkey, point) else - inj.errs << ('CMP: ' + pathify(ppath) + "\u2A2F" + stringify(point) + - ' fail:' + ref.to_s + ' ' + stringify(term)) + inj.errs << "CMP: #{pathify(ppath)}\u2A2F#{stringify(point)} fail:#{ref} #{stringify(term)}" end end nil @@ -2089,18 +2073,18 @@ def self.select_CMP(inj, _val, ref, store) def self.select(children, query) return [] unless isnode(children) - if ismap(children) - children = items(children).map { |item| - v = item[1] - setprop(v, '$KEY', item[0]) if ismap(v) - v - } - else - children = children.each_with_index.map { |n, i| - setprop(n, '$KEY', i) if ismap(n) - n - } - end + children = if ismap(children) + items(children).map do |item| + v = item[1] + setprop(v, '$KEY', item[0]) if ismap(v) + v + end + else + children.each_with_index.map do |n, i| + setprop(n, '$KEY', i) if ismap(n) + n + end + end results = [] q = clone(query) @@ -2119,16 +2103,16 @@ def self.select(children, query) '$LT' => method(:select_CMP), '$GTE' => method(:select_CMP), '$LTE' => method(:select_CMP), - '$LIKE' => method(:select_CMP), + '$LIKE' => method(:select_CMP) } children.each do |child| terrs = [] validate(child, clone(q), { - 'errs' => terrs, - 'meta' => { S_BEXACT => true }, - 'extra' => select_extra, - }) + 'errs' => terrs, + 'meta' => { S_BEXACT => true }, + 'extra' => select_extra + }) results << child if terrs.empty? end @@ -2138,11 +2122,11 @@ def self.select(children, query) # --- setpath --- def self.setpath(store, path, val, injdef = nil) pt = typify(path) - if 0 < (T_list & pt) + if T_list.anybits?(pt) parts = path - elsif 0 < (T_string & pt) + elsif T_string.anybits?(pt) parts = path.split(S_DT) - elsif 0 < (T_number & pt) + elsif T_number.anybits?(pt) parts = [path] else return nil @@ -2152,12 +2136,12 @@ def self.setpath(store, path, val, injdef = nil) numparts = size(parts) parent = base ? getprop(store, base, store) : store - (0...numparts - 1).each do |pI| + (0...(numparts - 1)).each do |pI| part_key = getelem(parts, pI) next_parent = getprop(parent, part_key) unless isnode(next_parent) next_part = getelem(parts, pI + 1) - next_parent = (0 < (T_number & typify(next_part))) ? [] : {} + next_parent = T_number.anybits?(typify(next_part)) ? [] : {} setprop(parent, part_key, next_parent) end parent = next_parent @@ -2172,7 +2156,6 @@ def self.setpath(store, path, val, injdef = nil) parent end - # --- Injection class --- class Injection attr_accessor :mode, :full, :keyI, :keys, :key, :val, :parent, @@ -2207,19 +2190,15 @@ def descend parentkey = VoxgigStruct.getelem(@path, -2) if @dparent.nil? - if VoxgigStruct.size(@dpath) > 1 - @dpath = @dpath + [parentkey] - end - else - if parentkey - @dparent = VoxgigStruct.getprop(@dparent, parentkey) - lastpart = VoxgigStruct.getelem(@dpath, -1) - if lastpart == '$:' + parentkey.to_s - @dpath = VoxgigStruct.slice(@dpath, -1) - else - @dpath = @dpath + [parentkey] - end - end + @dpath += [parentkey] if VoxgigStruct.size(@dpath) > 1 + elsif parentkey + @dparent = VoxgigStruct.getprop(@dparent, parentkey) + lastpart = VoxgigStruct.getelem(@dpath, -1) + @dpath = if lastpart == "$:#{parentkey}" + VoxgigStruct.slice(@dpath, -1) + else + @dpath + [parentkey] + end end @dparent @@ -2274,11 +2253,8 @@ def setval(val, ancestor = nil) end def to_s(prefix = nil) - 'INJ' + (prefix ? '/' + prefix : '') + ':' + - VoxgigStruct.pad(VoxgigStruct.pathify(@path, 1)) + - (VoxgigStruct::MODENAME[VoxgigStruct::M_VAL] || '') + (@full ? '/full' : '') + ':' + - 'key=' + @keyI.to_s + '/' + @key.to_s + "INJ#{"/#{prefix}" if prefix}:#{VoxgigStruct.pad(VoxgigStruct.pathify(@path, 1))}" \ + "#{VoxgigStruct::MODENAME[VoxgigStruct::M_VAL] || ''}#{'/full' if @full}:key=#{@keyI}/#{@key}" end end - end diff --git a/rb/walk_bench.rb b/rb/walk_bench.rb index 20e77235..aaba5a4b 100644 --- a/rb/walk_bench.rb +++ b/rb/walk_bench.rb @@ -11,7 +11,8 @@ # Build a balanced tree of maps with given width and depth. # Total nodes: (width^(depth+1) - 1) / (width - 1). def build_tree(width, depth) - return 0 if depth == 0 + return 0 if depth.zero? + out = {} width.times do |i| out["k#{i}"] = build_tree(width, depth - 1) @@ -50,7 +51,7 @@ def measure(label, tree, runs) t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) VoxgigStruct.walk(tree, cb) t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - times << (t1 - t0) / 1_000_000.0 # ms + times << ((t1 - t0) / 1_000_000.0) # ms end times.sort! @@ -82,5 +83,5 @@ def measure(label, tree, runs) deep = build_tree(2, 20) measure('deep (w=2,d=20)', deep, 5) else - puts "walk_bench skipped (set WALK_BENCH=1 to run)" + puts 'walk_bench skipped (set WALK_BENCH=1 to run)' end diff --git a/rs/Makefile b/rs/Makefile index 95ff7562..a82a5bab 100644 --- a/rs/Makefile +++ b/rs/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test clean reset +.PHONY: inspect build test lint clippy fmt-check clean reset audit inspect: @echo "Rust toolchain:" @@ -14,8 +14,21 @@ build: test: cargo test +# Code quality: Clippy (treat warnings as errors) + rustfmt check. +lint: clippy fmt-check + +clippy: + cargo clippy --all-targets --all-features -- -D warnings + +fmt-check: + cargo fmt --all -- --check + clean: cargo clean reset: clean rm -f Cargo.lock + +# Supply-chain: scan Cargo dependencies against the RustSec advisory DB. +audit: + cargo audit diff --git a/rs/rustfmt.toml b/rs/rustfmt.toml new file mode 100644 index 00000000..6281db0b --- /dev/null +++ b/rs/rustfmt.toml @@ -0,0 +1,4 @@ +# rustfmt configuration for the Rust port. +# https://rust-lang.github.io/rustfmt/ +edition = "2021" +max_width = 100 diff --git a/rs/src/consts.rs b/rs/src/consts.rs index e30d4638..44e1c735 100644 --- a/rs/src/consts.rs +++ b/rs/src/consts.rs @@ -61,7 +61,7 @@ pub const T_STRING: u32 = 1 << 25; pub const T_FUNCTION: u32 = 1 << 24; pub const T_SYMBOL: u32 = 1 << 23; pub const T_NULL: u32 = 1 << 22; // the actual JSON null value. -// t -= 7 => t = 14 + // t -= 7 => t = 14 pub const T_LIST: u32 = 1 << 14; pub const T_MAP: u32 = 1 << 13; pub const T_INSTANCE: u32 = 1 << 12; @@ -81,21 +81,20 @@ pub const TYPENAME: [&str; 26] = [ "function", // 7 clz32(1<<24) "symbol", // 8 clz32(1<<23) "null", // 9 clz32(1<<22) - "", "", "", "", "", "", "", // 10..=16 + "", "", "", "", "", "", "", // 10..=16 "list", // 17 clz32(1<<14) "map", // 18 clz32(1<<13) "instance", // 19 clz32(1<<12) - "", "", "", "", // 20..=23 - "scalar", // 24 clz32(1<<7) - "node", // 25 clz32(1<<6) + "", "", "", "", // 20..=23 + "scalar", // 24 clz32(1<<7) + "node", // 25 clz32(1<<6) ]; pub const MAXDEPTH: i64 = 32; // ---- regexes ---------------------------------------------------------- pub static R_INTEGER_KEY: Lazy = Lazy::new(|| Regex::new(r"^[-0-9]+$").unwrap()); -pub static R_ESCAPE_REGEXP: Lazy = - Lazy::new(|| Regex::new(r"[.*+?^${}()|\[\]\\]").unwrap()); +pub static R_ESCAPE_REGEXP: Lazy = Lazy::new(|| Regex::new(r"[.*+?^${}()|\[\]\\]").unwrap()); pub static R_QUOTES: Lazy = Lazy::new(|| Regex::new(r#"""#).unwrap()); pub static R_DOT: Lazy = Lazy::new(|| Regex::new(r"\.").unwrap()); pub static R_CLONE_REF: Lazy = Lazy::new(|| Regex::new(r"^`\$REF:([0-9]+)`$").unwrap()); diff --git a/rs/src/lib.rs b/rs/src/lib.rs index fc4b3e1b..2739bfcc 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -24,7 +24,7 @@ pub use consts::{ pub use mini::{ clone, del_prop, esc_re, esc_url, filter, filter_vals, flatten, get_def, get_elem, get_elem_or_else, get_prop, has_key, is_empty, is_func, is_key, is_list, is_map, is_node, - items, jm, jt, jsonify, join, join_vals, keys_of, keysof_vec, pad, pathify, set_prop, size, + items, jm, join, join_vals, jsonify, jt, keys_of, keysof_vec, pad, pathify, set_prop, size, slice, str_key, stringify, type_name, typify, }; diff --git a/rs/src/major.rs b/rs/src/major.rs index 59252fe2..6e1f7cb5 100644 --- a/rs/src/major.rs +++ b/rs/src/major.rs @@ -46,7 +46,15 @@ pub fn walk( maxdepth: Option, ) -> Value { let mut path: Vec = Vec::new(); - walk_impl(val, before, after, maxdepth, &Value::Noval, &Value::Noval, &mut path) + walk_impl( + val, + before, + after, + maxdepth, + &Value::Noval, + &Value::Noval, + &mut path, + ) } fn walk_impl( @@ -168,7 +176,11 @@ pub fn merge(val: &Value, maxdepth: Option) -> Value { .unwrap_or(Value::Noval); get_prop(&prev, key, Value::Noval) } else { - dst_b.borrow().get(pi as usize).cloned().unwrap_or(Value::Noval) + dst_b + .borrow() + .get(pi as usize) + .cloned() + .unwrap_or(Value::Noval) }; let mut db = dst_b.borrow_mut(); grow(&mut db, pi as usize); @@ -359,8 +371,13 @@ impl Injection { { let b = this.borrow(); if let Value::Map(m) = &b.meta { - let cur = m.borrow().get("__d").and_then(|v| v.as_num()).unwrap_or(0.0); - m.borrow_mut().insert("__d".to_string(), Value::Num(cur + 1.0)); + let cur = m + .borrow() + .get("__d") + .and_then(|v| v.as_num()) + .unwrap_or(0.0); + m.borrow_mut() + .insert("__d".to_string(), Value::Num(cur + 1.0)); } } let (parentkey, has_dparent, dpath_len, last_part) = { @@ -370,7 +387,12 @@ impl Injection { } else { None }; - (parentkey, !b.dparent.is_noval(), b.dpath.len(), b.dpath.last().cloned()) + ( + parentkey, + !b.dparent.is_noval(), + b.dpath.len(), + b.dpath.last().cloned(), + ) }; if !has_dparent { @@ -396,7 +418,21 @@ impl Injection { } pub fn child(this: &Inj, key_i: i64, keys: SVec) -> Inj { - let (key, parent_val, cval, path, nodes, mode, handler, modify, base, meta, errs, dpath, dparent) = { + let ( + key, + parent_val, + cval, + path, + nodes, + mode, + handler, + modify, + base, + meta, + errs, + dpath, + dparent, + ) = { let b = this.borrow(); let key = str_key( keys.borrow() @@ -521,7 +557,9 @@ pub fn get_path_inj(store: &Value, path: &Value, injdef: Option<&Inj>) -> Value None => store.clone(), }; let numparts = parts.len(); - let dparent = injdef.map(|i| i.borrow().dparent.clone()).unwrap_or(Value::Noval); + let dparent = injdef + .map(|i| i.borrow().dparent.clone()) + .unwrap_or(Value::Noval); let mut val = store.clone(); @@ -606,9 +644,7 @@ pub fn get_path_inj(store: &Value, path: &Value, injdef: Option<&Inj>) -> Value if ascends <= dpath.len() as i64 { val = get_path_inj( store, - &Value::list( - fullpath.into_iter().map(Value::Str).collect(), - ), + &Value::list(fullpath.into_iter().map(Value::Str).collect()), None, ); } else { @@ -760,7 +796,13 @@ fn injectstr(val: &str, store: &Value, inj: Option<&Inj>) -> Value { match &found { Value::Noval => String::new(), Value::Str(s) => s.clone(), - other => jsonify(other, Some(&JsonFlags { indent: 0, offset: 0 })), + other => jsonify( + other, + Some(&JsonFlags { + indent: 0, + offset: 0, + }), + ), } }) .to_string(); @@ -1012,8 +1054,9 @@ pub fn inject_child(child: Value, store: &Value, inj: &Inj) -> Inj { cinj } -const FORMATTER_NAMES: [&str; 7] = - ["identity", "upper", "lower", "string", "number", "integer", "concat"]; +const FORMATTER_NAMES: [&str; 7] = [ + "identity", "upper", "lower", "string", "number", "integer", "concat", +]; fn apply_formatter(name: &str, k: &Value, v: &Value) -> Value { match name { @@ -1061,7 +1104,13 @@ fn apply_formatter(name: &str, k: &Value, v: &Value) -> Value { Value::str( items_vec(v) .iter() - .map(|(_, n)| if is_node(n) { String::new() } else { js_string(n) }) + .map(|(_, n)| { + if is_node(n) { + String::new() + } else { + js_string(n) + } + }) .collect::>() .join(""), ) @@ -1085,7 +1134,10 @@ fn transform_format(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { }; let name = get_prop(&parent, &Value::Num(1.0), Value::Noval); let child = get_prop(&parent, &Value::Num(2.0), Value::Noval); - let tkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let tkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let nlen = nodes.len(); let target = if nlen >= 2 { nodes[nlen - 2].clone() @@ -1103,7 +1155,10 @@ fn transform_format(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { .filter(|n| FORMATTER_NAMES.contains(n)) .map(|s| s.to_string()); if fname.is_none() && !is_func(&name) { - errs_push(inj, format!("$FORMAT: unknown format: {}.", js_string(&name))); + errs_push( + inj, + format!("$FORMAT: unknown format: {}.", js_string(&name)), + ); return Value::Noval; } @@ -1114,9 +1169,8 @@ fn transform_format(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { walk(resolved, Some(&mut fmt), None, None) } else if let Value::Func(f) = &name { let f = f.clone(); - let mut fmt = |_k: &Value, v: &Value, _p: &Value, _t: &[String]| -> Value { - f(inj, v, "", store) - }; + let mut fmt = + |_k: &Value, v: &Value, _p: &Value, _t: &[String]| -> Value { f(inj, v, "", store) }; walk(resolved, Some(&mut fmt), None, None) } else { resolved @@ -1146,7 +1200,10 @@ fn transform_apply(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { } let apply = ia.get(1).cloned().unwrap_or(Value::Noval); let child = ia.get(2).cloned().unwrap_or(Value::Noval); - let tkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let tkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let nlen = nodes.len(); let target = if nlen >= 2 { nodes[nlen - 2].clone() @@ -1229,7 +1286,10 @@ fn transform_merge(inj: &Inj, _v: &Value, _r: &str, _store: &Value) -> Value { args = Value::list(vec![args]); } Injection::setval(inj, Value::Noval, None); // remove $MERGE key from parent - let args_vec: Vec = args.as_list().map(|l| l.borrow().clone()).unwrap_or_default(); + let args_vec: Vec = args + .as_list() + .map(|l| l.borrow().clone()) + .unwrap_or_default(); let mut mergelist: Vec = vec![parent.clone()]; mergelist.extend(args_vec); mergelist.push(clone(&parent)); @@ -1301,14 +1361,25 @@ fn transform_ref(inj: &Inj, val: &Value, _r: &str, store: &Value) -> Value { let tinj = Injection::child( inj, 0, - Rc::new(RefCell::new(vec![tpath.last().cloned().unwrap_or_default()])), + Rc::new(RefCell::new(vec![tpath + .last() + .cloned() + .unwrap_or_default()])), ); { let mut b = tinj.borrow_mut(); b.path = tpath.clone(); let nlen = nodes.len(); - b.nodes = if nlen >= 1 { nodes[..nlen - 1].to_vec() } else { Vec::new() }; - b.parent = if nlen >= 2 { nodes[nlen - 2].clone() } else { Value::Noval }; + b.nodes = if nlen >= 1 { + nodes[..nlen - 1].to_vec() + } else { + Vec::new() + }; + b.parent = if nlen >= 2 { + nodes[nlen - 2].clone() + } else { + Value::Noval + }; b.val = tref.clone(); b.dpath = cpath.clone(); b.dparent = tcur.clone(); @@ -1345,7 +1416,12 @@ fn transform_each(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { let (parent, path, nodes, base) = { let b = inj.borrow(); - (b.parent.clone(), b.path.clone(), b.nodes.clone(), b.base.clone()) + ( + b.parent.clone(), + b.path.clone(), + b.nodes.clone(), + b.base.clone(), + ) }; let args: Vec = slice(parent.clone(), Some(1), None, false) .as_list() @@ -1360,11 +1436,18 @@ fn transform_each(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { let child = ia.get(2).cloned().unwrap_or(Value::Noval); let srcpath_str = srcpath.as_str().unwrap_or("").to_string(); - let srcstore = get_prop(store, &Value::str(base.clone().unwrap_or_default()), store.clone()); + let srcstore = get_prop( + store, + &Value::str(base.clone().unwrap_or_default()), + store.clone(), + ); let src = get_path_inj(&srcstore, &srcpath, Some(inj)); let srctype = typify(&src); - let tkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let tkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let nlen = nodes.len(); let target = if nlen >= 2 { nodes[nlen - 2].clone() @@ -1403,7 +1486,10 @@ fn transform_each(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { } else { Value::list(items_vec(&src).into_iter().map(|(_, v)| v).collect()) }; - let ckey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let ckey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let tpath = slice_str_vec(&path, Some(-1), None); let mut dpath: Vec = vec![S_DTOP.to_string()]; dpath.extend(srcpath_split(&srcpath_str)); @@ -1424,7 +1510,11 @@ fn transform_each(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { { let mut b = tinj.borrow_mut(); b.path = tpath; - b.nodes = if nlen >= 1 { nodes[..nlen - 1].to_vec() } else { Vec::new() }; + b.nodes = if nlen >= 1 { + nodes[..nlen - 1].to_vec() + } else { + Vec::new() + }; b.parent = b.nodes.last().cloned().unwrap_or(Value::Noval); b.val = tval_v.clone(); b.dpath = dpath; @@ -1448,10 +1538,19 @@ fn transform_pack(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { } let (key, parent, path, nodes, base) = { let b = inj.borrow(); - (b.key.clone(), b.parent.clone(), b.path.clone(), b.nodes.clone(), b.base.clone()) + ( + b.key.clone(), + b.parent.clone(), + b.path.clone(), + b.nodes.clone(), + b.base.clone(), + ) }; let args = get_prop(&parent, &Value::str(key), Value::Noval); - let args_vec: Vec = args.as_list().map(|l| l.borrow().clone()).unwrap_or_default(); + let args_vec: Vec = args + .as_list() + .map(|l| l.borrow().clone()) + .unwrap_or_default(); let ia = injector_args(&[T_STRING as i64, T_ANY as i64], &args_vec); if let Value::Str(e) = &ia[0] { errs_push(inj, format!("$EACH: {e}")); @@ -1461,21 +1560,34 @@ fn transform_pack(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { let origchildspec = ia.get(2).cloned().unwrap_or(Value::Noval); let srcpath_str = srcpath.as_str().unwrap_or("").to_string(); - let tkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let tkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let pathsize = path.len(); let nlen = nodes.len(); let target = if pathsize >= 2 { nodes.get(pathsize - 2).cloned().unwrap_or(Value::Noval) } else { - nodes.get(pathsize.saturating_sub(1)).cloned().unwrap_or(Value::Noval) + nodes + .get(pathsize.saturating_sub(1)) + .cloned() + .unwrap_or(Value::Noval) }; let target = if target.is_noval() { - nodes.get(pathsize.saturating_sub(1)).cloned().unwrap_or(Value::Noval) + nodes + .get(pathsize.saturating_sub(1)) + .cloned() + .unwrap_or(Value::Noval) } else { target }; - let srcstore = get_prop(store, &Value::str(base.clone().unwrap_or_default()), store.clone()); + let srcstore = get_prop( + store, + &Value::str(base.clone().unwrap_or_default()), + store.clone(), + ); let src_raw = get_path_inj(&srcstore, &srcpath, Some(inj)); let src: Value = if is_list(&src_raw) { src_raw @@ -1551,7 +1663,10 @@ fn transform_pack(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { set_prop(tsrc.clone(), &kn, n); } let tpath = slice_str_vec(&path, Some(-1), None); - let ckey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let ckey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let mut dpath: Vec = vec![S_DTOP.to_string()]; dpath.extend(srcpath_split(&srcpath_str)); dpath.push(format!("$:{ckey}")); @@ -1568,7 +1683,11 @@ fn transform_pack(inj: &Inj, _val: &Value, _r: &str, store: &Value) -> Value { { let mut b = tinj.borrow_mut(); b.path = tpath; - b.nodes = if nlen >= 1 { nodes[..nlen - 1].to_vec() } else { Vec::new() }; + b.nodes = if nlen >= 1 { + nodes[..nlen - 1].to_vec() + } else { + Vec::new() + }; b.parent = b.nodes.last().cloned().unwrap_or(Value::Noval); b.val = tval.clone(); b.dpath = dpath; @@ -1653,14 +1772,8 @@ pub fn transform( store_base.insert("$EACH".to_string(), Value::func(transform_each)); store_base.insert("$PACK".to_string(), Value::func(transform_pack)); store_base.insert("$REF".to_string(), Value::func(transform_ref)); - store_base.insert( - "$FORMAT".to_string(), - Value::func(transform_format), - ); - store_base.insert( - "$APPLY".to_string(), - Value::func(transform_apply), - ); + store_base.insert("$FORMAT".to_string(), Value::func(transform_format)); + store_base.insert("$APPLY".to_string(), Value::func(transform_apply)); let store = merge( &Value::list(vec![ @@ -1691,7 +1804,9 @@ fn iso_now() -> String { // best-effort ISO-8601 UTC string; the corpus can't assert the exact // value (it changes), so granularity to the second is fine. use std::time::{SystemTime, UNIX_EPOCH}; - let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default(); + let dur = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default(); let secs = dur.as_secs() as i64; let millis = dur.subsec_millis(); // days since 1970-01-01 @@ -1699,9 +1814,7 @@ fn iso_now() -> String { let tod = secs.rem_euclid(86_400); let (h, m, s) = (tod / 3600, (tod % 3600) / 60, tod % 60); let (y, mo, d) = civil_from_days(days); - format!( - "{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.{millis:03}Z" - ) + format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.{millis:03}Z") } fn civil_from_days(z: i64) -> (i64, i64, i64) { @@ -1757,7 +1870,10 @@ fn validate_string(inj: &Inj, _v: &Value, _r: &str, _store: &Value) -> Value { if matches!(&out, Value::Str(s) if s.is_empty()) { errs_push( inj, - format!("Empty string at {}", pathify(&path_value(&path), Some(1), None)), + format!( + "Empty string at {}", + pathify(&path_value(&path), Some(1), None) + ), ); return Value::Noval; } @@ -1807,19 +1923,33 @@ fn tvals_desc(tvals: &[Value]) -> String { fn validate_child(inj: &Inj, _v: &Value, _r: &str, _store: &Value) -> Value { let (mode, key, parent, path, dparent) = { let b = inj.borrow(); - (b.mode, b.key.clone(), b.parent.clone(), b.path.clone(), b.dparent.clone()) + ( + b.mode, + b.key.clone(), + b.parent.clone(), + b.path.clone(), + b.dparent.clone(), + ) }; if mode == M_KEYPRE { let childtm = get_prop(&parent, &Value::str(key), Value::Noval); - let pkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let pkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let mut tval = get_prop(&dparent, &Value::str(pkey), Value::Noval); if tval.is_noval() { tval = Value::empty_map(); } else if !is_map(&tval) { errs_push( inj, - invalid_type_msg(&path[..path.len().saturating_sub(1)], S_object, typify(&tval), &tval), + invalid_type_msg( + &path[..path.len().saturating_sub(1)], + S_object, + typify(&tval), + &tval, + ), ); return Value::Noval; } @@ -1845,7 +1975,12 @@ fn validate_child(inj: &Inj, _v: &Value, _r: &str, _store: &Value) -> Value { if !is_list(&dparent) { errs_push( inj, - invalid_type_msg(&path[..path.len().saturating_sub(1)], S_list, typify(&dparent), &dparent), + invalid_type_msg( + &path[..path.len().saturating_sub(1)], + S_list, + typify(&dparent), + &dparent, + ), ); let plen = parent.as_list().map(|l| l.borrow().len()).unwrap_or(0) as i64; inj.borrow_mut().key_i = plen; @@ -1911,7 +2046,10 @@ fn validate_one(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { } for tval in &tvals { let terrs = Value::empty_list(); - let mut vstore = match merge(&Value::list(vec![Value::empty_map(), store.clone()]), Some(1)) { + let mut vstore = match merge( + &Value::list(vec![Value::empty_map(), store.clone()]), + Some(1), + ) { v @ Value::Map(_) => v, _ => Value::empty_map(), }; @@ -1935,7 +2073,11 @@ fn validate_one(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { inj, invalid_type_msg( &path_after, - &format!("{}{}", if tvals.len() > 1 { "one of " } else { "" }, valdesc), + &format!( + "{}{}", + if tvals.len() > 1 { "one of " } else { "" }, + valdesc + ), typify(&dparent), &dparent, ), @@ -1993,7 +2135,9 @@ fn validate_exact(inj: &Inj, _v: &Value, _r: &str, _store: &Value) -> Value { for tval in &tvals { let mut exactmatch = tval == &dparent; if !exactmatch && is_node(tval) { - let cs = currentstr.get_or_insert_with(|| stringify(&dparent, None, false)).clone(); + let cs = currentstr + .get_or_insert_with(|| stringify(&dparent, None, false)) + .clone(); exactmatch = stringify(tval, None, false) == cs; } if exactmatch { @@ -2042,13 +2186,19 @@ fn validation_modify(pval: &Value, key: &Value, parent: &Value, inj: &Inj, _stor } let ctype = typify(&cval); if ptype != ctype && !pval.is_noval() { - errs_push(inj, invalid_type_msg(&path, &type_name(ptype), ctype, &cval)); + errs_push( + inj, + invalid_type_msg(&path, &type_name(ptype), ctype, &cval), + ); return; } if is_map(&cval) { if !is_map(pval) { - errs_push(inj, invalid_type_msg(&path, &type_name(ptype), ctype, &cval)); + errs_push( + inj, + invalid_type_msg(&path, &type_name(ptype), ctype, &cval), + ); return; } let ckeys = keysof_vec(&cval); @@ -2082,18 +2232,31 @@ fn validation_modify(pval: &Value, key: &Value, parent: &Value, inj: &Inj, _stor } } else if is_list(&cval) { if !is_list(pval) { - errs_push(inj, invalid_type_msg(&path, &type_name(ptype), ctype, &cval)); + errs_push( + inj, + invalid_type_msg(&path, &type_name(ptype), ctype, &cval), + ); } } else if exact { if &cval != pval { let pathmsg = if path.len() > 1 { - format!("at field {}{}", pathify(&path_value(&path), Some(1), None), S_VIZ) + format!( + "at field {}{}", + pathify(&path_value(&path), Some(1), None), + S_VIZ + ) } else { String::new() }; errs_push( inj, - format!("Value {}{} should equal {}{}", pathmsg, js_string(&cval), js_string(pval), S_DT), + format!( + "Value {}{} should equal {}{}", + pathmsg, + js_string(&cval), + js_string(pval), + S_DT + ), ); } } else { @@ -2132,12 +2295,22 @@ pub fn validate( // build the validator store let mut vmap: indexmap::IndexMap = indexmap::IndexMap::new(); - for k in ["$DELETE", "$COPY", "$KEY", "$META", "$MERGE", "$EACH", "$PACK"] { + for k in [ + "$DELETE", "$COPY", "$KEY", "$META", "$MERGE", "$EACH", "$PACK", + ] { vmap.insert(k.to_string(), Value::Null); } vmap.insert("$STRING".to_string(), Value::func(validate_string)); for k in [ - "$NUMBER", "$INTEGER", "$DECIMAL", "$BOOLEAN", "$NULL", "$NIL", "$MAP", "$LIST", "$FUNCTION", + "$NUMBER", + "$INTEGER", + "$DECIMAL", + "$BOOLEAN", + "$NULL", + "$NIL", + "$MAP", + "$LIST", + "$FUNCTION", "$INSTANCE", ] { vmap.insert(k.to_string(), Value::func(validate_type)); @@ -2176,8 +2349,7 @@ pub fn validate( ..Default::default() }; - let out = transform(data, spec, Some(&td)) - .unwrap_or(Value::Noval); + let out = transform(data, spec, Some(&td)).unwrap_or(Value::Noval); let errlen = errs.as_list().map(|l| l.borrow().len()).unwrap_or(0); if errlen > 0 && !collect { @@ -2215,7 +2387,10 @@ fn js_gt(a: &Value, b: &Value) -> bool { } fn select_subvalidate(point: &Value, term: &Value, store: &Value, meta: &Value) -> bool { - let vstore = match merge(&Value::list(vec![Value::empty_map(), store.clone()]), Some(1)) { + let vstore = match merge( + &Value::list(vec![Value::empty_map(), store.clone()]), + Some(1), + ) { v @ Value::Map(_) => v, _ => Value::empty_map(), }; @@ -2228,7 +2403,10 @@ fn select_subvalidate(point: &Value, term: &Value, store: &Value, meta: &Value) ..Default::default() }; let _ = validate(point, term, Some(&vd)); - terrs.as_list().map(|l| l.borrow().is_empty()).unwrap_or(true) + terrs + .as_list() + .map(|l| l.borrow().is_empty()) + .unwrap_or(true) } fn select_and(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { @@ -2237,7 +2415,13 @@ fn select_and(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { } let (key, parent, path, nodes, meta) = { let b = inj.borrow(); - (b.key.clone(), b.parent.clone(), b.path.clone(), b.nodes.clone(), b.meta.clone()) + ( + b.key.clone(), + b.parent.clone(), + b.path.clone(), + b.nodes.clone(), + b.meta.clone(), + ) }; let terms: Vec = get_prop(&parent, &Value::str(key), Value::Noval) .as_list() @@ -2259,7 +2443,10 @@ fn select_and(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { ); } } - let gkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let gkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let nlen = nodes.len(); if nlen >= 2 { set_prop(nodes[nlen - 2].clone(), &Value::str(gkey), point); @@ -2273,7 +2460,13 @@ fn select_or(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { } let (key, parent, path, nodes, meta) = { let b = inj.borrow(); - (b.key.clone(), b.parent.clone(), b.path.clone(), b.nodes.clone(), b.meta.clone()) + ( + b.key.clone(), + b.parent.clone(), + b.path.clone(), + b.nodes.clone(), + b.meta.clone(), + ) }; let terms: Vec = get_prop(&parent, &Value::str(key), Value::Noval) .as_list() @@ -2283,7 +2476,10 @@ fn select_or(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { let point = get_path_inj(store, &path_value(&ppath), None); for term in &terms { if select_subvalidate(&point, term, store, &meta) { - let gkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let gkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let nlen = nodes.len(); if nlen >= 2 { set_prop(nodes[nlen - 2].clone(), &Value::str(gkey), point); @@ -2310,7 +2506,13 @@ fn select_not(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { } let (key, parent, path, nodes, meta) = { let b = inj.borrow(); - (b.key.clone(), b.parent.clone(), b.path.clone(), b.nodes.clone(), b.meta.clone()) + ( + b.key.clone(), + b.parent.clone(), + b.path.clone(), + b.nodes.clone(), + b.meta.clone(), + ) }; let term = get_prop(&parent, &Value::str(key), Value::Noval); let ppath = slice_str_vec(&path, Some(-1), None); @@ -2327,7 +2529,10 @@ fn select_not(inj: &Inj, _v: &Value, _r: &str, store: &Value) -> Value { ), ); } - let gkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let gkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let nlen = nodes.len(); if nlen >= 2 { set_prop(nodes[nlen - 2].clone(), &Value::str(gkey), point); @@ -2341,10 +2546,18 @@ fn select_cmp(inj: &Inj, _v: &Value, r: &str, store: &Value) -> Value { } let (key, parent, path, nodes) = { let b = inj.borrow(); - (b.key.clone(), b.parent.clone(), b.path.clone(), b.nodes.clone()) + ( + b.key.clone(), + b.parent.clone(), + b.path.clone(), + b.nodes.clone(), + ) }; let term = get_prop(&parent, &Value::str(key), Value::Noval); - let gkey = path.get(path.len().saturating_sub(2)).cloned().unwrap_or_default(); + let gkey = path + .get(path.len().saturating_sub(2)) + .cloned() + .unwrap_or_default(); let ppath = slice_str_vec(&path, Some(-1), None); let point = get_path_inj(store, &path_value(&ppath), None); @@ -2442,7 +2655,11 @@ pub fn select(children: &Value, query: &Value) -> Value { ..Default::default() }; let _ = validate(child, &clone(&q), Some(&vd)); - if errs.as_list().map(|l| l.borrow().is_empty()).unwrap_or(true) { + if errs + .as_list() + .map(|l| l.borrow().is_empty()) + .unwrap_or(true) + { results.push(child.clone()); } } diff --git a/rs/src/mini.rs b/rs/src/mini.rs index 2976c432..4a99e66b 100644 --- a/rs/src/mini.rs +++ b/rs/src/mini.rs @@ -99,20 +99,8 @@ pub fn size(val: &Value) -> i64 { Value::List(l) => l.borrow().len() as i64, Value::Map(m) => m.borrow().len() as i64, Value::Str(s) => s.encode_utf16().count() as i64, - Value::Num(n) => { - if n.is_finite() { - n.floor() as i64 - } else { - 0 - } - } - Value::Bool(b) => { - if *b { - 1 - } else { - 0 - } - } + Value::Num(n) if n.is_finite() => n.floor() as i64, + Value::Bool(b) => i64::from(*b), _ => 0, } } @@ -307,7 +295,11 @@ pub fn get_prop(val: &Value, key: &Value, alt: Value) -> Value { .and_then(|i| lb.get(i).cloned()) .unwrap_or(Value::Noval) } - Value::Map(m) => m.borrow().get(&js_string(key)).cloned().unwrap_or(Value::Noval), + Value::Map(m) => m + .borrow() + .get(&js_string(key)) + .cloned() + .unwrap_or(Value::Noval), Value::Sentinel(s) => { if js_string(key) == s.tag { Value::Bool(true) @@ -491,9 +483,8 @@ pub fn join(arr: &Value, sep: Option<&str>, url: bool) -> String { }; // filter to string non-empty entries - let str_entries: Vec = filter_vals(arr, |n| { - matches!(&n.1, Value::Str(s) if !s.is_empty()) - }); + let str_entries: Vec = + filter_vals(arr, |n| matches!(&n.1, Value::Str(s) if !s.is_empty())); let processed: Vec = items_vec(&Value::list(str_entries)) .into_iter() @@ -549,7 +540,10 @@ pub struct JsonFlags { impl Default for JsonFlags { fn default() -> Self { - JsonFlags { indent: 2, offset: 0 } + JsonFlags { + indent: 2, + offset: 0, + } } } @@ -618,7 +612,8 @@ fn json_encode(val: &Value, indent: usize, level: usize) -> Option { let items: Vec = lb .iter() .map(|v| { - let enc = json_encode(v, indent, level + 1).unwrap_or_else(|| "null".to_string()); + let enc = + json_encode(v, indent, level + 1).unwrap_or_else(|| "null".to_string()); if indent > 0 { format!("{inner_pad}{enc}") } else { @@ -646,7 +641,8 @@ fn json_encode(val: &Value, indent: usize, level: usize) -> Option { let items: Vec = entries .iter() .map(|(k, v)| { - let enc = json_encode(v, indent, level + 1).unwrap_or_else(|| "null".to_string()); + let enc = + json_encode(v, indent, level + 1).unwrap_or_else(|| "null".to_string()); if indent > 0 { format!("{inner_pad}{}{colon}{enc}", json_quote(k)) } else { @@ -682,7 +678,11 @@ fn json_quote(s: &str) -> String { pub fn stringify(val: &Value, maxlen: Option, pretty: bool) -> String { let mut valstr; if val.is_noval() { - return if pretty { "<>".to_string() } else { String::new() }; + return if pretty { + "<>".to_string() + } else { + String::new() + }; } if let Value::Str(s) = val { valstr = s.clone(); @@ -823,7 +823,12 @@ pub fn pathify(val: &Value, startin: Option, endin: Option) -> String if let Some(path) = &path { if start >= 0 { let plen = path.len() as i64; - let sliced = slice(Value::list(path.clone()), Some(start), Some(plen - end), false); + let sliced = slice( + Value::list(path.clone()), + Some(start), + Some(plen - end), + false, + ); let sv: Vec = sliced .as_list() .map(|l| l.borrow().clone()) diff --git a/rs/src/value.rs b/rs/src/value.rs index 983c0e5f..0d56585d 100644 --- a/rs/src/value.rs +++ b/rs/src/value.rs @@ -200,7 +200,8 @@ impl PartialEq for Value { let a = a.borrow(); let b = b.borrow(); a.len() == b.len() - && a.iter().all(|(k, v)| b.get(k).map(|w| v == w).unwrap_or(false)) + && a.iter() + .all(|(k, v)| b.get(k).map(|w| v == w).unwrap_or(false)) } _ => false, } @@ -352,7 +353,9 @@ pub fn js_string_to_number(s: &str) -> f64 { return 0.0; } if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) { - return i64::from_str_radix(hex, 16).map(|n| n as f64).unwrap_or(f64::NAN); + return i64::from_str_radix(hex, 16) + .map(|n| n as f64) + .unwrap_or(f64::NAN); } t.parse::().unwrap_or(f64::NAN) } diff --git a/rs/tests/corpus.rs b/rs/tests/corpus.rs index f2b40ca9..0715ba29 100644 --- a/rs/tests/corpus.rs +++ b/rs/tests/corpus.rs @@ -140,7 +140,10 @@ struct Run { impl Run { fn new() -> Self { - Run { failures: Vec::new(), passed: 0 } + Run { + failures: Vec::new(), + passed: 0, + } } /// Run a test set: `label` for messages, `null_flag` matches the TS @@ -207,7 +210,9 @@ impl Run { self.passed += 1; continue; } - if matched && (expected == Value::str(NULLMARK) || expected.is_noval() || expected.is_null()) { + if matched + && (expected == Value::str(NULLMARK) || expected.is_noval() || expected.is_null()) + { self.passed += 1; continue; } @@ -253,7 +258,8 @@ impl Run { match result { Err(msg) => { if err_expected.is_noval() { - self.failures.push(format!("{label}#{i}: unexpected error: {msg}")); + self.failures + .push(format!("{label}#{i}: unexpected error: {msg}")); continue; } let want = match &err_expected { @@ -267,7 +273,9 @@ impl Run { let ok = if let Some(rem) = want.strip_prefix('/').and_then(|s| s.strip_suffix('/')) { - regex::Regex::new(rem).map(|re| re.is_match(&msg)).unwrap_or(false) + regex::Regex::new(rem) + .map(|re| re.is_match(&msg)) + .unwrap_or(false) } else { msg.to_lowercase().contains(&want.to_lowercase()) }; @@ -298,15 +306,14 @@ impl Run { base.insert("out".to_string(), res.clone()); let base = Value::map(base); if let Some(why) = match_check(&match_spec, &base) { - self.failures.push(format!("{label}#{i}: match failed ({why})")); + self.failures + .push(format!("{label}#{i}: match failed ({why})")); continue; } matched = true; } if res == expected - || (matched - && (expected == Value::str(NULLMARK) - || expected.is_nullish())) + || (matched && (expected == Value::str(NULLMARK) || expected.is_nullish())) { self.passed += 1; } else { @@ -412,7 +419,12 @@ fn inject_def_from_value(v: &Value) -> InjectDef { if let Value::Str(s) = dpath { d.dpath = Some(s.split('.').map(|x| x.to_string()).collect()); } else if let Value::List(l) = &dpath { - d.dpath = Some(l.borrow().iter().map(|x| as_str_opt(x).unwrap_or_default()).collect()); + d.dpath = Some( + l.borrow() + .iter() + .map(|x| as_str_opt(x).unwrap_or_default()) + .collect(), + ); } } d @@ -433,13 +445,27 @@ fn corpus() { } // -------- minor -------------------------------------------------- - run.run_set(&set!("minor", "isnode"), true, "minor-isnode", |v| b(is_node(&v))); - run.run_set(&set!("minor", "ismap"), true, "minor-ismap", |v| b(is_map(&v))); - run.run_set(&set!("minor", "islist"), true, "minor-islist", |v| b(is_list(&v))); - run.run_set(&set!("minor", "iskey"), false, "minor-iskey", |v| b(is_key(&v))); - run.run_set(&set!("minor", "strkey"), false, "minor-strkey", |v| Value::str(str_key(v))); - run.run_set(&set!("minor", "isempty"), false, "minor-isempty", |v| b(is_empty(&v))); - run.run_set(&set!("minor", "isfunc"), true, "minor-isfunc", |v| b(is_func(&v))); + run.run_set(&set!("minor", "isnode"), true, "minor-isnode", |v| { + b(is_node(&v)) + }); + run.run_set(&set!("minor", "ismap"), true, "minor-ismap", |v| { + b(is_map(&v)) + }); + run.run_set(&set!("minor", "islist"), true, "minor-islist", |v| { + b(is_list(&v)) + }); + run.run_set(&set!("minor", "iskey"), false, "minor-iskey", |v| { + b(is_key(&v)) + }); + run.run_set(&set!("minor", "strkey"), false, "minor-strkey", |v| { + Value::str(str_key(v)) + }); + run.run_set(&set!("minor", "isempty"), false, "minor-isempty", |v| { + b(is_empty(&v)) + }); + run.run_set(&set!("minor", "isfunc"), true, "minor-isfunc", |v| { + b(is_func(&v)) + }); run.run_set(&set!("minor", "clone"), false, "minor-clone", |v| clone(&v)); run.run_set(&set!("minor", "filter"), true, "minor-filter", |vin| { let val = vget(&vin, "val"); @@ -453,15 +479,24 @@ fn corpus() { run.run_set(&set!("minor", "flatten"), true, "minor-flatten", |vin| { flatten(&vget(&vin, "val"), as_i64_opt(&vget(&vin, "depth"))) }); - run.run_set(&set!("minor", "escre"), true, "minor-escre", |v| Value::str(esc_re(&v))); - run.run_set(&set!("minor", "escurl"), true, "minor-escurl", |v| Value::str(esc_url(&v))); - run.run_set(&set!("minor", "stringify"), true, "minor-stringify", |vin| { - let mut val = vget(&vin, "val"); - if val == Value::str(NULLMARK) { - val = Value::str("null"); - } - Value::str(stringify(&val, as_i64_opt(&vget(&vin, "max")), false)) + run.run_set(&set!("minor", "escre"), true, "minor-escre", |v| { + Value::str(esc_re(&v)) + }); + run.run_set(&set!("minor", "escurl"), true, "minor-escurl", |v| { + Value::str(esc_url(&v)) }); + run.run_set( + &set!("minor", "stringify"), + true, + "minor-stringify", + |vin| { + let mut val = vget(&vin, "val"); + if val == Value::str(NULLMARK) { + val = Value::str("null"); + } + Value::str(stringify(&val, as_i64_opt(&vget(&vin, "max")), false)) + }, + ); run.run_set(&set!("minor", "jsonify"), false, "minor-jsonify", |vin| { let flags_v = vget(&vin, "flags"); let flags = if flags_v.is_noval() { @@ -475,7 +510,11 @@ fn corpus() { }); run.run_set(&set!("minor", "pathify"), true, "minor-pathify", |vin| { let pathv = vget(&vin, "path"); - let path = if pathv == Value::str(NULLMARK) { Value::Noval } else { pathv.clone() }; + let path = if pathv == Value::str(NULLMARK) { + Value::Noval + } else { + pathv.clone() + }; let mut ps = pathify(&path, as_i64_opt(&vget(&vin, "from")), None); ps = ps.replace("__NULL__.", ""); if pathv == Value::str(NULLMARK) { @@ -501,7 +540,9 @@ fn corpus() { run.run_set(&set!("minor", "haskey"), false, "minor-haskey", |vin| { b(has_key(&vget(&vin, "src"), &vget(&vin, "key"))) }); - run.run_set(&set!("minor", "keysof"), true, "minor-keysof", |v| keys_of(&v)); + run.run_set(&set!("minor", "keysof"), true, "minor-keysof", |v| { + keys_of(&v) + }); run.run_set(&set!("minor", "join"), false, "minor-join", |vin| { let val = vget(&vin, "val"); let sep = as_str_opt(&vget(&vin, "sep")); @@ -511,16 +552,34 @@ fn corpus() { run.run_set(&set!("minor", "typename"), true, "minor-typename", |v| { Value::str(type_name(v.as_num().unwrap_or(0.0) as i64)) }); - run.run_set(&set!("minor", "typify"), false, "minor-typify", |v| Value::Num(typify(&v) as f64)); - run.run_set(&set!("minor", "size"), false, "minor-size", |v| Value::Num(size(&v) as f64)); + run.run_set(&set!("minor", "typify"), false, "minor-typify", |v| { + Value::Num(typify(&v) as f64) + }); + run.run_set(&set!("minor", "size"), false, "minor-size", |v| { + Value::Num(size(&v) as f64) + }); run.run_set(&set!("minor", "slice"), false, "minor-slice", |vin| { - slice(vget(&vin, "val"), as_i64_opt(&vget(&vin, "start")), as_i64_opt(&vget(&vin, "end")), false) + slice( + vget(&vin, "val"), + as_i64_opt(&vget(&vin, "start")), + as_i64_opt(&vget(&vin, "end")), + false, + ) }); run.run_set(&set!("minor", "pad"), false, "minor-pad", |vin| { - Value::str(pad(vget(&vin, "val"), as_i64_opt(&vget(&vin, "pad")), as_str_opt(&vget(&vin, "char")))) + Value::str(pad( + vget(&vin, "val"), + as_i64_opt(&vget(&vin, "pad")), + as_str_opt(&vget(&vin, "char")), + )) }); run.run_set(&set!("minor", "setpath"), false, "minor-setpath", |vin| { - set_path(&vget(&vin, "store"), &vget(&vin, "path"), vget(&vin, "val"), None) + set_path( + &vget(&vin, "store"), + &vget(&vin, "path"), + vget(&vin, "val"), + None, + ) }); // -------- walk --------------------------------------------------- @@ -547,7 +606,11 @@ fn corpus() { stringify(k, None, false), stringify(v, None, false), stringify(p, None, false), - pathify(&Value::list(path.iter().cloned().map(Value::Str).collect()), None, None), + pathify( + &Value::list(path.iter().cloned().map(Value::Str).collect()), + None, + None + ), ))); v.clone() }; @@ -559,7 +622,11 @@ fn corpus() { stringify(k, None, false), stringify(v, None, false), stringify(p, None, false), - pathify(&Value::list(path.iter().cloned().map(Value::Str).collect()), None, None), + pathify( + &Value::list(path.iter().cloned().map(Value::Str).collect()), + None, + None + ), ))); v.clone() } @@ -573,7 +640,11 @@ fn corpus() { let out = Value::list(lines.borrow().clone()); out }; - for (label, b, a) in [("after", false, true), ("before", true, false), ("both", true, true)] { + for (label, b, a) in [ + ("after", false, true), + ("before", true, false), + ("both", true, true), + ] { let got = mk_log(&input, b, a); let exp = vget(&want, label); if got == exp { @@ -613,7 +684,12 @@ fn corpus() { } val.clone() }; - let _ = walk(vget(&vin, "src"), Some(&mut copy), None, as_i64_opt(&vget(&vin, "maxdepth"))); + let _ = walk( + vget(&vin, "src"), + Some(&mut copy), + None, + as_i64_opt(&vget(&vin, "maxdepth")), + ); let r = top.borrow().clone(); r }); @@ -634,14 +710,22 @@ fn corpus() { let i = path.len(); let mut v = val.clone(); if matches!(val, Value::List(_) | Value::Map(_)) { - v = if is_map(val) { Value::empty_map() } else { Value::empty_list() }; + v = if is_map(val) { + Value::empty_map() + } else { + Value::empty_list() + }; let mut cb = c.borrow_mut(); while cb.len() <= i { cb.push(Value::Noval); } cb[i] = v.clone(); } - let parent_copy = c.borrow().get(i.saturating_sub(1)).cloned().unwrap_or(Value::Noval); + let parent_copy = c + .borrow() + .get(i.saturating_sub(1)) + .cloned() + .unwrap_or(Value::Noval); set_prop(parent_copy, k, v); val.clone() }; @@ -652,7 +736,12 @@ fn corpus() { // -------- merge -------------------------------------------------- for name in ["cases", "array", "integrity"] { - run.run_set(&set!("merge", name), true, &format!("merge-{name}"), |vin| merge(&vin, None)); + run.run_set( + &set!("merge", name), + true, + &format!("merge-{name}"), + |vin| merge(&vin, None), + ); } run.run_set(&set!("merge", "depth"), true, "merge-depth", |vin| { merge(&vget(&vin, "val"), as_i64_opt(&vget(&vin, "depth"))) @@ -679,41 +768,60 @@ fn corpus() { run.run_set(&set!("getpath", "basic"), true, "getpath-basic", |vin| { get_path(&vget(&vin, "store"), &vget(&vin, "path"), None) }); - run.run_set(&set!("getpath", "relative"), true, "getpath-relative", |vin| { - let dpath = match vget(&vin, "dpath") { - Value::Str(dp) => Some(dp.split('.').map(|x| x.to_string()).collect()), - _ => None, - }; - let d = InjectDef { - dparent: Some(vget(&vin, "dparent")), - dpath, - ..Default::default() - }; - get_path(&vget(&vin, "store"), &vget(&vin, "path"), Some(&d)) - }); - run.run_set(&set!("getpath", "special"), true, "getpath-special", |vin| { - let d = inject_def_from_value(&vget(&vin, "inj")); - get_path(&vget(&vin, "store"), &vget(&vin, "path"), Some(&d)) - }); - run.run_set(&set!("getpath", "handler"), true, "getpath-handler", |vin| { - // getpath({ $TOP: store, $FOO: () => 'foo' }, path, { handler: (_inj, val) => val() }) - let store_inner = vget(&vin, "store"); - let mut topmap = IndexMap::new(); - topmap.insert("$TOP".to_string(), store_inner); - topmap.insert( - "$FOO".to_string(), - Value::func(|_inj: &Inj, _v: &Value, _r: &str, _st: &Value| Value::str("foo")), - ); - let store = Value::map(topmap); - let handler: NativeFn = Rc::new(|inj: &Inj, val: &Value, _r: &str, st: &Value| -> Value { - match val { - Value::Func(f) => f(inj, &Value::Noval, "", st), - other => other.clone(), - } - }); - let d = InjectDef { handler: Some(handler), ..Default::default() }; - get_path(&store, &vget(&vin, "path"), Some(&d)) - }); + run.run_set( + &set!("getpath", "relative"), + true, + "getpath-relative", + |vin| { + let dpath = match vget(&vin, "dpath") { + Value::Str(dp) => Some(dp.split('.').map(|x| x.to_string()).collect()), + _ => None, + }; + let d = InjectDef { + dparent: Some(vget(&vin, "dparent")), + dpath, + ..Default::default() + }; + get_path(&vget(&vin, "store"), &vget(&vin, "path"), Some(&d)) + }, + ); + run.run_set( + &set!("getpath", "special"), + true, + "getpath-special", + |vin| { + let d = inject_def_from_value(&vget(&vin, "inj")); + get_path(&vget(&vin, "store"), &vget(&vin, "path"), Some(&d)) + }, + ); + run.run_set( + &set!("getpath", "handler"), + true, + "getpath-handler", + |vin| { + // getpath({ $TOP: store, $FOO: () => 'foo' }, path, { handler: (_inj, val) => val() }) + let store_inner = vget(&vin, "store"); + let mut topmap = IndexMap::new(); + topmap.insert("$TOP".to_string(), store_inner); + topmap.insert( + "$FOO".to_string(), + Value::func(|_inj: &Inj, _v: &Value, _r: &str, _st: &Value| Value::str("foo")), + ); + let store = Value::map(topmap); + let handler: NativeFn = + Rc::new(|inj: &Inj, val: &Value, _r: &str, st: &Value| -> Value { + match val { + Value::Func(f) => f(inj, &Value::Noval, "", st), + other => other.clone(), + } + }); + let d = InjectDef { + handler: Some(handler), + ..Default::default() + }; + get_path(&store, &vget(&vin, "path"), Some(&d)) + }, + ); // -------- inject ------------------------------------------------- { @@ -722,7 +830,11 @@ fn corpus() { let bin = vget(&basic, "in"); let bout = fix_json(&vget(&basic, "out"), true); let got = fix_json( - &inject(clone(&vget(&bin, "val")), &clone(&vget(&bin, "store")), None), + &inject( + clone(&vget(&bin, "val")), + &clone(&vget(&bin, "store")), + None, + ), true, ); if got == bout { @@ -742,12 +854,19 @@ fn corpus() { if svv == NULLMARK { set_prop(parent.clone(), key, Value::Null); } else { - set_prop(parent.clone(), key, Value::str(svv.replace(NULLMARK, "null"))); + set_prop( + parent.clone(), + key, + Value::str(svv.replace(NULLMARK, "null")), + ); } } }, ); - let d = InjectDef { modify: Some(null_mod), ..Default::default() }; + let d = InjectDef { + modify: Some(null_mod), + ..Default::default() + }; inject(vget(&vin, "val"), &vget(&vin, "store"), Some(&d)) }); run.run_set(&set!("inject", "deep"), true, "inject-deep", |vin| { @@ -759,7 +878,11 @@ fn corpus() { let basic = vget_path(&s, &["transform", "basic"]); let bin = vget(&basic, "in"); let bout = fix_json(&vget(&basic, "out"), true); - let got = match transform(&clone(&vget(&bin, "data")), &clone(&vget(&bin, "spec")), None) { + let got = match transform( + &clone(&vget(&bin, "data")), + &clone(&vget(&bin, "spec")), + None, + ) { Ok(v) => fix_json(&v, true), Err(e) => Value::str(format!("ERR:{}", e)), }; @@ -782,46 +905,71 @@ fn corpus() { ("format", false), ("apply", true), ] { - run.run_set_fallible(&set!("transform", name), null_flag, &format!("transform-{name}"), |vin| { - transform(&vget(&vin, "data"), &vget(&vin, "spec"), None).map_err(|e| e.message) - }); - } - run.run_set(&set!("transform", "modify"), true, "transform-modify", |vin| { - let m: Modify = Rc::new( - |val: &Value, key: &Value, parent: &Value, _inj: &Inj, _store: &Value| { - if let Value::Str(svv) = val { - set_prop(parent.clone(), key, Value::str(format!("@{svv}"))); - } - }, + run.run_set_fallible( + &set!("transform", name), + null_flag, + &format!("transform-{name}"), + |vin| transform(&vget(&vin, "data"), &vget(&vin, "spec"), None).map_err(|e| e.message), ); - let d = InjectDef { modify: Some(m), ..Default::default() }; - match transform(&vget(&vin, "data"), &vget(&vin, "spec"), Some(&d)) { - Ok(v) => v, - Err(_) => Value::Noval, - } - }); + } + run.run_set( + &set!("transform", "modify"), + true, + "transform-modify", + |vin| { + let m: Modify = Rc::new( + |val: &Value, key: &Value, parent: &Value, _inj: &Inj, _store: &Value| { + if let Value::Str(svv) = val { + set_prop(parent.clone(), key, Value::str(format!("@{svv}"))); + } + }, + ); + let d = InjectDef { + modify: Some(m), + ..Default::default() + }; + match transform(&vget(&vin, "data"), &vget(&vin, "spec"), Some(&d)) { + Ok(v) => v, + Err(_) => Value::Noval, + } + }, + ); // -------- validate ----------------------------------------------- for name in ["basic", "invalid"] { - run.run_set_fallible(&set!("validate", name), false, &format!("validate-{name}"), |vin| { - validate(&vget(&vin, "data"), &vget(&vin, "spec"), None).map_err(|e| e.message) - }); + run.run_set_fallible( + &set!("validate", name), + false, + &format!("validate-{name}"), + |vin| validate(&vget(&vin, "data"), &vget(&vin, "spec"), None).map_err(|e| e.message), + ); } for name in ["child", "one", "exact"] { - run.run_set_fallible(&set!("validate", name), true, &format!("validate-{name}"), |vin| { - validate(&vget(&vin, "data"), &vget(&vin, "spec"), None).map_err(|e| e.message) - }); + run.run_set_fallible( + &set!("validate", name), + true, + &format!("validate-{name}"), + |vin| validate(&vget(&vin, "data"), &vget(&vin, "spec"), None).map_err(|e| e.message), + ); } - run.run_set_fallible(&set!("validate", "special"), true, "validate-special", |vin| { - let d = inject_def_from_value(&vget(&vin, "inj")); - validate(&vget(&vin, "data"), &vget(&vin, "spec"), Some(&d)).map_err(|e| e.message) - }); + run.run_set_fallible( + &set!("validate", "special"), + true, + "validate-special", + |vin| { + let d = inject_def_from_value(&vget(&vin, "inj")); + validate(&vget(&vin, "data"), &vget(&vin, "spec"), Some(&d)).map_err(|e| e.message) + }, + ); // -------- select ------------------------------------------------- for name in ["basic", "operators", "edge", "alts"] { - run.run_set(&set!("select", name), true, &format!("select-{name}"), |vin| { - select(&vget(&vin, "obj"), &vget(&vin, "query")) - }); + run.run_set( + &set!("select", name), + true, + &format!("select-{name}"), + |vin| select(&vget(&vin, "obj"), &vget(&vin, "query")), + ); } // -------- primary / SDK ------------------------------------------ @@ -840,10 +988,7 @@ fn corpus() { } else { voxgig_struct::value::js_string(&bar) }; - Value::map_of([( - "zed".to_string(), - Value::str(format!("ZED{foo_s}_{bar_s}")), - )]) + Value::map_of([("zed".to_string(), Value::str(format!("ZED{foo_s}_{bar_s}")))]) } { let check = vget_path(&spec, &["primary", "check"]); @@ -885,10 +1030,7 @@ fn corpus() { // -------- report ------------------------------------------------- if !run.failures.is_empty() { let n = run.failures.len(); - let mut msg = format!( - "\n{} corpus check(s) failed ({} passed):\n", - n, run.passed - ); + let mut msg = format!("\n{} corpus check(s) failed ({} passed):\n", n, run.passed); for f in run.failures.iter().take(60) { msg.push_str(" - "); msg.push_str(f); diff --git a/tools/check_parity.py b/tools/check_parity.py new file mode 100755 index 00000000..7278b420 --- /dev/null +++ b/tools/check_parity.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Cross-port API parity check. + +The canonical public API is the `export { ... }` block in +`ts/src/StructUtility.ts`. Every "complete" port is expected to define an +equivalent function for each canonical name, in that language's casing +convention (snake_case in Rust, PascalCase in Go/C#, camelCase in Java, +lower-smushed everywhere else). + +This is a smoke test: it confirms a function with the expected name exists in +each port's source (matching is done on a case/underscore-insensitive key, so +`get_path`, `GetPath`, `getPath` and `getpath` all count as the same name). +It does not check signatures — the shared JSONic test corpus is the real +behavioural contract. + +Exit status: 0 if every complete port defines every canonical function; +1 otherwise. Ports the README marks "in progress" are reported for +information only and never affect the exit status. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +# Ports the README marks "Complete" — these must be in full parity with the +# canonical TypeScript API. ("ts" itself is the canonical source, so it is +# trivially in parity and is not checked.) +COMPLETE_PORTS = ["js", "py", "go", "php", "rb", "lua", "rs", "zig", "cs"] +PARTIAL_PORTS = ["java", "cpp", "kt"] + +# Accepted, documented divergences (normalised name keys). Anything NOT listed +# here is treated as a parity gap and fails the check; this list should only +# shrink. +KNOWN_GAPS: dict[str, set[str]] = {} + +# Source files per port (implementation only — not tests). +SOURCES = { + "ts": ["ts/src/StructUtility.ts"], + "js": ["js/src/struct.js"], + "py": ["py/voxgig_struct/voxgig_struct.py", "py/voxgig_struct/__init__.py"], + "go": ["go/voxgigstruct.go"], + "php": ["php/src/Struct.php"], + "rb": ["rb/voxgig_struct.rb"], + "lua": ["lua/src/struct.lua"], + "rs": ["rs/src/lib.rs", "rs/src/major.rs", "rs/src/mini.rs"], + "java": ["java/src/Struct.java"], + "cpp": ["cpp/src/voxgig_struct.hpp", "cpp/src/value.hpp", "cpp/src/utility_decls.hpp"], + "cs": ["cs/Struct.cs"], + "zig": ["zig/src/struct.zig"], + "kt": ["kt/src/main/kotlin/voxgig/struct/Struct.kt"], +} + + +def norm(name: str) -> str: + """Case/underscore-insensitive comparison key.""" + return name.replace("_", "").lower() + + +def canonical_names() -> list[str]: + ts = (ROOT / "ts/src/StructUtility.ts").read_text() + m = re.search(r"^export \{\n(.*?)\n\}", ts, re.S | re.M) + if not m: + sys.exit("could not parse the canonical export list from ts/src/StructUtility.ts") + names = [n.strip().rstrip(",") for n in m.group(1).splitlines() if n.strip()] + out = [] + for n in names: + if not n or n == "StructUtility": + continue + # Drop value/flag constants — they're not functions. + if n in {"SKIP", "DELETE", "MODENAME"} or n.startswith(("T_", "M_")): + continue + out.append(n) + return out + + +_IDENT_BEFORE_PAREN = re.compile(r"\b([A-Za-z_][A-Za-z0-9_]*)\s*\(") +# Catches re-exports / aliases / object-literal keys / module-table entries: +# `jm = jo` (Python alias), `select = select_fn` (Lua module table), +# `getpath: getPath,` (JS module.exports), `var Jm = ...` (Go), `jm,` (export list) +_IDENT_DECL = re.compile(r"^\s*(?:(?:export|public|static|const|let|var|local)\s+)*([A-Za-z_][A-Za-z0-9_]*)\s*(?:[:=,]|$)", re.M) + + +def defined_keys(port: str) -> set[str]: + """Comparison keys for every identifier a port's source defines/re-exports. + + A function *definition* always writes the name immediately before `(`; a + re-export/alias/object key writes it before `=`/`:`/`,`/EOL. Together this + is a superset of the port's public names — exactly what we want for a + "is function X present?" check (false extras are harmless; a false omission + would be a real parity gap).""" + keys: set[str] = set() + for rel in SOURCES.get(port, []): + p = ROOT / rel + if not p.exists(): + continue + text = p.read_text() + for ident in _IDENT_BEFORE_PAREN.findall(text): + keys.add(norm(ident)) + for ident in _IDENT_DECL.findall(text): + keys.add(norm(ident)) + return keys + + +def main() -> int: + names = canonical_names() + canon_keys = {norm(n): n for n in names} + print(f"canonical API: {len(names)} functions") + + ok = True + for port in COMPLETE_PORTS: + have = defined_keys(port) + gaps = KNOWN_GAPS.get(port, set()) + miss = [orig for key, orig in canon_keys.items() if key not in have and key not in gaps] + accepted = sorted(orig for key, orig in canon_keys.items() if key not in have and key in gaps) + suffix = f" (known gaps: {', '.join(accepted)})" if accepted else "" + if miss: + ok = False + print(f" FAIL {port:5} — missing {len(miss)}: " + ", ".join(sorted(miss)) + suffix) + else: + print(f" ok {port}{suffix}") + + for port in PARTIAL_PORTS: + have = defined_keys(port) + miss = [orig for key, orig in canon_keys.items() if key not in have] + if miss: + print(f" note {port:5} (in progress) — missing {len(miss)}: " + ", ".join(sorted(miss))) + else: + print(f" ok {port:5} (in progress; full parity)") + + if not ok: + print("\nA complete port is missing one or more canonical functions.") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ts/.prettierignore b/ts/.prettierignore new file mode 100644 index 00000000..6bc8ea70 --- /dev/null +++ b/ts/.prettierignore @@ -0,0 +1,4 @@ +dist/ +dist-test/ +node_modules/ +coverage/ diff --git a/ts/.prettierrc.json b/ts/.prettierrc.json new file mode 100644 index 00000000..b0b2d026 --- /dev/null +++ b/ts/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all", + "arrowParens": "always" +} diff --git a/ts/Makefile b/ts/Makefile index 1b19b0d3..07c1c0cb 100644 --- a/ts/Makefile +++ b/ts/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test clean reset +.PHONY: inspect build test lint typecheck format-check clean reset audit inspect: @echo "Node.js version:" @@ -13,8 +13,23 @@ build: test: npm test +# Code quality: ESLint + Prettier format check. +lint: + npm run lint + npm run format:check + +typecheck: + npm run typecheck + +format-check: + npm run format:check + clean: rm -rf dist reset: clean rm -rf node_modules + +# Supply-chain: scan dependencies for known vulnerabilities. +audit: + npm audit --audit-level=high diff --git a/ts/dist-test/client.test.js.map b/ts/dist-test/client.test.js.map index 5cbc8682..27dae9a9 100644 --- a/ts/dist-test/client.test.js.map +++ b/ts/dist-test/client.test.js.map @@ -1 +1 @@ -{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../test/client.test.ts"],"names":[],"mappings":";AACA,gBAAgB;AAChB,8CAA8C;;AAE9C,yCAA0C;AAE1C,qCAEiB;AAEjB,qCAA8B;AAE9B,MAAM,cAAc,GAAG,4BAA4B,CAAA;AAEnD,IAAA,oBAAQ,EAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;IAE5B,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,cAAc,EAAE,MAAM,YAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAEjE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAA;IAEvD,IAAA,gBAAI,EAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;AAEJ,CAAC,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../test/client.test.ts"],"names":[],"mappings":";AAAA,gBAAgB;AAChB,8CAA8C;;AAE9C,yCAA0C;AAE1C,qCAAqC;AAErC,qCAA8B;AAE9B,MAAM,cAAc,GAAG,4BAA4B,CAAA;AAEnD,IAAA,oBAAQ,EAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;IAC5B,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,cAAc,EAAE,MAAM,YAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAEjE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAA;IAEvD,IAAA,gBAAI,EAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/ts/dist-test/direct.js b/ts/dist-test/direct.js index 0a1a1f16..3c0645b9 100644 --- a/ts/dist-test/direct.js +++ b/ts/dist-test/direct.js @@ -1,7 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const __1 = require(".."); -let out; let errs; // errs = [] // out = transform(undefined, undefined, { errs }) @@ -58,8 +57,8 @@ const extra = { } }, }; -let meta = { capture: {} }; -out = (0, __1.transform)({ a: { b: 1, c: 2 } }, { a: { b: { '`$CAPTURE`': 'x' }, c: { '`$CAPTURE`': 'x' } } }, { extra, errs, meta }); +const meta = { capture: {} }; +const out = (0, __1.transform)({ a: { b: 1, c: 2 } }, { a: { b: { '`$CAPTURE`': 'x' }, c: { '`$CAPTURE`': 'x' } } }, { extra, errs, meta }); console.dir(out, { depth: null }); console.dir(errs, { depth: null }); console.dir(meta, { depth: null }); diff --git a/ts/dist-test/direct.js.map b/ts/dist-test/direct.js.map index f24555ff..61d335a5 100644 --- a/ts/dist-test/direct.js.map +++ b/ts/dist-test/direct.js.map @@ -1 +1 @@ -{"version":3,"file":"direct.js","sourceRoot":"","sources":["../test/direct.ts"],"names":[],"mappings":";;AACA,0BAIW;AAGX,IAAI,GAAQ,CAAA;AACZ,IAAI,IAAS,CAAA;AAGb,YAAY;AACZ,kDAAkD;AAClD,0CAA0C;AAE1C,YAAY;AACZ,6CAA6C;AAC7C,0CAA0C;AAE1C,YAAY;AACZ,6CAA6C;AAC7C,0CAA0C;AAE1C,YAAY;AACZ,kDAAkD;AAClD,0CAA0C;AAI1C,YAAY;AACZ,iDAAiD;AACjD,yCAAyC;AAEzC,YAAY;AACZ,gDAAgD;AAChD,yCAAyC;AAEzC,YAAY;AACZ,gDAAgD;AAChD,yCAAyC;AAGzC,YAAY;AACZ,+EAA+E;AAC/E,yCAAyC;AAGzC,YAAY;AACZ,yEAAyE;AACzE,yCAAyC;AAEzC,YAAY;AACZ,iFAAiF;AACjF,yCAAyC;AAEzC,YAAY;AACZ,8BAA8B;AAC9B,oBAAoB;AACpB,YAAY;AACZ,uBAAuB;AACvB,uCAAuC;AACvC,yCAAyC;AAEzC,YAAY;AACZ,0DAA0D;AAC1D,yCAAyC;AAEzC,YAAY;AACZ,uCAAuC;AACvC,yCAAyC;AAGzC,MAAM,KAAK,GAAG;IACZ,QAAQ,EAAE,CAAC,GAAQ,EAAE,EAAE;QACrB,IAAI,YAAQ,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC;YAC1B,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,GAAG,CAAA;YAC1B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;YAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;YACzB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAA;AAED,IAAI,IAAI,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;AAC1B,GAAG,GAAG,IAAA,aAAS,EACb,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EACrB,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAE,EAC7D,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CACtB,CAAA;AACD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACjC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAClC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"direct.js","sourceRoot":"","sources":["../test/direct.ts"],"names":[],"mappings":";;AAAA,0BAAwC;AAExC,IAAI,IAAS,CAAA;AAEb,YAAY;AACZ,kDAAkD;AAClD,0CAA0C;AAE1C,YAAY;AACZ,6CAA6C;AAC7C,0CAA0C;AAE1C,YAAY;AACZ,6CAA6C;AAC7C,0CAA0C;AAE1C,YAAY;AACZ,kDAAkD;AAClD,0CAA0C;AAE1C,YAAY;AACZ,iDAAiD;AACjD,yCAAyC;AAEzC,YAAY;AACZ,gDAAgD;AAChD,yCAAyC;AAEzC,YAAY;AACZ,gDAAgD;AAChD,yCAAyC;AAEzC,YAAY;AACZ,+EAA+E;AAC/E,yCAAyC;AAEzC,YAAY;AACZ,yEAAyE;AACzE,yCAAyC;AAEzC,YAAY;AACZ,iFAAiF;AACjF,yCAAyC;AAEzC,YAAY;AACZ,8BAA8B;AAC9B,oBAAoB;AACpB,YAAY;AACZ,uBAAuB;AACvB,uCAAuC;AACvC,yCAAyC;AAEzC,YAAY;AACZ,0DAA0D;AAC1D,yCAAyC;AAEzC,YAAY;AACZ,uCAAuC;AACvC,yCAAyC;AAEzC,MAAM,KAAK,GAAG;IACZ,QAAQ,EAAE,CAAC,GAAQ,EAAE,EAAE;QACrB,IAAI,YAAQ,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC;YAC1B,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,GAAG,CAAA;YAC1B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;YAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;YACzB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAA;AAED,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;AAC5B,MAAM,GAAG,GAAG,IAAA,aAAS,EACnB,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EACrB,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAE,EAC7D,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CACtB,CAAA;AACD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACjC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAClC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA"} \ No newline at end of file diff --git a/ts/dist-test/runner.js b/ts/dist-test/runner.js index 978d93db..f5c13076 100644 --- a/ts/dist-test/runner.js +++ b/ts/dist-test/runner.js @@ -19,10 +19,10 @@ async function makeRunner(testfile, client) { store = store || {}; const utility = client.utility(); const structUtils = utility.struct; - let spec = resolveSpec(name, testfile); - let clients = await resolveClients(client, spec, store, structUtils); + const spec = resolveSpec(name, testfile); + const clients = await resolveClients(client, spec, store, structUtils); let subject = resolveSubject(name, utility); - let runsetflags = async (testspec, flags, testsubject) => { + const runsetflags = async (testspec, flags, testsubject) => { subject = testsubject || subject; flags = resolveFlags(flags); const testspecmap = fixJSON(testspec, flags); @@ -30,8 +30,8 @@ async function makeRunner(testfile, client) { for (let entry of testset) { try { entry = resolveEntry(entry, flags); - let testpack = resolveTestPack(name, entry, subject, client, clients); - let args = resolveArgs(entry, testpack, utility, structUtils); + const testpack = resolveTestPack(name, entry, subject, client, clients); + const args = resolveArgs(entry, testpack, utility, structUtils); let res = await testpack.subject(...args); res = fixJSON(res, flags); entry.res = res; @@ -45,7 +45,7 @@ async function makeRunner(testfile, client) { } } }; - let runset = async (testspec, testsubject) => runsetflags(testspec, {}, testsubject); + const runset = async (testspec, testsubject) => runsetflags(testspec, {}, testsubject); const runpack = { spec, runset, @@ -58,13 +58,13 @@ async function makeRunner(testfile, client) { } function resolveSpec(name, testfile) { const alltests = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, testfile), 'utf8')); - let spec = alltests.primary?.[name] || alltests[name] || alltests; + const spec = alltests.primary?.[name] || alltests[name] || alltests; return spec; } async function resolveClients(client, spec, store, structUtils) { const clients = {}; if (spec.DEF && spec.DEF.client) { - for (let cn in spec.DEF.client) { + for (const cn in spec.DEF.client) { const cdef = spec.DEF.client[cn]; const copts = cdef.test.options || {}; if ('object' === typeof store && structUtils?.inject) { @@ -93,8 +93,7 @@ function resolveEntry(entry, flags) { function checkResult(entry, args, res, structUtils) { let matched = false; if (entry.err) { - return (0, node_assert_1.fail)('Expected error did not occur: ' + entry.err + - '\n\nENTRY: ' + JSON.stringify(entry, null, 2)); + return (0, node_assert_1.fail)('Expected error did not occur: ' + entry.err + '\n\nENTRY: ' + JSON.stringify(entry, null, 2)); } if (entry.match) { const result = { in: entry.in, args, out: entry.res, ctx: entry.ctx }; @@ -122,8 +121,7 @@ function handleError(entry, err, structUtils) { } return; } - (0, node_assert_1.fail)('ERROR MATCH: [' + structUtils.stringify(entry_err) + - '] <=> [' + err.message + ']'); + (0, node_assert_1.fail)('ERROR MATCH: [' + structUtils.stringify(entry_err) + '] <=> [' + err.message + ']'); } // Unexpected error (test didn't specify an error expectation) else if (err instanceof node_assert_1.AssertionError) { @@ -175,7 +173,7 @@ function match(check, basex, structUtils) { const cbase = structUtils.clone(basex); structUtils.walk(check, (_key, val, _parent, path) => { if (!structUtils.isnode(val)) { - let baseval = structUtils.getpath(cbase, path); + const baseval = structUtils.getpath(cbase, path); if (baseval === val) { return val; } @@ -188,9 +186,13 @@ function match(check, basex, structUtils) { return val; } if (!matchval(val, baseval, structUtils)) { - (0, node_assert_1.fail)('MATCH: ' + path.join('.') + - ': [' + structUtils.stringify(val) + - '] <=> [' + structUtils.stringify(baseval) + ']'); + (0, node_assert_1.fail)('MATCH: ' + + path.join('.') + + ': [' + + structUtils.stringify(val) + + '] <=> [' + + structUtils.stringify(baseval) + + ']'); } } return val; @@ -200,8 +202,8 @@ function matchval(check, base, structUtils) { let pass = check === base; if (!pass) { if ('string' === typeof check) { - let basestr = structUtils.stringify(base); - let rem = check.match(/^\/(.+)\/$/); + const basestr = structUtils.stringify(base); + const rem = check.match(/^\/(.+)\/$/); if (rem) { pass = new RegExp(rem[1]).test(basestr); } @@ -235,7 +237,7 @@ function fixJSON(val, flags) { return JSON.parse(JSON.stringify(val, replacer)); } function nullModifier(val, key, parent) { - if ("__NULL__" === val) { + if ('__NULL__' === val) { parent[key] = null; } else if ('string' === typeof val) { diff --git a/ts/dist-test/runner.js.map b/ts/dist-test/runner.js.map index 67b9a499..328df9e3 100644 --- a/ts/dist-test/runner.js.map +++ b/ts/dist-test/runner.js.map @@ -1 +1 @@ -{"version":3,"file":"runner.js","sourceRoot":"","sources":["../test/runner.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"runner.js","sourceRoot":"","sources":["../test/runner.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/ts/dist-test/sdk.js b/ts/dist-test/sdk.js index b22b5745..69f7911b 100644 --- a/ts/dist-test/sdk.js +++ b/ts/dist-test/sdk.js @@ -28,9 +28,9 @@ class SDK { zed: 'ZED' + (null == __classPrivateFieldGet(this, _SDK_opts, "f") ? '' : null == __classPrivateFieldGet(this, _SDK_opts, "f").foo ? '' : __classPrivateFieldGet(this, _SDK_opts, "f").foo) + '_' + - (null == ctx.meta?.bar ? '0' : ctx.meta.bar) + (null == ctx.meta?.bar ? '0' : ctx.meta.bar), }; - } + }, }, "f"); } static async test(opts) { diff --git a/ts/dist-test/sdk.js.map b/ts/dist-test/sdk.js.map index 36aa9518..e96f5a5e 100644 --- a/ts/dist-test/sdk.js.map +++ b/ts/dist-test/sdk.js.map @@ -1 +1 @@ -{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../test/sdk.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,yDAAqD;AAErD,MAAM,GAAG;IAKP,YAAY,IAAU;QAHtB,oBAAa,EAAE,EAAA;QACf,uBAAgB,EAAE,EAAA;QAGhB,uBAAA,IAAI,aAAS,IAAI,IAAI,EAAE,MAAA,CAAA;QACvB,uBAAA,IAAI,gBAAY;YACd,MAAM,EAAE,IAAI,6BAAa,EAAE;YAC3B,UAAU,EAAE,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM;YACnC,WAAW,EAAE,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM;YACpC,KAAK,EAAE,CAAC,GAAQ,EAAE,EAAE;gBAClB,OAAO;oBACL,GAAG,EAAE,KAAK;wBACR,CAAC,IAAI,IAAI,uBAAA,IAAI,iBAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,uBAAA,IAAI,iBAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,uBAAA,IAAI,iBAAM,CAAC,GAAG,CAAC;wBACxE,GAAG;wBACH,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;iBAC/C,CAAA;YACH,CAAC;SACF,MAAA,CAAA;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAU;QAC1B,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAU;QACrB,OAAO,IAAI,GAAG,CAAC,IAAI,IAAI,uBAAA,IAAI,iBAAM,CAAC,CAAA;IACpC,CAAC;IAED,OAAO;QACL,OAAO,uBAAA,IAAI,oBAAS,CAAA;IACtB,CAAC;CACF;AAGC,kBAAG"} \ No newline at end of file +{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../test/sdk.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yDAAqD;AAErD,MAAM,GAAG;IAIP,YAAY,IAAU;QAHtB,oBAAa,EAAE,EAAA;QACf,uBAAgB,EAAE,EAAA;QAGhB,uBAAA,IAAI,aAAS,IAAI,IAAI,EAAE,MAAA,CAAA;QACvB,uBAAA,IAAI,gBAAY;YACd,MAAM,EAAE,IAAI,6BAAa,EAAE;YAC3B,UAAU,EAAE,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM;YACnC,WAAW,EAAE,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM;YACpC,KAAK,EAAE,CAAC,GAAQ,EAAE,EAAE;gBAClB,OAAO;oBACL,GAAG,EACD,KAAK;wBACL,CAAC,IAAI,IAAI,uBAAA,IAAI,iBAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,uBAAA,IAAI,iBAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,uBAAA,IAAI,iBAAM,CAAC,GAAG,CAAC;wBACxE,GAAG;wBACH,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;iBAC/C,CAAA;YACH,CAAC;SACF,MAAA,CAAA;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAU;QAC1B,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAU;QACrB,OAAO,IAAI,GAAG,CAAC,IAAI,IAAI,uBAAA,IAAI,iBAAM,CAAC,CAAA;IACpC,CAAC;IAED,OAAO;QACL,OAAO,uBAAA,IAAI,oBAAS,CAAA;IACtB,CAAC;CACF;AAEQ,kBAAG"} \ No newline at end of file diff --git a/ts/dist-test/utility/StructUtility.test.js b/ts/dist-test/utility/StructUtility.test.js index 6c9069ce..16942524 100644 --- a/ts/dist-test/utility/StructUtility.test.js +++ b/ts/dist-test/utility/StructUtility.test.js @@ -89,7 +89,9 @@ const { equal, deepEqual } = node_assert_1.default; (0, node_test_1.test)('minor-isfunc', async () => { const { isfunc } = struct; await runset(spec.minor.isfunc, isfunc); - function f0() { return null; } + function f0() { + return null; + } equal(isfunc(f0), true); equal(isfunc(() => null), true); }); @@ -101,7 +103,7 @@ const { equal, deepEqual } = node_assert_1.default; const f0 = () => null; deepEqual({ a: f0 }, clone({ a: f0 })); const x = { y: 1 }; - let xc = clone(x); + const xc = clone(x); deepEqual(x, xc); (0, node_assert_1.default)(x !== xc); class A { @@ -110,7 +112,7 @@ const { equal, deepEqual } = node_assert_1.default; } } const a = new A(); - let ac = clone(a); + const ac = clone(a); deepEqual(a, ac); (0, node_assert_1.default)(a === ac); equal(a.constructor.name, ac.constructor.name); @@ -132,7 +134,7 @@ const { equal, deepEqual } = node_assert_1.default; await runset(spec.minor.escurl, struct.escurl); }); (0, node_test_1.test)('minor-stringify', async () => { - await runset(spec.minor.stringify, (vin) => struct.stringify((runner_1.NULLMARK === vin.val ? "null" : vin.val), vin.max)); + await runset(spec.minor.stringify, (vin) => struct.stringify(runner_1.NULLMARK === vin.val ? 'null' : vin.val, vin.max)); }); (0, node_test_1.test)('minor-edge-stringify', async () => { const { stringify } = struct; @@ -151,9 +153,9 @@ const { equal, deepEqual } = node_assert_1.default; }); (0, node_test_1.test)('minor-pathify', async () => { await runsetflags(spec.minor.pathify, { null: true }, (vin) => { - let path = runner_1.NULLMARK == vin.path ? undefined : vin.path; + const path = runner_1.NULLMARK == vin.path ? undefined : vin.path; let pathstr = struct.pathify(path, vin.from).replace('__NULL__.', ''); - pathstr = runner_1.NULLMARK === vin.path ? pathstr.replace('>', ':null>') : pathstr; + pathstr = runner_1.NULLMARK === vin.path ? pathstr.replace(/>/g, ':null>') : pathstr; return pathstr; }); }); @@ -164,7 +166,11 @@ const { equal, deepEqual } = node_assert_1.default; const { items } = struct; const a0 = [11, 22, 33]; a0.x = 1; - deepEqual(items(a0), [['0', 11], ['1', 22], ['2', 33]]); + deepEqual(items(a0), [ + ['0', 11], + ['1', 22], + ['2', 33], + ]); }); (0, node_test_1.test)('minor-getelem', async () => { const { getelem } = struct; @@ -180,10 +186,10 @@ const { equal, deepEqual } = node_assert_1.default; }); (0, node_test_1.test)('minor-edge-getprop', async () => { const { getprop } = struct; - let strarr = ['a', 'b', 'c', 'd', 'e']; + const strarr = ['a', 'b', 'c', 'd', 'e']; deepEqual(getprop(strarr, 2), 'c'); deepEqual(getprop(strarr, '2'), 'c'); - let intarr = [2, 3, 5, 7, 11]; + const intarr = [2, 3, 5, 7, 11]; deepEqual(getprop(intarr, 2), 5); deepEqual(getprop(intarr, '2'), 5); }); @@ -192,12 +198,12 @@ const { equal, deepEqual } = node_assert_1.default; }); (0, node_test_1.test)('minor-edge-setprop', async () => { const { setprop } = struct; - let strarr0 = ['a', 'b', 'c', 'd', 'e']; - let strarr1 = ['a', 'b', 'c', 'd', 'e']; + const strarr0 = ['a', 'b', 'c', 'd', 'e']; + const strarr1 = ['a', 'b', 'c', 'd', 'e']; deepEqual(setprop(strarr0, 2, 'C'), ['a', 'b', 'C', 'd', 'e']); deepEqual(setprop(strarr1, '2', 'CC'), ['a', 'b', 'CC', 'd', 'e']); - let intarr0 = [2, 3, 5, 7, 11]; - let intarr1 = [2, 3, 5, 7, 11]; + const intarr0 = [2, 3, 5, 7, 11]; + const intarr1 = [2, 3, 5, 7, 11]; deepEqual(setprop(intarr0, 2, 55), [2, 3, 55, 7, 11]); deepEqual(setprop(intarr1, '2', 555), [2, 3, 555, 7, 11]); }); @@ -206,12 +212,12 @@ const { equal, deepEqual } = node_assert_1.default; }); (0, node_test_1.test)('minor-edge-delprop', async () => { const { delprop } = struct; - let strarr0 = ['a', 'b', 'c', 'd', 'e']; - let strarr1 = ['a', 'b', 'c', 'd', 'e']; + const strarr0 = ['a', 'b', 'c', 'd', 'e']; + const strarr1 = ['a', 'b', 'c', 'd', 'e']; deepEqual(delprop(strarr0, 2), ['a', 'b', 'd', 'e']); deepEqual(delprop(strarr1, '2'), ['a', 'b', 'd', 'e']); - let intarr0 = [2, 3, 5, 7, 11]; - let intarr1 = [2, 3, 5, 7, 11]; + const intarr0 = [2, 3, 5, 7, 11]; + const intarr1 = [2, 3, 5, 7, 11]; deepEqual(delprop(intarr0, 2), [2, 3, 7, 11]); deepEqual(delprop(intarr1, '2'), [2, 3, 7, 11]); }); @@ -275,10 +281,14 @@ const { equal, deepEqual } = node_assert_1.default; const test = clone(spec.walk.log); let log = []; function walklog(key, val, parent, path) { - log.push('k=' + stringify(key) + - ', v=' + stringify(val) + - ', p=' + stringify(parent) + - ', t=' + pathify(path)); + log.push('k=' + + stringify(key) + + ', v=' + + stringify(val) + + ', p=' + + stringify(parent) + + ', t=' + + pathify(path)); return val; } walk(test.in, undefined, walklog); @@ -302,7 +312,7 @@ const { equal, deepEqual } = node_assert_1.default; let cur = undefined; function copy(key, val, _parent, _path) { if (undefined === key || struct.isnode(val)) { - let child = struct.islist(val) ? [] : {}; + const child = struct.islist(val) ? [] : {}; if (undefined === key) { top = cur = child; } @@ -329,7 +339,7 @@ const { equal, deepEqual } = node_assert_1.default; return val; } let v = val; - let i = size(path); + const i = size(path); if (isnode(v)) { v = cur[i] = ismap(v) ? {} : []; } @@ -414,7 +424,7 @@ const { equal, deepEqual } = node_assert_1.default; }, vin.path, { handler: (_inj, val, _cur, _ref) => { return val(); - } + }, })); }); // inject tests @@ -468,21 +478,22 @@ const { equal, deepEqual } = node_assert_1.default; if (null != key && null != parent && 'string' === typeof val) { val = parent[key] = '@' + val; } - } + }, })); }); (0, node_test_1.test)('transform-extra', async () => { deepEqual(struct.transform({ a: 1 }, { x: '`a`', b: '`$COPY`', c: '`$UPPER`' }, { extra: { - b: 2, $UPPER: (state) => { + b: 2, + $UPPER: (state) => { const { path } = state; return ('' + struct.getprop(path, path.length - 1)).toUpperCase(); - } - } + }, + }, }), { x: 1, b: 2, - c: 'C' + c: 'C', }); }); (0, node_test_1.test)('transform-funcval', async () => { @@ -538,8 +549,8 @@ const { equal, deepEqual } = node_assert_1.default; $INTEGER: (inj) => { const { key } = inj; // let out = getprop(current, key) - let out = struct.getprop(inj.dparent, key); - let t = typeof out; + const out = struct.getprop(inj.dparent, key); + const t = typeof out; if ('number' !== t && !Number.isInteger(out)) { inj.errs.push('Not an integer at ' + inj.path.slice(1).join('.') + ': ' + out); return; @@ -606,7 +617,7 @@ const { equal, deepEqual } = node_assert_1.default; "k": null } ]`); - equal(jsonify(jm(true, 1, false, 2, null, 3, ['a'], 4, { 'b': 0 }, 5)), `{ + equal(jsonify(jm(true, 1, false, 2, null, 3, ['a'], 4, { b: 0 }, 5)), `{ "true": 1, "false": 2, "null": 3, diff --git a/ts/dist-test/utility/StructUtility.test.js.map b/ts/dist-test/utility/StructUtility.test.js.map index ed036b0d..480cf4a4 100644 --- a/ts/dist-test/utility/StructUtility.test.js.map +++ b/ts/dist-test/utility/StructUtility.test.js.map @@ -1 +1 @@ -{"version":3,"file":"StructUtility.test.js","sourceRoot":"","sources":["../../test/utility/StructUtility.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"StructUtility.test.js","sourceRoot":"","sources":["../../test/utility/StructUtility.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/ts/dist-test/utility/index.js.map b/ts/dist-test/utility/index.js.map index 1853799c..35d4defa 100644 --- a/ts/dist-test/utility/index.js.map +++ b/ts/dist-test/utility/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../test/utility/index.ts"],"names":[],"mappings":";;;AACA,gCAA4B;AAM1B,oFANO,SAAG,OAMP;AAJL,MAAM,cAAc,GAAG,4BAA4B,CAAA;AAKjD,wCAAc"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../test/utility/index.ts"],"names":[],"mappings":";;;AAAA,gCAA4B;AAInB,oFAJA,SAAG,OAIA;AAFZ,MAAM,cAAc,GAAG,4BAA4B,CAAA;AAErC,wCAAc"} \ No newline at end of file diff --git a/ts/dist-test/walk-bench.test.js.map b/ts/dist-test/walk-bench.test.js.map index 7f4c756b..de293099 100644 --- a/ts/dist-test/walk-bench.test.js.map +++ b/ts/dist-test/walk-bench.test.js.map @@ -1 +1 @@ -{"version":3,"file":"walk-bench.test.js","sourceRoot":"","sources":["../test/walk-bench.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"walk-bench.test.js","sourceRoot":"","sources":["../test/walk-bench.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/ts/dist/StructUtility.d.ts b/ts/dist/StructUtility.d.ts index 5e597a16..4e27b3dd 100644 --- a/ts/dist/StructUtility.d.ts +++ b/ts/dist/StructUtility.d.ts @@ -48,9 +48,9 @@ declare function ismap(val: any): val is { declare function islist(val: any): val is any[]; declare function iskey(key: any): key is PropKey; declare function isempty(val: any): boolean; -declare function isfunc(val: any): val is Function; +declare function isfunc(val: any): val is (...args: any[]) => any; declare function size(val: any): number; -declare function slice(val: V, start?: number, end?: number, mutate?: boolean): V; +declare function slice(val: V, start?: number, end?: number, mutate?: boolean): V; declare function pad(str: any, padding?: number, padchar?: string): string; declare function typify(value: any): number; declare function getelem(val: any, key: any, alt?: any): any; diff --git a/ts/dist/StructUtility.js b/ts/dist/StructUtility.js index dd9c0f83..2d4ae3fc 100644 --- a/ts/dist/StructUtility.js +++ b/ts/dist/StructUtility.js @@ -189,12 +189,20 @@ const TYPENAME = [ S_function, S_symbol, S_null, - '', '', '', - '', '', '', '', + '', + '', + '', + '', + '', + '', + '', S_list, S_map, S_instance, - '', '', '', '', + '', + '', + '', + '', S_scalar, S_node, ]; @@ -208,9 +216,6 @@ exports.DELETE = DELETE; // Regular expression constants const R_INTEGER_KEY = /^[-0-9]+$/; // Match integer keys (including <0). const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g; // Chars that need escaping in regexp. -const R_TRAILING_SLASH = /\/+$/; // Trailing slashes in URLs. -const R_LEADING_TRAILING_SLASH = /([^\/])\/+/; // Multiple slashes in URL middle. -const R_LEADING_SLASH = /^\/+/; // Leading slashes in URLs. const R_QUOTES = /"/g; // Double quotes for removal. const R_DOT = /\./g; // Dots in path strings. const R_CLONE_REF = /^`\$REF:([0-9]+)`$/; // Copy reference in cloning. @@ -255,9 +260,10 @@ function iskey(key) { } // Check for an "empty" value - undefined, empty string, array, object. function isempty(val) { - return null == val || S_MT === val || + return (null == val || + S_MT === val || (Array.isArray(val) && 0 === val.length) || - (S_object === typeof val && 0 === Object.keys(val).length); + (S_object === typeof val && 0 === Object.keys(val).length)); } // Value is a function. function isfunc(val) { @@ -334,7 +340,7 @@ function slice(val, start, end, mutate) { for (let i = 0, j = start; j < end; i++, j++) { val[i] = val[j]; } - val.length = (end - start); + val.length = end - start; } else { val = val.slice(start, end); @@ -359,7 +365,7 @@ function slice(val, start, end, mutate) { function pad(str, padding, padchar) { str = S_string === typeof str ? str : stringify(str); padding = null == padding ? 44 : padding; - padchar = null == padchar ? S_SP : ((padchar + S_SP)[0]); + padchar = null == padchar ? S_SP : (padchar + S_SP)[0]; return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar); } // Determine the type of a value as a bit code. @@ -400,7 +406,7 @@ function typify(value) { } else if (S_object === typestr) { if (value.constructor instanceof Function) { - let cname = value.constructor.name; + const cname = value.constructor.name; if ('Object' !== cname && 'Array' !== cname) { return T_node | T_instance; } @@ -418,7 +424,7 @@ function getelem(val, key, alt) { return alt; } if (islist(val)) { - let nkey = parseInt(key); + const nkey = parseInt(key); if (Number.isInteger(nkey) && ('' + key).match(R_INTEGER_KEY)) { if (nkey < 0) { key = val.length + nkey; @@ -470,8 +476,11 @@ function strkey(key = NONE) { // Sorted keys of a map, or indexes (as strings) of a list. // Root utility - only uses language facilities. function keysof(val) { - return !isnode(val) ? [] : - ismap(val) ? Object.keys(val).sort() : val.map((_n, i) => S_MT + i); + return !isnode(val) + ? [] + : ismap(val) + ? Object.keys(val).sort() + : val.map((_n, i) => S_MT + i); } // Value of property with name key in node val is defined. // Root utility - only uses language facilities. @@ -498,9 +507,9 @@ function flatten(list, depth) { } // Filter item values using check function. function filter(val, check) { - let all = items(val); - let numall = size(all); - let out = []; + const all = items(val); + const numall = size(all); + const out = []; for (let i = 0; i < numall; i++) { if (check(all[i])) { out.push(all[i][1]); @@ -521,7 +530,7 @@ function escurl(s) { // Replace a search string (all), or a regexp, in a source string. function replace(s, from, to) { let rs = s; - let ts = typify(s); + const ts = typify(s); if (0 === (T_string & ts)) { rs = stringify(s); } @@ -540,8 +549,8 @@ function join(arr, sep, url) { const sepre = 1 === size(sepdef) ? escre(sepdef) : NONE; const out = filter(items( // filter(arr, (n) => null != n[1] && S_MT !== n[1]), - filter(arr, (n) => (0 < (T_string & typify(n[1]))) && S_MT !== n[1]), (n) => { - let i = +n[0]; + filter(arr, (n) => 0 < (T_string & typify(n[1])) && S_MT !== n[1]), (n) => { + const i = +n[0]; let s = n[1]; if (NONE !== sepre && S_MT !== sepre) { if (url && 0 === i) { @@ -557,8 +566,7 @@ function join(arr, sep, url) { s = replace(s, RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'), '$1' + sepdef + '$2'); } return s; - }), (n) => S_MT !== n[1]) - .join(sepdef); + }), (n) => S_MT !== n[1]).join(sepdef); return out; } // Output JSON in a "standard" format, with 2 space indents, each property on a new line, @@ -577,11 +585,12 @@ function jsonify(val, flags) { if (0 < offset) { // Left offset entire indented JSON so that it aligns with surrounding code // indented by offset. Assume first brace is on line with asignment, so not offset. - str = '{\n' + - join(items(slice(str.split('\n'), 1), (n) => pad(n[1], 0 - offset - size(n[1]))), '\n'); + str = + '{\n' + + join(items(slice(str.split('\n'), 1), (n) => pad(n[1], 0 - offset - size(n[1]))), '\n'); } } - catch (e) { + catch { str = '__JSONIFY_FAILED__'; } } @@ -600,9 +609,7 @@ function stringify(val, maxlen, pretty) { else { try { valstr = JSON.stringify(val, function (_key, val) { - if (val !== null && - typeof val === "object" && - !Array.isArray(val)) { + if (val !== null && typeof val === 'object' && !Array.isArray(val)) { const sortedObj = {}; items(val, (n) => { sortedObj[n[0]] = val[n[0]]; @@ -613,17 +620,19 @@ function stringify(val, maxlen, pretty) { }); valstr = valstr.replace(R_QUOTES, S_MT); } - catch (err) { + catch { valstr = '__STRINGIFY_FAILED__'; } } if (null != maxlen && -1 < maxlen) { - let js = valstr.substring(0, maxlen); - valstr = maxlen < valstr.length ? (js.substring(0, maxlen - 3) + '...') : valstr; + const js = valstr.substring(0, maxlen); + valstr = maxlen < valstr.length ? js.substring(0, maxlen - 3) + '...' : valstr; } if (pretty) { // Indicate deeper JSON levels with different terminal colors (simplistic wrt strings). - let c = items([81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69], (n) => '\x1b[38;5;' + n[1] + 'm'), r = '\x1b[0m', d = 0, o = c[0], t = o; + const c = items([81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69], (n) => '\x1b[38;5;' + n[1] + 'm'); + const r = '\x1b[0m'; + let d = 0, o = c[0], t = o; for (const ch of valstr) { if (ch === '{' || ch === '[') { d++; @@ -646,10 +655,13 @@ function stringify(val, maxlen, pretty) { // Build a human friendly path string. function pathify(val, startin, endin) { let pathstr = NONE; - let path = islist(val) ? val : - S_string == typeof val ? [val] : - S_number == typeof val ? [val] : - NONE; + let path = islist(val) + ? val + : S_string == typeof val + ? [val] + : S_number == typeof val + ? [val] + : NONE; const start = null == startin ? 0 : -1 < startin ? startin : 0; const end = null == endin ? 0 : -1 < endin ? endin : 0; if (NONE != path && 0 <= start) { @@ -659,9 +671,8 @@ function pathify(val, startin, endin) { } else { pathstr = join(items(filter(path, (n) => iskey(n[1])), (n) => { - let p = n[1]; - return S_number === typeof p ? S_MT + Math.floor(p) : - p.replace(R_DOT, S_MT); + const p = n[1]; + return S_number === typeof p ? S_MT + Math.floor(p) : p.replace(R_DOT, S_MT); }), S_DT); } } @@ -675,10 +686,8 @@ function pathify(val, startin, endin) { function clone(val) { const refs = []; const reftype = T_function | T_instance; - const replacer = (_k, v) => 0 < (reftype & typify(v)) ? - (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v; - const reviver = (_k, v, m) => S_string === typeof v ? - (m = v.match(R_CLONE_REF), m ? refs[m[1]] : v) : v; + const replacer = (_k, v) => 0 < (reftype & typify(v)) ? (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v; + const reviver = (_k, v, m) => S_string === typeof v ? ((m = v.match(R_CLONE_REF)), m ? refs[m[1]] : v) : v; const out = NONE === val ? NONE : JSON.parse(JSON.stringify(val, replacer), reviver); return out; } @@ -702,7 +711,7 @@ function jt(...v) { } return a; } -// Safely delete a property from an object or array element. +// Safely delete a property from an object or array element. // Undefined arguments and invalid keys are ignored. // Returns the (possibly modified) parent. // For objects, the property is deleted using the delete operator. @@ -809,7 +818,7 @@ key, parent, path, pool) { for (let i = 0; i < depth; i++) { childPath[i] = path[i]; } - for (let [ckey, child] of items(out)) { + for (const [ckey, child] of items(out)) { childPath[depth] = S_MT + ckey; setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath, pool)); } @@ -840,16 +849,16 @@ function merge(val, maxdepth) { // Merge a list of values. out = getprop(list, 0, {}); for (let oI = 1; oI < lenlist; oI++) { - let obj = list[oI]; + const obj = list[oI]; if (!isnode(obj)) { // Nodes win. out = obj; } else { // Current value at path end in overriding node. - let cur = [out]; + const cur = [out]; // Current value at path end in destination node. - let dst = [out]; + const dst = [out]; function before(key, val, _parent, path) { const pI = size(path); if (md <= pI) { @@ -908,9 +917,13 @@ function merge(val, maxdepth) { // String paths create only maps. Use a string list to create list parts. function setpath(store, path, val, injdef) { const pathType = typify(path); - const parts = 0 < (T_list & pathType) ? path : - 0 < (T_string & pathType) ? path.split(S_DT) : - 0 < (T_number & pathType) ? [path] : NONE; + const parts = 0 < (T_list & pathType) + ? path + : 0 < (T_string & pathType) + ? path.split(S_DT) + : 0 < (T_number & pathType) + ? [path] + : NONE; if (NONE === parts) { return NONE; } @@ -936,9 +949,13 @@ function setpath(store, path, val, injdef) { } function getpath(store, path, injdef) { // Operate on a string array. - const parts = islist(path) ? path : - 'string' === typeof path ? path.split(S_DT) : - 'number' === typeof path ? [strkey(path)] : NONE; + const parts = islist(path) + ? path + : 'string' === typeof path + ? path.split(S_DT) + : 'number' === typeof path + ? [strkey(path)] + : NONE; if (NONE === parts) { return NONE; } @@ -1063,8 +1080,8 @@ function inject(val, store, injdef) { nodekeys = keysof(val); if (ismap(val)) { nodekeys = flatten([ - filter(nodekeys, (n => !n[1].includes(S_DS))), - filter(nodekeys, (n => n[1].includes(S_DS))), + filter(nodekeys, (n) => !n[1].includes(S_DS)), + filter(nodekeys, (n) => n[1].includes(S_DS)), ]); } else { @@ -1112,9 +1129,9 @@ function inject(val, store, injdef) { } // Custom modification. if (inj.modify && SKIP !== val) { - let mkey = inj.key; - let mparent = inj.parent; - let mval = getprop(mparent, mkey); + const mkey = inj.key; + const mparent = inj.parent; + const mval = getprop(mparent, mkey); inj.modify(mval, mkey, mparent, inj, store); } // console.log('INJ-VAL', val) @@ -1135,7 +1152,7 @@ const transform_COPY = (inj, _val) => { if (!checkPlacement(M_VAL, ijname, T_any, inj)) { return NONE; } - let out = getprop(inj.dparent, inj.key); + const out = getprop(inj.dparent, inj.key); inj.setval(out); return out; }; @@ -1164,9 +1181,9 @@ const transform_ANNO = (inj) => { delprop(parent, S_BANNO); return NONE; }; -// Merge a list of objects into the current object. +// Merge a list of objects into the current object. // Must be a key in an object. The value is merged over the current object. -// If the value is an array, the elements are first merged using `merge`. +// If the value is an array, the elements are first merged using `merge`. // If the value is the empty string, merge the top level store. // Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] } const transform_MERGE = (inj) => { @@ -1220,11 +1237,11 @@ const transform_EACH = (inj, _val, _ref, store) => { tval = items(src, () => clone(child)); } else if (0 < (T_map & srctype)) { - tval = items(src, (n => merge([ + tval = items(src, (n) => merge([ clone(child), // Make a note of the key for $KEY transforms. - { [S_BANNO]: { KEY: n[0] } } - ], 1))); + { [S_BANNO]: { KEY: n[0] } }, + ], 1)); } let rval = []; if (0 < size(tval)) { @@ -1258,7 +1275,7 @@ const transform_EACH = (inj, _val, _ref, store) => { // Convert a node to a map. // Format: { '`$PACK`':['source-path', child-template]} const transform_PACK = (inj, _val, _ref, store) => { - const { mode, key, path, parent, nodes } = inj; + const { key, path, parent, nodes } = inj; const ijname = 'EACH'; if (!checkPlacement(M_KEYPRE, ijname, T_map, inj)) { return NONE; @@ -1297,7 +1314,7 @@ const transform_PACK = (inj, _val, _ref, store) => { const childspec = delprop(origchildspec, S_BKEY); const child = getprop(childspec, S_BVAL, childspec); // Build parallel target object. - let tval = {}; + const tval = {}; items(src, (item) => { const srckey = item[0]; const srcnode = item[1]; @@ -1323,12 +1340,13 @@ const transform_PACK = (inj, _val, _ref, store) => { let rval = {}; if (!isempty(tval)) { // Build parallel source object. - let tsrc = {}; + const tsrc = {}; src.reduce((a, n, i) => { - let kn = null == keypath ? i : - keypath.startsWith('`') ? - inject(keypath, merge([{}, store, { $TOP: n }], 1)) : - getpath(n, keypath, inj); + const kn = null == keypath + ? i + : keypath.startsWith('`') + ? inject(keypath, merge([{}, store, { $TOP: n }], 1)) + : getpath(n, keypath, inj); setprop(a, kn, n); return a; }, tsrc); @@ -1386,11 +1404,11 @@ const transform_REF = (inj, val, _ref, store) => { return v; }); } - let tref = clone(ref); + const tref = clone(ref); const cpath = slice(inj.path, -3); const tpath = slice(inj.path, -1); - let tcur = getpath(store, cpath); - let tval = getpath(store, tpath); + const tcur = getpath(store, cpath); + const tval = getpath(store, tpath); let rval = NONE; if (!hasSubRef || NONE !== tval) { const tinj = inj.child(0, [getelem(tpath, -1)]); @@ -1428,21 +1446,21 @@ const transform_FORMAT = (inj, _val, _ref, store) => { const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)); const cinj = injectChild(child, store, inj); const resolved = cinj.val; - let formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name); + const formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name); if (NONE === formatter) { inj.errs.push('$FORMAT: unknown format: ' + name + '.'); return NONE; } - let out = walk(resolved, formatter); + const out = walk(resolved, formatter); setprop(target, tkey, out); // _updateAncestors(inj, target, tkey, out) return out; }; const FORMATTER = { identity: (_k, v) => v, - upper: (_k, v) => isnode(v) ? v : ('' + v).toUpperCase(), - lower: (_k, v) => isnode(v) ? v : ('' + v).toLowerCase(), - string: (_k, v) => isnode(v) ? v : ('' + v), + upper: (_k, v) => (isnode(v) ? v : ('' + v).toUpperCase()), + lower: (_k, v) => (isnode(v) ? v : ('' + v).toLowerCase()), + string: (_k, v) => (isnode(v) ? v : '' + v), number: (_k, v) => { if (isnode(v)) { return v; @@ -1467,7 +1485,9 @@ const FORMATTER = { return n | 0; } }, - concat: (k, v) => null == k && islist(v) ? join(items(v, (n => isnode(n[1]) ? S_MT : (S_MT + n[1]))), S_MT) : v + concat: (k, v) => null == k && islist(v) + ? join(items(v, (n) => (isnode(n[1]) ? S_MT : S_MT + n[1])), S_MT) + : v, }; const transform_APPLY = (inj, _val, _ref, store) => { const ijname = 'APPLY'; @@ -1501,12 +1521,11 @@ injdef) { const collect = null != injdef?.errs; const errs = injdef?.errs || []; const extraTransforms = {}; - const extraData = null == extra ? NONE : items(extra) - .reduce((a, n) => (n[0].startsWith(S_DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {}); - const dataClone = merge([ - isempty(extraData) ? NONE : clone(extraData), - clone(data), - ]); + const extraData = null == extra + ? NONE + : items(extra).reduce((a, n) => (n[0].startsWith(S_DS) ? (extraTransforms[n[0]] = n[1]) : (a[n[0]] = n[1]), + a), {}); + const dataClone = merge([isempty(extraData) ? NONE : clone(extraData), clone(data)]); // Define a top level store that provides transform operations. const store = merge([ { @@ -1536,10 +1555,10 @@ injdef) { extraTransforms, { $ERRS: errs, - } + }, ], 1); const out = inject(spec, store, injdef); - const generr = (0 < size(errs) && !collect); + const generr = 0 < size(errs) && !collect; if (generr) { throw new Error(join(errs, ' | ')); } @@ -1547,15 +1566,15 @@ injdef) { } // A required string value. NOTE: Rejects empty strings. const validate_STRING = (inj) => { - let out = getprop(inj.dparent, inj.key); + const out = getprop(inj.dparent, inj.key); const t = typify(out); if (0 === (T_string & t)) { - let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010'); + const msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010'); inj.errs.push(msg); return NONE; } if (S_MT === out) { - let msg = 'Empty string at ' + pathify(inj.path, 1); + const msg = 'Empty string at ' + pathify(inj.path, 1); inj.errs.push(msg); return NONE; } @@ -1564,7 +1583,7 @@ const validate_STRING = (inj) => { const validate_TYPE = (inj, _val, ref) => { const tname = slice(ref, 1).toLowerCase(); const typev = 1 << (31 - TYPENAME.indexOf(tname)); - let out = getprop(inj.dparent, inj.key); + const out = getprop(inj.dparent, inj.key); const t = typify(out); // console.log('TYPE', tname, typev, tn(typev), 'O=', t, tn(t), out, 'C=', t & typev) if (0 === (t & typev)) { @@ -1575,7 +1594,7 @@ const validate_TYPE = (inj, _val, ref) => { }; // Allow any value. const validate_ANY = (inj) => { - let out = getprop(inj.dparent, inj.key); + const out = getprop(inj.dparent, inj.key); return out; }; // Specify child values for map or list. @@ -1598,7 +1617,7 @@ const validate_CHILD = (inj) => { return NONE; } const ckeys = keysof(tval); - for (let ckey of ckeys) { + for (const ckey of ckeys) { setprop(parent, ckey, clone(childtm)); // NOTE: modifying inj! This extends the child value loop in inject. keys.push(ckey); @@ -1658,7 +1677,7 @@ const validate_ONE = (inj, _val, _ref, store) => { inj.setval(inj.dparent, 2); inj.path = slice(inj.path, -1); inj.key = getelem(inj.path, -1); - let tvals = slice(parent, 1); + const tvals = slice(parent, 1); if (0 === size(tvals)) { inj.errs.push('The $ONE validator at field ' + pathify(inj.path, 1, 1) + @@ -1666,9 +1685,9 @@ const validate_ONE = (inj, _val, _ref, store) => { return; } // See if we can find a match. - for (let tval of tvals) { + for (const tval of tvals) { // If match, then errs.length = 0 - let terrs = []; + const terrs = []; const vstore = merge([{}, store], 1); vstore.$TOP = inj.dparent; const vcurrent = validate(inj.dparent, tval, { @@ -1703,7 +1722,7 @@ const validate_EXACT = (inj) => { // inj.path = slice(inj.path, 0, size(inj.path) - 1) inj.path = slice(inj.path, 0, -1); inj.key = getelem(inj.path, -1); - let tvals = slice(parent, 1); + const tvals = slice(parent, 1); if (0 === size(tvals)) { inj.errs.push('The $EXACT validator at field ' + pathify(inj.path, 1, 1) + @@ -1712,7 +1731,7 @@ const validate_EXACT = (inj) => { } // See if we can find an exact value match. let currentstr = undefined; - for (let tval of tvals) { + for (const tval of tvals) { let exactmatch = tval === inj.dparent; if (!exactmatch && isnode(tval)) { currentstr = undefined === currentstr ? stringify(inj.dparent) : currentstr; @@ -1726,7 +1745,9 @@ const validate_EXACT = (inj) => { // There was no match. const valdesc = replace(join(items(tvals, (n) => stringify(n[1])), ', '), R_TRANSFORM_NAME, (_m, p1) => p1.toLowerCase()); inj.errs.push(_invalidTypeMsg(inj.path, (1 < size(inj.path) ? '' : 'value ') + - 'exactly equal to ' + (1 === size(tvals) ? '' : 'one of ') + valdesc, typify(inj.dparent), inj.dparent, 'V0110')); + 'exactly equal to ' + + (1 === size(tvals) ? '' : 'one of ') + + valdesc, typify(inj.dparent), inj.dparent, 'V0110')); } else { delprop(parent, key); @@ -1769,7 +1790,7 @@ const _validation = (pval, key, parent, inj) => { // Empty spec object {} means object can be open (any keys). if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) { const badkeys = []; - for (let ckey of ckeys) { + for (const ckey of ckeys) { if (!haskey(pval, ckey)) { badkeys.push(ckey); } @@ -1796,8 +1817,7 @@ const _validation = (pval, key, parent, inj) => { else if (exact) { if (cval !== pval) { const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT; - inj.errs.push('Value ' + pathmsg + cval + - ' should equal ' + pval + S_DT); + inj.errs.push('Value ' + pathmsg + cval + ' should equal ' + pval + S_DT); } } else { @@ -1853,9 +1873,9 @@ injdef) { // NOTE: collecterrs parameter always wins. { $ERRS: errs, - } + }, ], 1); - let meta = getprop(injdef, 'meta', {}); + const meta = getprop(injdef, 'meta', {}); setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false)); const out = transform(data, spec, { meta, @@ -1864,7 +1884,7 @@ injdef) { handler: _validatehandler, errs, }); - const generr = (0 < size(errs) && !collect); + const generr = 0 < size(errs) && !collect; if (generr) { throw new Error(join(errs, ' | ')); } @@ -1877,8 +1897,8 @@ const select_AND = (inj, _val, _ref, store) => { const point = getpath(store, ppath); const vstore = merge([{}, store], 1); vstore.$TOP = point; - for (let term of terms) { - let terrs = []; + for (const term of terms) { + const terrs = []; validate(point, term, { extra: vstore, errs: terrs, @@ -1900,8 +1920,8 @@ const select_OR = (inj, _val, _ref, store) => { const point = getpath(store, ppath); const vstore = merge([{}, store], 1); vstore.$TOP = point; - for (let term of terms) { - let terrs = []; + for (const term of terms) { + const terrs = []; validate(point, term, { extra: vstore, errs: terrs, @@ -1924,7 +1944,7 @@ const select_NOT = (inj, _val, _ref, store) => { const point = getpath(store, ppath); const vstore = merge([{}, store], 1); vstore.$TOP = point; - let terrs = []; + const terrs = []; validate(point, term, { extra: vstore, errs: terrs, @@ -1968,8 +1988,14 @@ const select_CMP = (inj, _val, ref, store) => { setprop(gp, gkey, point); } else { - inj.errs.push('CMP: ' + pathify(ppath) + S_VIZ + stringify(point) + - ' fail:' + ref + ' ' + stringify(term)); + inj.errs.push('CMP: ' + + pathify(ppath) + + S_VIZ + + stringify(point) + + ' fail:' + + ref + + ' ' + + stringify(term)); } } return NONE; @@ -1983,7 +2009,7 @@ function select(children, query) { return []; } if (ismap(children)) { - children = items(children, n => { + children = items(children, (n) => { setprop(n[1], S_DKEY, n[0]); return n[1]; }); @@ -2004,7 +2030,7 @@ function select(children, query) { $GTE: select_CMP, $LTE: select_CMP, $LIKE: select_CMP, - } + }, }; const q = clone(query); walk(q, (_k, v) => { @@ -2042,14 +2068,31 @@ class Injection { this.meta = {}; } toString(prefix) { - return 'INJ' + (null == prefix ? '' : S_FS + prefix) + S_CN + + return ('INJ' + + (null == prefix ? '' : S_FS + prefix) + + S_CN + pad(pathify(this.path, 1)) + - MODENAME[this.mode] + (this.full ? '/full' : '') + S_CN + - 'key=' + this.keyI + S_FS + this.key + S_FS + S_OS + this.keys + S_CS + - ' p=' + stringify(this.parent, -1, 1) + - ' m=' + stringify(this.meta, -1, 1) + - ' d/' + pathify(this.dpath, 1) + '=' + stringify(this.dparent, -1, 1) + - ' r=' + stringify(this.nodes[0]?.[S_DTOP], -1, 1); + MODENAME[this.mode] + + (this.full ? '/full' : '') + + S_CN + + 'key=' + + this.keyI + + S_FS + + this.key + + S_FS + + S_OS + + this.keys + + S_CS + + ' p=' + + stringify(this.parent, -1, 1) + + ' m=' + + stringify(this.meta, -1, 1) + + ' d/' + + pathify(this.dpath, 1) + + '=' + + stringify(this.dparent, -1, 1) + + ' r=' + + stringify(this.nodes[0]?.[S_DTOP], -1, 1)); } descend() { this.meta.__d++; @@ -2066,7 +2109,7 @@ class Injection { // this.dparent is the containing node of the current store value. if (null != parentkey) { this.dparent = getprop(this.dparent, parentkey); - let lastpart = getelem(this.dpath, -1); + const lastpart = getelem(this.dpath, -1); if (lastpart === '$:' + parentkey) { this.dpath = slice(this.dpath, -1); } @@ -2101,16 +2144,15 @@ class Injection { setval(val, ancestor) { let parent = NONE; if (null == ancestor || ancestor < 2) { - parent = NONE === val ? - this.parent = delprop(this.parent, this.key) : - setprop(this.parent, this.key, val); + parent = + NONE === val + ? (this.parent = delprop(this.parent, this.key)) + : setprop(this.parent, this.key, val); } else { const aval = getelem(this.nodes, 0 - ancestor); const akey = getelem(this.path, 0 - ancestor); - parent = NONE === val ? - delprop(aval, akey) : - setprop(aval, akey, val); + parent = NONE === val ? delprop(aval, akey) : setprop(aval, akey, val); } // console.log('SETVAL', val, this.key, this.parent) return parent; @@ -2125,14 +2167,16 @@ class Injection { // } // Build a type validation error message. function _invalidTypeMsg(path, needtype, vt, v, _whence) { - let vs = null == v ? 'no value' : stringify(v); - return 'Expected ' + - (1 < size(path) ? ('field ' + pathify(path, 1) + ' to be ') : '') + - needtype + ', but found ' + - (null != v ? typename(vt) + S_VIZ : '') + vs + + const vs = null == v ? 'no value' : stringify(v); + return ('Expected ' + + (1 < size(path) ? 'field ' + pathify(path, 1) + ' to be ' : '') + + needtype + + ', but found ' + + (null != v ? typename(vt) + S_VIZ : '') + + vs + // Uncomment to help debug validation errors. // ' [' + _whence + ']' + - '.'; + '.'); } // Default inject handler for transforms. If the path resolves to a function, // call the function passing the injection inj. This is how transforms operate. @@ -2238,15 +2282,25 @@ const PLACEMENT = { }; function checkPlacement(modes, ijname, parentTypes, inj) { if (0 === (modes & inj.mode)) { - inj.errs.push('$' + ijname + ': invalid placement as ' + PLACEMENT[inj.mode] + - ', expected: ' + join(items([M_KEYPRE, M_KEYPOST, M_VAL].filter(m => modes & m), (n) => PLACEMENT[n[1]]), ',') + '.'); + inj.errs.push('$' + + ijname + + ': invalid placement as ' + + PLACEMENT[inj.mode] + + ', expected: ' + + join(items([M_KEYPRE, M_KEYPOST, M_VAL].filter((m) => modes & m), (n) => PLACEMENT[n[1]]), ',') + + '.'); return false; } if (!isempty(parentTypes)) { const ptype = typify(inj.parent); if (0 === (parentTypes & ptype)) { - inj.errs.push('$' + ijname + ': invalid placement in parent ' + typename(ptype) + - ', expected: ' + typename(parentTypes) + '.'); + inj.errs.push('$' + + ijname + + ': invalid placement in parent ' + + typename(ptype) + + ', expected: ' + + typename(parentTypes) + + '.'); return false; } } @@ -2262,9 +2316,16 @@ function injectorArgs(argTypes, args) { const arg = args[argI]; const argType = typify(arg); if (0 === (argTypes[argI] & argType)) { - found[0] = 'invalid argument: ' + stringify(arg, 22) + - ' (' + typename(argType) + ' at position ' + (1 + argI) + - ') is not of type: ' + typename(argTypes[argI]) + '.'; + found[0] = + 'invalid argument: ' + + stringify(arg, 22) + + ' (' + + typename(argType) + + ' at position ' + + (1 + argI) + + ') is not of type: ' + + typename(argTypes[argI]) + + '.'; break; } found[1 + argI] = arg; diff --git a/ts/dist/StructUtility.js.map b/ts/dist/StructUtility.js.map index 23f26271..ce7494f9 100644 --- a/ts/dist/StructUtility.js.map +++ b/ts/dist/StructUtility.js.map @@ -1 +1 @@ -{"version":3,"file":"StructUtility.js","sourceRoot":"","sources":["../src/StructUtility.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"StructUtility.js","sourceRoot":"","sources":["../src/StructUtility.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/ts/eslint.config.mjs b/ts/eslint.config.mjs new file mode 100644 index 00000000..19eefbc8 --- /dev/null +++ b/ts/eslint.config.mjs @@ -0,0 +1,29 @@ +// Flat ESLint config for the canonical TypeScript port. +// See https://typescript-eslint.io/getting-started/ + +import js from '@eslint/js' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { + ignores: ['dist/', 'dist-test/', 'node_modules/', 'coverage/', 'test/quick.js'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { ...globals.node }, + }, + rules: { + // The library is deliberately "JSON-shaped any" at its boundaries. + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + }, + }, +) diff --git a/ts/package.json b/ts/package.json index 9408c5c0..676482fb 100644 --- a/ts/package.json +++ b/ts/package.json @@ -25,6 +25,11 @@ "test-direct": "node dist-test/direct.js", "watch": "tsc --build src test -w", "build": "tsc --build src test", + "typecheck": "tsc --build src test --force", + "lint": "eslint src test", + "lint:fix": "eslint src test --fix", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"", "doc": "echo doc", "inject-version": "node -e \"t=['./src/StructUtility.ts','./test/runner.ts','./test/utility/StructUtility.test.ts'],r=require,fs=r('fs'),v=r('./package.json').version,t.map(f=>fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace(/\\/\\/ VERSION: @voxgig\\/struct [^\\n]+/, '// VERSION: @voxgig/struct '+v)))\"", "clean": "rm -rf dist dist-test node_modules yarn.lock package-lock.json", @@ -41,7 +46,12 @@ "dist" ], "devDependencies": { + "@eslint/js": "^9.14.0", "@types/node": "^25.6.0", - "typescript": "^5.9.3" + "eslint": "^9.14.0", + "globals": "^15.12.0", + "prettier": "^3.3.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.13.0" } } diff --git a/ts/src/StructUtility.ts b/ts/src/StructUtility.ts index f728eb4f..b202abfe 100644 --- a/ts/src/StructUtility.ts +++ b/ts/src/StructUtility.ts @@ -53,7 +53,6 @@ * */ - // String constants are explicitly defined. // Mode value for inject step (bitfield). @@ -137,12 +136,20 @@ const TYPENAME = [ S_function, S_symbol, S_null, - '', '', '', - '', '', '', '', + '', + '', + '', + '', + '', + '', + '', S_list, S_map, S_instance, - '', '', '', '', + '', + '', + '', + '', S_scalar, S_node, ] @@ -154,23 +161,19 @@ const NONE = undefined const SKIP = { '`$SKIP`': true } const DELETE = { '`$DELETE`': true } - // Regular expression constants -const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0). -const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g // Chars that need escaping in regexp. -const R_TRAILING_SLASH = /\/+$/ // Trailing slashes in URLs. -const R_LEADING_TRAILING_SLASH = /([^\/])\/+/ // Multiple slashes in URL middle. -const R_LEADING_SLASH = /^\/+/ // Leading slashes in URLs. -const R_QUOTES = /"/g // Double quotes for removal. -const R_DOT = /\./g // Dots in path strings. -const R_CLONE_REF = /^`\$REF:([0-9]+)`$/ // Copy reference in cloning. -const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax. -const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence. -const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names. -const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ // Full string injection pattern. -const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence. -const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence. -const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern. +const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0). +const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g // Chars that need escaping in regexp. +const R_QUOTES = /"/g // Double quotes for removal. +const R_DOT = /\./g // Dots in path strings. +const R_CLONE_REF = /^`\$REF:([0-9]+)`$/ // Copy reference in cloning. +const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax. +const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence. +const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names. +const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ // Full string injection pattern. +const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence. +const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence. +const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern. // Default max depth (for walk etc). const MAXDEPTH = 32 @@ -181,7 +184,6 @@ type PropKey = string | number // Type that can be indexed by both string and number keys. type Indexable = { [key: string]: any } & { [key: number]: any } - // For each key in a node (map or list), perform value injections in // three phases: on key value, before child, and then on key value again. // This mode is passed via the Injection structure. @@ -191,19 +193,19 @@ type InjectMode = number // - `a.b.c`: insert value at {a:{b:{c:1}}} // - `$FOO`: apply transform FOO type Injector = ( - inj: Injection, // Injection state. - val: any, // Injection value specification. - ref: string, // Original injection reference string. - store: any, // Current source root value. + inj: Injection, // Injection state. + val: any, // Injection value specification. + ref: string, // Original injection reference string. + store: any, // Current source root value. ) => any // Apply a custom modification to injections. type Modify = ( - val: any, // Value. - key?: PropKey, // Value key, if any, - parent?: any, // Parent node, if any. - inj?: Injection, // Injection state, if any. - store?: any, // Store, if any + val: any, // Value. + key?: PropKey, // Value key, if any, + parent?: any, // Parent node, if any. + inj?: Injection, // Injection state, if any. + store?: any, // Store, if any ) => void // Function applied to each node and leaf when walking a node structure depth first. @@ -217,16 +219,14 @@ type WalkApply = ( key: string | number | undefined, val: any, parent: any, - path: string[] + path: string[], ) => any - // Return type string for narrowest type. function typename(t: number) { return getelem(TYPENAME, Math.clz32(t), TYPENAME[0]) } - // Get a defined value. Returns alt if val is undefined. function getdef(val: any, alt: any) { if (NONE === val) { @@ -235,7 +235,6 @@ function getdef(val: any, alt: any) { return val } - // Value is a node - defined, and a map (hash) or list (array). // NOTE: typescript // things @@ -243,47 +242,43 @@ function isnode(val: any): val is Indexable { return null != val && S_object == typeof val } - // Value is a defined map (hash) with string keys. function ismap(val: any): val is { [key: string]: any } { return null != val && S_object == typeof val && !Array.isArray(val) } - // Value is a defined list (array) with integer keys (indexes). function islist(val: any): val is any[] { return Array.isArray(val) } - // Value is a defined string (non-empty) or integer key. function iskey(key: any): key is PropKey { const keytype = typeof key return (S_string === keytype && S_MT !== key) || S_number === keytype } - // Check for an "empty" value - undefined, empty string, array, object. function isempty(val: any) { - return null == val || S_MT === val || + return ( + null == val || + S_MT === val || (Array.isArray(val) && 0 === val.length) || (S_object === typeof val && 0 === Object.keys(val).length) + ) } - // Value is a function. -function isfunc(val: any): val is Function { +function isfunc(val: any): val is (...args: any[]) => any { return S_function === typeof val } - // The integer size of the value. For arrays and strings, the length, // for numbers, the integer part, for boolean, true is 1 and falso 0, for all other values, 0. function size(val: any): number { if (islist(val)) { return val.length - } - else if (ismap(val)) { + } else if (ismap(val)) { return Object.keys(val).length } @@ -291,19 +286,15 @@ function size(val: any): number { if (S_string == valtype) { return val.length - } - else if (S_number == typeof val) { + } else if (S_number == typeof val) { return Math.floor(val) - } - else if (S_boolean == typeof val) { + } else if (S_boolean == typeof val) { return true === val ? 1 : 0 - } - else { + } else { return 0 } } - // Extract part of an array or string into a new value, from the start // point to the end point. If no end is specified, extract to the // full length of the value. Negative arguments count from the end of @@ -311,7 +302,7 @@ function size(val: any): number { // is inclusive, and end is *exclusive*. // NOTE: input lists are not mutated by default. Use the mutate // argument to mutate lists in place. -function slice(val: V, start?: number, end?: number, mutate?: boolean): V { +function slice(val: V, start?: number, end?: number, mutate?: boolean): V { if (S_number === typeof val) { start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1 @@ -331,21 +322,16 @@ function slice(val: V, start?: number, end?: number, mutate?: boo end = 0 } start = 0 - } - - else if (null != end) { + } else if (null != end) { if (end < 0) { end = vlen + end if (end < 0) { end = 0 } - } - else if (vlen < end) { + } else if (vlen < end) { end = vlen } - } - - else { + } else { end = vlen } @@ -359,21 +345,17 @@ function slice(val: V, start?: number, end?: number, mutate?: boo for (let i = 0, j = start; j < end; i++, j++) { val[i] = val[j] } - val.length = (end - start) - } - else { + val.length = end - start + } else { val = val.slice(start, end) as V } - } - else if (S_string === typeof val) { + } else if (S_string === typeof val) { val = (val as string).substring(start, end) as V } - } - else { + } else { if (islist(val)) { val = [] as V - } - else if (S_string === typeof val) { + } else if (S_string === typeof val) { val = S_MT as V } } @@ -382,19 +364,16 @@ function slice(val: V, start?: number, end?: number, mutate?: boo return val } - // String padding. function pad(str: any, padding?: number, padchar?: string): string { str = S_string === typeof str ? str : stringify(str) padding = null == padding ? 44 : padding - padchar = null == padchar ? S_SP : ((padchar + S_SP)[0]) + padchar = null == padchar ? S_SP : (padchar + S_SP)[0] return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar) } - // Determine the type of a value as a bit code. function typify(value: any): number { - if (undefined === value) { return T_noval } @@ -403,41 +382,30 @@ function typify(value: any): number { if (null === value) { return T_scalar | T_null - } - else if (S_number === typestr) { + } else if (S_number === typestr) { if (Number.isInteger(value)) { return T_scalar | T_number | T_integer - } - else if (isNaN(value)) { + } else if (isNaN(value)) { return T_noval - } - else { + } else { return T_scalar | T_number | T_decimal } - } - else if (S_string === typestr) { + } else if (S_string === typestr) { return T_scalar | T_string - } - else if (S_boolean === typestr) { + } else if (S_boolean === typestr) { return T_scalar | T_boolean - } - else if (S_function === typestr) { + } else if (S_function === typestr) { return T_scalar | T_function } // For languages that have symbolic atoms. else if (S_symbol === typestr) { return T_scalar | T_symbol - } - - else if (Array.isArray(value)) { + } else if (Array.isArray(value)) { return T_node | T_list - } - - else if (S_object === typestr) { - + } else if (S_object === typestr) { if (value.constructor instanceof Function) { - let cname = value.constructor.name + const cname = value.constructor.name if ('Object' !== cname && 'Array' !== cname) { return T_node | T_instance } @@ -450,7 +418,6 @@ function typify(value: any): number { return T_any } - // Get a list element. The key should be an integer, or a string // that can parse to an integer only. Negative integers count from the end of the list. function getelem(val: any, key: any, alt?: any) { @@ -461,7 +428,7 @@ function getelem(val: any, key: any, alt?: any) { } if (islist(val)) { - let nkey = parseInt(key) + const nkey = parseInt(key) if (Number.isInteger(nkey) && ('' + key).match(R_INTEGER_KEY)) { if (nkey < 0) { key = val.length + nkey @@ -477,7 +444,6 @@ function getelem(val: any, key: any, alt?: any) { return out } - // Safely get a property of a node. Undefined arguments return undefined. // If the key is not found, return the alternative value, if any. function getprop(val: any, key: any, alt?: any) { @@ -498,7 +464,6 @@ function getprop(val: any, key: any, alt?: any) { return out } - // Convert different types of keys to string representation. // String keys are returned as is. // Number keys are converted to strings. @@ -513,42 +478,37 @@ function strkey(key: any = NONE): string { if (0 < (T_string & t)) { return key - } - else if (0 < (T_boolean & t)) { + } else if (0 < (T_boolean & t)) { return S_MT - } - else if (0 < (T_number & t)) { + } else if (0 < (T_number & t)) { return key % 1 === 0 ? String(key) : String(Math.floor(key)) } return S_MT } - // Sorted keys of a map, or indexes (as strings) of a list. // Root utility - only uses language facilities. function keysof(val: any): string[] { - return !isnode(val) ? [] : - ismap(val) ? Object.keys(val).sort() : (val as any).map((_n: any, i: number) => S_MT + i) + return !isnode(val) + ? [] + : ismap(val) + ? Object.keys(val).sort() + : (val as any).map((_n: any, i: number) => S_MT + i) } - // Value of property with name key in node val is defined. // Root utility - only uses language facilities. function haskey(val: any, key: any) { return NONE !== getprop(val, key) } - // List the sorted keys of a map or list as an array of tuples of the form [key, value]. // As with keysof, list indexes are converted to strings. // Root utility - only uses language facilities. -function items(val: any): [string, any][]; -function items(val: any, apply: (item: [string, any]) => T): T[]; -function items( - val: any, - apply?: (item: [string, any]) => any -): any[] { +function items(val: any): [string, any][] +function items(val: any, apply: (item: [string, any]) => T): T[] +function items(val: any, apply?: (item: [string, any]) => any): any[] { let out: [string, any][] = keysof(val).map((k: any) => [k, val[k]]) if (null != apply) { out = out.map(apply) @@ -556,7 +516,6 @@ function items( return out } - // To replicate the array spread operator: // a=1, b=[2,3], c=[4,5] // [a,...b,c] -> [1,2,3,[4,5]] @@ -569,12 +528,11 @@ function flatten(list: any[], depth?: number) { return list.flat(getdef(depth, 1)) } - // Filter item values using check function. function filter(val: any, check: (item: [string, any]) => boolean): any[] { - let all = items(val) - let numall = size(all) - let out = [] + const all = items(val) + const numall = size(all) + const out = [] for (let i = 0; i < numall; i++) { if (check(all[i])) { out.push(all[i][1]) @@ -583,38 +541,32 @@ function filter(val: any, check: (item: [string, any]) => boolean): any[] { return out } - // Escape regular expression. function escre(s: string) { // s = null == s ? S_MT : s return replace(s, R_ESCAPE_REGEXP, '\\$&') } - // Escape URLs. function escurl(s: string) { s = null == s ? S_MT : s return encodeURIComponent(s) } - // Replace a search string (all), or a regexp, in a source string. function replace(s: string, from: string | RegExp, to: any) { let rs = s - let ts = typify(s) + const ts = typify(s) if (0 === (T_string & ts)) { rs = stringify(s) - } - else if (0 < ((T_noval | T_null) & ts)) { + } else if (0 < ((T_noval | T_null) & ts)) { rs = S_MT - } - else { + } else { rs = stringify(s) } return rs.replace(from, to) } - // Concatenate url part strings, merging sep char as needed. function join(arr: any[], sep?: string, url?: boolean) { const sarr = size(arr) @@ -623,9 +575,9 @@ function join(arr: any[], sep?: string, url?: boolean) { const out = filter( items( // filter(arr, (n) => null != n[1] && S_MT !== n[1]), - filter(arr, (n) => (0 < (T_string & typify(n[1]))) && S_MT !== n[1]), + filter(arr, (n) => 0 < (T_string & typify(n[1])) && S_MT !== n[1]), (n) => { - let i = +n[0] + const i = +n[0] let s = n[1] if (NONE !== sepre && S_MT !== sepre) { @@ -642,22 +594,26 @@ function join(arr: any[], sep?: string, url?: boolean) { s = replace(s, RegExp(sepre + '+$'), S_MT) } - s = replace(s, RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'), - '$1' + sepdef + '$2') + s = replace( + s, + RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'), + '$1' + sepdef + '$2', + ) } return s - }), (n) => S_MT !== n[1]) - .join(sepdef) + }, + ), + (n) => S_MT !== n[1], + ).join(sepdef) return out } - // Output JSON in a "standard" format, with 2 space indents, each property on a new line, // and spaces after {[: and before ]}. Any "wierd" values (NaN, etc) are output as null. // In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed. -function jsonify(val: any, flags?: { indent?: number, offset?: number }) { +function jsonify(val: any, flags?: { indent?: number; offset?: number }) { let str = S_null if (null != val) { @@ -671,14 +627,14 @@ function jsonify(val: any, flags?: { indent?: number, offset?: number }) { if (0 < offset) { // Left offset entire indented JSON so that it aligns with surrounding code // indented by offset. Assume first brace is on line with asignment, so not offset. - str = '{\n' + + str = + '{\n' + join( - items( - slice(str.split('\n'), 1), - (n: any) => pad(n[1], 0 - offset - size(n[1]))), '\n') + items(slice(str.split('\n'), 1), (n: any) => pad(n[1], 0 - offset - size(n[1]))), + '\n', + ) } - } - catch (e: any) { + } catch { str = '__JSONIFY_FAILED__' } } @@ -686,7 +642,6 @@ function jsonify(val: any, flags?: { indent?: number, offset?: number }) { return str } - // Safely stringify a value for humans (NOT JSON!). function stringify(val: any, maxlen?: number, pretty?: any): string { let valstr = S_MT @@ -698,15 +653,10 @@ function stringify(val: any, maxlen?: number, pretty?: any): string { if (S_string === typeof val) { valstr = val - } - else { + } else { try { - valstr = JSON.stringify(val, function(_key: string, val: any) { - if ( - val !== null && - typeof val === "object" && - !Array.isArray(val) - ) { + valstr = JSON.stringify(val, function (_key: string, val: any) { + if (val !== null && typeof val === 'object' && !Array.isArray(val)) { const sortedObj: any = {} items(val, (n) => { sortedObj[n[0]] = val[n[0]] @@ -716,48 +666,56 @@ function stringify(val: any, maxlen?: number, pretty?: any): string { return val }) valstr = valstr.replace(R_QUOTES, S_MT) - } - catch (err: any) { + } catch { valstr = '__STRINGIFY_FAILED__' } } if (null != maxlen && -1 < maxlen) { - let js = valstr.substring(0, maxlen) - valstr = maxlen < valstr.length ? (js.substring(0, maxlen - 3) + '...') : valstr + const js = valstr.substring(0, maxlen) + valstr = maxlen < valstr.length ? js.substring(0, maxlen - 3) + '...' : valstr } if (pretty) { // Indicate deeper JSON levels with different terminal colors (simplistic wrt strings). - let c = items( + const c = items( [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69], - (n) => '\x1b[38;5;' + n[1] + 'm'), - r = '\x1b[0m', d = 0, o = c[0], t = o + (n) => '\x1b[38;5;' + n[1] + 'm', + ) + const r = '\x1b[0m' + let d = 0, + o = c[0], + t = o for (const ch of valstr) { if (ch === '{' || ch === '[') { - d++; o = c[d % c.length]; t += o + ch + d++ + o = c[d % c.length] + t += o + ch } else if (ch === '}' || ch === ']') { - t += o + ch; d--; o = c[d % c.length] + t += o + ch + d-- + o = c[d % c.length] } else { t += o + ch } } return t + r - } return valstr } - // Build a human friendly path string. function pathify(val: any, startin?: number, endin?: number) { let pathstr: string | undefined = NONE - let path: any[] | undefined = islist(val) ? val : - S_string == typeof val ? [val] : - S_number == typeof val ? [val] : - NONE + let path: any[] | undefined = islist(val) + ? val + : S_string == typeof val + ? [val] + : S_number == typeof val + ? [val] + : NONE const start = null == startin ? 0 : -1 < startin ? startin : 0 const end = null == endin ? 0 : -1 < endin ? endin : 0 @@ -766,15 +724,17 @@ function pathify(val: any, startin?: number, endin?: number) { path = slice(path, start, path.length - end) if (0 === path.length) { pathstr = '' - } - else { + } else { pathstr = join( items( - filter(path, (n) => iskey(n[1])), (n) => { - let p = n[1] - return S_number === typeof p ? S_MT + Math.floor(p) : - p.replace(R_DOT, S_MT) - }), S_DT) + filter(path, (n) => iskey(n[1])), + (n) => { + const p = n[1] + return S_number === typeof p ? S_MT + Math.floor(p) : p.replace(R_DOT, S_MT) + }, + ), + S_DT, + ) } } @@ -785,21 +745,19 @@ function pathify(val: any, startin?: number, endin?: number) { return pathstr } - // Clone a JSON-like data structure. // NOTE: function and instance values are copied, *not* cloned. function clone(val: any): any { const refs: any[] = [] const reftype = T_function | T_instance - const replacer: any = (_k: any, v: any) => 0 < (reftype & typify(v)) ? - (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v - const reviver: any = (_k: any, v: any, m: any) => S_string === typeof v ? - (m = v.match(R_CLONE_REF), m ? refs[m[1]] : v) : v + const replacer: any = (_k: any, v: any) => + 0 < (reftype & typify(v)) ? (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v + const reviver: any = (_k: any, v: any, m: any) => + S_string === typeof v ? ((m = v.match(R_CLONE_REF)), m ? refs[m[1]] : v) : v const out = NONE === val ? NONE : JSON.parse(JSON.stringify(val, replacer), reviver) return out } - // Define a JSON Object using function arguments. function jm(...kv: any[]): Record { const kvsize = size(kv) @@ -812,7 +770,6 @@ function jm(...kv: any[]): Record { return o } - // Define a JSON Array using function arguments. function jt(...v: any[]): any[] { const vsize = size(v) @@ -823,8 +780,7 @@ function jt(...v: any[]): any[] { return a } - -// Safely delete a property from an object or array element. +// Safely delete a property from an object or array element. // Undefined arguments and invalid keys are ignored. // Returns the (possibly modified) parent. // For objects, the property is deleted using the delete operator. @@ -838,8 +794,7 @@ function delprop(parent: PARENT, key: any): PARENT { if (ismap(parent)) { key = strkey(key) delete (parent as any)[key] - } - else if (islist(parent)) { + } else if (islist(parent)) { // Ensure key is an integer. let keyI = +key @@ -863,7 +818,6 @@ function delprop(parent: PARENT, key: any): PARENT { return parent } - // Safely set a property. Undefined arguments and invalid keys are ignored. // Returns the (possibly modified) parent. // If the parent is a list, and the key is negative, prepend the value. @@ -878,8 +832,7 @@ function setprop(parent: PARENT, key: any, val: any): PARENT { key = S_MT + key const pany = parent as any pany[key] = val - } - else if (islist(parent)) { + } else if (islist(parent)) { // Ensure key is an integer. let keyI = +key @@ -905,7 +858,6 @@ function setprop(parent: PARENT, key: any, val: any): PARENT { return parent } - // Walk a data structure depth first, applying a function to each value. // The `path` argument passed to the before/after callbacks is a single // mutable array per depth, shared across all callback invocations for the @@ -929,7 +881,7 @@ function walk( key?: string | number, parent?: any, path?: string[], - pool?: string[][] + pool?: string[][], ): any { if (NONE === pool) { pool = [[]] @@ -961,11 +913,9 @@ function walk( childPath[i] = path[i] } - for (let [ckey, child] of items(out)) { + for (const [ckey, child] of items(out)) { childPath[depth] = S_MT + ckey - setprop(out, ckey, walk( - child, before, after, maxdepth, ckey, out, childPath, pool - )) + setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath, pool)) } } @@ -974,7 +924,6 @@ function walk( return out } - // Merge a list of values into each other. Later values have // precedence. Nodes override scalars. Node kinds (list or map) // override each other, and do *not* merge. The first element is @@ -994,8 +943,7 @@ function merge(val: any, maxdepth?: number): any { if (0 === lenlist) { return NONE - } - else if (1 === lenlist) { + } else if (1 === lenlist) { return list[0] } @@ -1003,25 +951,19 @@ function merge(val: any, maxdepth?: number): any { out = getprop(list, 0, {}) for (let oI = 1; oI < lenlist; oI++) { - let obj = list[oI] + const obj = list[oI] if (!isnode(obj)) { // Nodes win. out = obj - } - else { + } else { // Current value at path end in overriding node. - let cur: any[] = [out] + const cur: any[] = [out] // Current value at path end in destination node. - let dst: any[] = [out] - - function before( - key: string | number | undefined, - val: any, - _parent: any, - path: string[] - ) { + const dst: any[] = [out] + + function before(key: string | number | undefined, val: any, _parent: any, path: string[]) { const pI = size(path) if (md <= pI) { @@ -1035,7 +977,6 @@ function merge(val: any, maxdepth?: number): any { // Descend into override node - Set up correct target in `after` function. else { - // Descend into destination node using same key. dst[pI] = 0 < pI ? getprop(dst[pI - 1], key) : dst[pI] const tval = dst[pI] @@ -1066,12 +1007,7 @@ function merge(val: any, maxdepth?: number): any { return val } - function after( - key: string | number | undefined, - _val: any, - _parent: any, - path: string[] - ) { + function after(key: string | number | undefined, _val: any, _parent: any, path: string[]) { const cI = size(path) const target = cur[cI - 1] const value = cur[cI] @@ -1097,20 +1033,24 @@ function merge(val: any, maxdepth?: number): any { return out } - // Set a value using a path. Missing path parts are created. // String paths create only maps. Use a string list to create list parts. function setpath( store: any, path: number | string | string[], val: any, - injdef?: Partial + injdef?: Partial, ) { const pathType = typify(path) - const parts = 0 < (T_list & pathType) ? path : - 0 < (T_string & pathType) ? (path as string).split(S_DT) : - 0 < (T_number & pathType) ? [path] : NONE + const parts = + 0 < (T_list & pathType) + ? path + : 0 < (T_string & pathType) + ? (path as string).split(S_DT) + : 0 < (T_number & pathType) + ? [path] + : NONE if (NONE === parts) { return NONE @@ -1132,21 +1072,22 @@ function setpath( if (DELETE === val) { delprop(parent, getelem(parts, -1)) - } - else { + } else { setprop(parent, getelem(parts, -1), val) } return parent } - function getpath(store: any, path: number | string | string[], injdef?: Partial) { - // Operate on a string array. - const parts = islist(path) ? path : - 'string' === typeof path ? path.split(S_DT) : - 'number' === typeof path ? [strkey(path)] : NONE + const parts = islist(path) + ? path + : 'string' === typeof path + ? path.split(S_DT) + : 'number' === typeof path + ? [strkey(path)] + : NONE if (NONE === parts) { return NONE @@ -1162,9 +1103,7 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial< // An empty path (incl empty string) just finds the store. if (null == path || null == store || (1 === numparts && S_MT === parts[0])) { val = src - } - else if (0 < numparts) { - + } else if (0 < numparts) { // Check for $ACTIONs if (1 === numparts) { val = getprop(store, parts[0]) @@ -1186,16 +1125,13 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial< if (injdef && S_DKEY === part) { part = getprop(injdef, S_key) - } - else if (injdef && part.startsWith('$GET:')) { + } else if (injdef && part.startsWith('$GET:')) { // $GET:path$ -> get store value, use as path part (string) part = stringify(getpath(src, slice(part, 5, -1))) - } - else if (injdef && part.startsWith('$REF:')) { + } else if (injdef && part.startsWith('$REF:')) { // $REF:refpath$ -> get spec value, use as path part (string) part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1))) - } - else if (injdef && part.startsWith('$META:')) { + } else if (injdef && part.startsWith('$META:')) { // $META:metapath$ -> get meta value, use as path part (string) part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1))) } @@ -1204,7 +1140,6 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial< part = part.replace(R_DOUBLE_DOLLAR, '$') if (S_MT === part) { - let ascends = 0 while (S_MT === parts[1 + pI]) { ascends++ @@ -1218,26 +1153,22 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial< if (0 === ascends) { val = dparent - } - else { + } else { // const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1)) const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)]) if (ascends <= size(dpath)) { val = getpath(store, fullpath) - } - else { + } else { val = NONE } break } - } - else { + } else { val = dparent } - } - else { + } else { val = getprop(val, part) } } @@ -1256,16 +1187,11 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial< return val } - // Inject values from a data store into a node recursively, resolving // paths against the store, or current if they are local. The modify // argument allows custom modification of the result. The inj // (Injection) argument is used to maintain recursive state. -function inject( - val: any, - store: any, - injdef?: Partial, -) { +function inject(val: any, store: any, injdef?: Partial) { const valtype = typeof val let inj: Injection = injdef as Injection @@ -1293,7 +1219,6 @@ function inject( // Descend into node. if (isnode(val)) { - // Keys are sorted alphanumerically to ensure determinism. // Injection transforms ($FOO) are processed *after* other keys. // NOTE: the optional digits suffix of the transform can thus be @@ -1304,11 +1229,10 @@ function inject( if (ismap(val)) { nodekeys = flatten([ - filter(nodekeys, (n => !n[1].includes(S_DS))), - filter(nodekeys, (n => n[1].includes(S_DS))), + filter(nodekeys, (n) => !n[1].includes(S_DS)), + filter(nodekeys, (n) => n[1].includes(S_DS)), ]) - } - else { + } else { nodekeys = keysof(val) } @@ -1317,7 +1241,6 @@ function inject( // 2. inj.mode=M_VAL - The child value is injected. // 3. inj.mode=M_KEYPOST - Key string is injected again, allowing child mutation. for (let nkI = 0; nkI < nodekeys.length; nkI++) { - const childinj = inj.child(nkI, nodekeys) const nodekey = childinj.key childinj.mode = M_KEYPRE @@ -1364,17 +1287,11 @@ function inject( // Custom modification. if (inj.modify && SKIP !== val) { - let mkey = inj.key - let mparent = inj.parent - let mval = getprop(mparent, mkey) - - inj.modify( - mval, - mkey, - mparent, - inj, - store - ) + const mkey = inj.key + const mparent = inj.parent + const mval = getprop(mparent, mkey) + + inj.modify(mval, mkey, mparent, inj, store) } // console.log('INJ-VAL', val) @@ -1386,7 +1303,6 @@ function inject( return getprop(inj.parent, S_DTOP) } - // The transform_* functions are special command inject handlers (see Injector). // Delete a key from a map or list. @@ -1395,7 +1311,6 @@ const transform_DELETE: Injector = (inj: Injection) => { return NONE } - // Copy value from source data. const transform_COPY: Injector = (inj: Injection, _val: any) => { const ijname = 'COPY' @@ -1404,13 +1319,12 @@ const transform_COPY: Injector = (inj: Injection, _val: any) => { return NONE } - let out = getprop(inj.dparent, inj.key) + const out = getprop(inj.dparent, inj.key) inj.setval(out) return out } - // As a value, inject the key of the parent node. // As a key, defined the name of the key property in the source object. const transform_KEY: Injector = (inj: Injection) => { @@ -1433,7 +1347,6 @@ const transform_KEY: Injector = (inj: Injection) => { return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2)) } - // Annotate node. Does nothing itself, just used by // other injectors, and is removed when called. const transform_ANNO: Injector = (inj: Injection) => { @@ -1442,10 +1355,9 @@ const transform_ANNO: Injector = (inj: Injection) => { return NONE } - -// Merge a list of objects into the current object. +// Merge a list of objects into the current object. // Must be a key in an object. The value is merged over the current object. -// If the value is an array, the elements are first merged using `merge`. +// If the value is an array, the elements are first merged using `merge`. // If the value is the empty string, merge the top level store. // Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] } const transform_MERGE: Injector = (inj: Injection) => { @@ -1478,15 +1390,9 @@ const transform_MERGE: Injector = (inj: Injection) => { return out } - // Convert a node to a list. // Format: ['`$EACH`', '`source-path-of-node`', child-template] -const transform_EACH: Injector = ( - inj: Injection, - _val: any, - _ref: string, - store: any -) => { +const transform_EACH: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { const ijname = 'EACH' if (!checkPlacement(M_VAL, ijname, T_list, inj)) { @@ -1515,18 +1421,22 @@ const transform_EACH: Injector = ( let tval: any = [] const tkey = getelem(inj.path, -2) - const target = getelem(inj.nodes, - 2, () => getelem(inj.nodes, -1)) + const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) // Create clones of the child template for each value of the current soruce. if (0 < (T_list & srctype)) { tval = items(src, () => clone(child)) - } - else if (0 < (T_map & srctype)) { - tval = items(src, (n => merge([ - clone(child), - // Make a note of the key for $KEY transforms. - { [S_BANNO]: { KEY: n[0] } } - ], 1))) + } else if (0 < (T_map & srctype)) { + tval = items(src, (n) => + merge( + [ + clone(child), + // Make a note of the key for $KEY transforms. + { [S_BANNO]: { KEY: n[0] } }, + ], + 1, + ), + ) } let rval = [] @@ -1570,16 +1480,10 @@ const transform_EACH: Injector = ( return rval[0] } - // Convert a node to a map. // Format: { '`$PACK`':['source-path', child-template]} -const transform_PACK: Injector = ( - inj: Injection, - _val: any, - _ref: string, - store: any -) => { - const { mode, key, path, parent, nodes } = inj +const transform_PACK: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { + const { key, path, parent, nodes } = inj const ijname = 'EACH' @@ -1611,8 +1515,7 @@ const transform_PACK: Injector = ( setprop(item[1], S_BANNO, { KEY: item[0] }) return item[1] }) - } - else { + } else { src = NONE } } @@ -1628,7 +1531,7 @@ const transform_PACK: Injector = ( const child = getprop(childspec, S_BVAL, childspec) // Build parallel target object. - let tval: any = {} + const tval: any = {} items(src, (item: [string, any]) => { const srckey = item[0] @@ -1638,8 +1541,7 @@ const transform_PACK: Injector = ( if (NONE !== keypath) { if (keypath.startsWith('`')) { key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1)) - } - else { + } else { key = getpath(srcnode, keypath, inj) } } @@ -1650,8 +1552,7 @@ const transform_PACK: Injector = ( const anno = getprop(srcnode, S_BANNO) if (NONE === anno) { delprop(tchild, S_BANNO) - } - else { + } else { setprop(tchild, S_BANNO, anno) } }) @@ -1659,14 +1560,15 @@ const transform_PACK: Injector = ( let rval = {} if (!isempty(tval)) { - // Build parallel source object. - let tsrc: any = {} + const tsrc: any = {} src.reduce((a: any, n: any, i: any) => { - let kn = null == keypath ? i : - keypath.startsWith('`') ? - inject(keypath, merge([{}, store, { $TOP: n }], 1)) : - getpath(n, keypath, inj) + const kn = + null == keypath + ? i + : keypath.startsWith('`') + ? inject(keypath, merge([{}, store, { $TOP: n }], 1)) + : getpath(n, keypath, inj) setprop(a, kn, n) return a @@ -1706,16 +1608,10 @@ const transform_PACK: Injector = ( return NONE } - // TODO: not found ref should removed key (setprop NONE) // Reference original spec (enables recursice transformations) // Format: ['`$REF`', '`spec-path`'] -const transform_REF: Injector = ( - inj: Injection, - val: any, - _ref: string, - store: any -) => { +const transform_REF: Injector = (inj: Injection, val: any, _ref: string, store: any) => { const { nodes } = inj if (M_VAL !== inj.mode) { @@ -1748,12 +1644,12 @@ const transform_REF: Injector = ( }) } - let tref = clone(ref) + const tref = clone(ref) const cpath = slice(inj.path, -3) const tpath = slice(inj.path, -1) - let tcur = getpath(store, cpath) - let tval = getpath(store, tpath) + const tcur = getpath(store, cpath) + const tval = getpath(store, tpath) let rval = NONE if (!hasSubRef || NONE !== tval) { @@ -1770,8 +1666,7 @@ const transform_REF: Injector = ( inject(tref, store, tinj) rval = tinj.val - } - else { + } else { rval = NONE } @@ -1784,13 +1679,7 @@ const transform_REF: Injector = ( return val } - -const transform_FORMAT: Injector = ( - inj: Injection, - _val: any, - _ref: string, - store: any -) => { +const transform_FORMAT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { // console.log('FORMAT-START', inj, _val) // Remove remaining keys to avoid spurious processing. @@ -1807,19 +1696,19 @@ const transform_FORMAT: Injector = ( // Source data. const tkey = getelem(inj.path, -2) - const target = getelem(inj.nodes, - 2, () => getelem(inj.nodes, -1)) + const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) const cinj = injectChild(child, store, inj) const resolved = cinj.val - let formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name) + const formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name) if (NONE === formatter) { inj.errs.push('$FORMAT: unknown format: ' + name + '.') return NONE } - let out = walk(resolved, formatter) + const out = walk(resolved, formatter) setprop(target, tkey, out) // _updateAncestors(inj, target, tkey, out) @@ -1827,17 +1716,15 @@ const transform_FORMAT: Injector = ( return out } - const FORMATTER: Record = { identity: (_k: any, v: any) => v, - upper: (_k: any, v: any) => isnode(v) ? v : ('' + v).toUpperCase(), - lower: (_k: any, v: any) => isnode(v) ? v : ('' + v).toLowerCase(), - string: (_k: any, v: any) => isnode(v) ? v : ('' + v), + upper: (_k: any, v: any) => (isnode(v) ? v : ('' + v).toUpperCase()), + lower: (_k: any, v: any) => (isnode(v) ? v : ('' + v).toLowerCase()), + string: (_k: any, v: any) => (isnode(v) ? v : '' + v), number: (_k: any, v: any) => { if (isnode(v)) { return v - } - else { + } else { let n = Number(v) if (isNaN(n)) { n = 0 @@ -1848,8 +1735,7 @@ const FORMATTER: Record = { integer: (_k: any, v: any) => { if (isnode(v)) { return v - } - else { + } else { let n = Number(v) if (isNaN(n)) { n = 0 @@ -1858,17 +1744,15 @@ const FORMATTER: Record = { } }, concat: (k: any, v: any) => - null == k && islist(v) ? join(items(v, (n => isnode(n[1]) ? S_MT : (S_MT + n[1]))), S_MT) : v + null == k && islist(v) + ? join( + items(v, (n) => (isnode(n[1]) ? S_MT : S_MT + n[1])), + S_MT, + ) + : v, } - - -const transform_APPLY: Injector = ( - inj: Injection, - _val: any, - _ref: string, - store: any -) => { +const transform_APPLY: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { const ijname = 'APPLY' if (!checkPlacement(M_VAL, ijname, T_list, inj)) { @@ -1883,7 +1767,7 @@ const transform_APPLY: Injector = ( } const tkey = getelem(inj.path, -2) - const target = getelem(inj.nodes, - 2, () => getelem(inj.nodes, -1)) + const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1)) const cinj = injectChild(child, store, inj) const resolved = cinj.val @@ -1895,14 +1779,13 @@ const transform_APPLY: Injector = ( return out } - // Transform data using spec. // Only operates on static JSON-like data. // Arrays are treated as if they are objects with indices as keys. function transform( data: any, // Source data to transform into new data (original not mutated) spec: any, // Transform specification; output follows this shape - injdef?: Partial + injdef?: Partial, ) { // Clone the spec so that the clone can be modified in place as the transform result. const origspec = spec @@ -1914,57 +1797,64 @@ function transform( const errs = injdef?.errs || [] const extraTransforms: any = {} - const extraData = null == extra ? NONE : items(extra) - .reduce((a: any, n: any[]) => - (n[0].startsWith(S_DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {}) - - const dataClone = merge([ - isempty(extraData) ? NONE : clone(extraData), - clone(data), - ]) + const extraData = + null == extra + ? NONE + : items(extra).reduce( + (a: any, n: any[]) => ( + n[0].startsWith(S_DS) ? (extraTransforms[n[0]] = n[1]) : (a[n[0]] = n[1]), + a + ), + {}, + ) + + const dataClone = merge([isempty(extraData) ? NONE : clone(extraData), clone(data)]) // Define a top level store that provides transform operations. - const store = merge([ - { - // The inject function recognises this special location for the root of the source data. - // NOTE: to escape data that contains "`$FOO`" keys at the top level, - // place that data inside a holding map: { myholder: mydata }. - $TOP: dataClone, - - $SPEC: () => origspec, - - // Escape backtick (this also works inside backticks). - $BT: () => S_BT, - - // Escape dollar sign (this also works inside backticks). - $DS: () => S_DS, - - // Insert current date and time as an ISO string. - $WHEN: () => new Date().toISOString(), - - $DELETE: transform_DELETE, - $COPY: transform_COPY, - $KEY: transform_KEY, - $ANNO: transform_ANNO, - $MERGE: transform_MERGE, - $EACH: transform_EACH, - $PACK: transform_PACK, - $REF: transform_REF, - $FORMAT: transform_FORMAT, - $APPLY: transform_APPLY, - }, - - // Custom extra transforms, if any. - extraTransforms, - - { - $ERRS: errs, - } - ], 1) + const store = merge( + [ + { + // The inject function recognises this special location for the root of the source data. + // NOTE: to escape data that contains "`$FOO`" keys at the top level, + // place that data inside a holding map: { myholder: mydata }. + $TOP: dataClone, + + $SPEC: () => origspec, + + // Escape backtick (this also works inside backticks). + $BT: () => S_BT, + + // Escape dollar sign (this also works inside backticks). + $DS: () => S_DS, + + // Insert current date and time as an ISO string. + $WHEN: () => new Date().toISOString(), + + $DELETE: transform_DELETE, + $COPY: transform_COPY, + $KEY: transform_KEY, + $ANNO: transform_ANNO, + $MERGE: transform_MERGE, + $EACH: transform_EACH, + $PACK: transform_PACK, + $REF: transform_REF, + $FORMAT: transform_FORMAT, + $APPLY: transform_APPLY, + }, + + // Custom extra transforms, if any. + extraTransforms, + + { + $ERRS: errs, + }, + ], + 1, + ) const out = inject(spec, store, injdef) - const generr = (0 < size(errs) && !collect) + const generr = 0 < size(errs) && !collect if (generr) { throw new Error(join(errs, ' | ')) } @@ -1972,20 +1862,19 @@ function transform( return out } - // A required string value. NOTE: Rejects empty strings. const validate_STRING: Injector = (inj: Injection) => { - let out = getprop(inj.dparent, inj.key) + const out = getprop(inj.dparent, inj.key) const t = typify(out) if (0 === (T_string & t)) { - let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010') + const msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010') inj.errs.push(msg) return NONE } if (S_MT === out) { - let msg = 'Empty string at ' + pathify(inj.path, 1) + const msg = 'Empty string at ' + pathify(inj.path, 1) inj.errs.push(msg) return NONE } @@ -1993,13 +1882,10 @@ const validate_STRING: Injector = (inj: Injection) => { return out } - - - const validate_TYPE: Injector = (inj: Injection, _val: any, ref: string) => { const tname = slice(ref, 1).toLowerCase() const typev = 1 << (31 - TYPENAME.indexOf(tname)) - let out = getprop(inj.dparent, inj.key) + const out = getprop(inj.dparent, inj.key) const t = typify(out) @@ -2013,15 +1899,12 @@ const validate_TYPE: Injector = (inj: Injection, _val: any, ref: string) => { return out } - // Allow any value. const validate_ANY: Injector = (inj: Injection) => { - let out = getprop(inj.dparent, inj.key) + const out = getprop(inj.dparent, inj.key) return out } - - // Specify child values for map or list. // Map syntax: {'`$CHILD`': child-template } // List syntax: ['`$CHILD`', child-template ] @@ -2040,15 +1923,13 @@ const validate_CHILD: Injector = (inj: Injection) => { if (NONE == tval) { tval = {} - } - else if (!ismap(tval)) { - inj.errs.push(_invalidTypeMsg( - slice(inj.path, -1), S_object, typify(tval), tval), 'V0220') + } else if (!ismap(tval)) { + inj.errs.push(_invalidTypeMsg(slice(inj.path, -1), S_object, typify(tval), tval), 'V0220') return NONE } const ckeys = keysof(tval) - for (let ckey of ckeys) { + for (const ckey of ckeys) { setprop(parent, ckey, clone(childtm)) // NOTE: modifying inj! This extends the child value loop in inject. @@ -2062,7 +1943,6 @@ const validate_CHILD: Injector = (inj: Injection) => { // List syntax. if (M_VAL === mode) { - if (!islist(parent)) { // $CHILD was not inside a list. inj.errs.push('Invalid $CHILD as value') @@ -2080,7 +1960,12 @@ const validate_CHILD: Injector = (inj: Injection) => { if (!islist(inj.dparent)) { const msg = _invalidTypeMsg( - slice(inj.path, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230') + slice(inj.path, -1), + S_list, + typify(inj.dparent), + inj.dparent, + 'V0230', + ) inj.errs.push(msg) inj.keyI = size(parent) return inj.dparent @@ -2100,26 +1985,22 @@ const validate_CHILD: Injector = (inj: Injection) => { return NONE } - // TODO: implement SOME, ALL // FIX: ONE should mean exactly one, not at least one (=SOME) // TODO: implement a generate validate_ALT to do all of these // Match at least one of the specified shapes. // Syntax: ['`$ONE`', alt0, alt1, ...] -const validate_ONE: Injector = ( - inj: Injection, - _val: any, - _ref: string, - store: any -) => { +const validate_ONE: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { const { mode, parent, keyI } = inj // Only operate in val mode, since parent is a list. if (M_VAL === mode) { if (!islist(parent) || 0 !== keyI) { - inj.errs.push('The $ONE validator at field ' + - pathify(inj.path, 1, 1) + - ' must be the first element of an array.') + inj.errs.push( + 'The $ONE validator at field ' + + pathify(inj.path, 1, 1) + + ' must be the first element of an array.', + ) return } @@ -2131,19 +2012,20 @@ const validate_ONE: Injector = ( inj.path = slice(inj.path, -1) inj.key = getelem(inj.path, -1) - let tvals = slice(parent, 1) + const tvals = slice(parent, 1) if (0 === size(tvals)) { - inj.errs.push('The $ONE validator at field ' + - pathify(inj.path, 1, 1) + - ' must have at least one argument.') + inj.errs.push( + 'The $ONE validator at field ' + + pathify(inj.path, 1, 1) + + ' must have at least one argument.', + ) return } // See if we can find a match. - for (let tval of tvals) { - + for (const tval of tvals) { // If match, then errs.length = 0 - let terrs: any[] = [] + const terrs: any[] = [] const vstore = merge([{}, store], 1) vstore.$TOP = inj.dparent @@ -2163,27 +2045,38 @@ const validate_ONE: Injector = ( } // There was no match. - const valdesc = - replace(join(items(tvals, (n) => stringify(n[1])), ', '), - R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase()) + const valdesc = replace( + join( + items(tvals, (n) => stringify(n[1])), + ', ', + ), + R_TRANSFORM_NAME, + (_m: any, p1: string) => p1.toLowerCase(), + ) - inj.errs.push(_invalidTypeMsg( - inj.path, - (1 < size(tvals) ? 'one of ' : '') + valdesc, - typify(inj.dparent), inj.dparent, 'V0210')) + inj.errs.push( + _invalidTypeMsg( + inj.path, + (1 < size(tvals) ? 'one of ' : '') + valdesc, + typify(inj.dparent), + inj.dparent, + 'V0210', + ), + ) } } - const validate_EXACT: Injector = (inj: Injection) => { const { mode, parent, key, keyI } = inj // Only operate in val mode, since parent is a list. if (M_VAL === mode) { if (!islist(parent) || 0 !== keyI) { - inj.errs.push('The $EXACT validator at field ' + - pathify(inj.path, 1, 1) + - ' must be the first element of an array.') + inj.errs.push( + 'The $EXACT validator at field ' + + pathify(inj.path, 1, 1) + + ' must be the first element of an array.', + ) return } @@ -2196,17 +2089,19 @@ const validate_EXACT: Injector = (inj: Injection) => { inj.path = slice(inj.path, 0, -1) inj.key = getelem(inj.path, -1) - let tvals = slice(parent, 1) + const tvals = slice(parent, 1) if (0 === size(tvals)) { - inj.errs.push('The $EXACT validator at field ' + - pathify(inj.path, 1, 1) + - ' must have at least one argument.') + inj.errs.push( + 'The $EXACT validator at field ' + + pathify(inj.path, 1, 1) + + ' must have at least one argument.', + ) return } // See if we can find an exact value match. let currentstr: string | undefined = undefined - for (let tval of tvals) { + for (const tval of tvals) { let exactmatch = tval === inj.dparent if (!exactmatch && isnode(tval)) { @@ -2221,31 +2116,35 @@ const validate_EXACT: Injector = (inj: Injection) => { } // There was no match. - const valdesc = - replace(join(items(tvals, (n) => stringify(n[1])), ', '), - R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase()) + const valdesc = replace( + join( + items(tvals, (n) => stringify(n[1])), + ', ', + ), + R_TRANSFORM_NAME, + (_m: any, p1: string) => p1.toLowerCase(), + ) - inj.errs.push(_invalidTypeMsg( - inj.path, - (1 < size(inj.path) ? '' : 'value ') + - 'exactly equal to ' + (1 === size(tvals) ? '' : 'one of ') + valdesc, - typify(inj.dparent), inj.dparent, 'V0110')) - } - else { + inj.errs.push( + _invalidTypeMsg( + inj.path, + (1 < size(inj.path) ? '' : 'value ') + + 'exactly equal to ' + + (1 === size(tvals) ? '' : 'one of ') + + valdesc, + typify(inj.dparent), + inj.dparent, + 'V0110', + ), + ) + } else { delprop(parent, key) } } - // This is the "modify" argument to inject. Use this to perform // generic validation. Runs *after* any special commands. -const _validation: Modify = ( - pval: any, - key?: any, - parent?: any, - inj?: Injection, -) => { - +const _validation: Modify = (pval: any, key?: any, parent?: any, inj?: Injection) => { if (NONE === inj) { return } @@ -2291,7 +2190,7 @@ const _validation: Modify = ( // Empty spec object {} means object can be open (any keys). if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) { const badkeys = [] - for (let ckey of ckeys) { + for (const ckey of ckeys) { if (!haskey(pval, ckey)) { badkeys.push(ckey) } @@ -2299,32 +2198,26 @@ const _validation: Modify = ( // Closed object, so reject extra keys not in shape. if (0 < size(badkeys)) { - const msg = - 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ') + const msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ') inj.errs.push(msg) } - } - else { + } else { // Object is open, so merge in extra keys. merge([pval, cval]) if (isnode(pval)) { delprop(pval, '`$OPEN`') } } - } - else if (islist(cval)) { + } else if (islist(cval)) { if (!islist(pval)) { inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030')) } - } - else if (exact) { + } else if (exact) { if (cval !== pval) { const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT - inj.errs.push('Value ' + pathmsg + cval + - ' should equal ' + pval + S_DT) + inj.errs.push('Value ' + pathmsg + cval + ' should equal ' + pval + S_DT) } - } - else { + } else { // Spec value was a default, copy over data setprop(parent, key, cval) } @@ -2332,8 +2225,6 @@ const _validation: Modify = ( return } - - // Validate a data structure against a shape specification. The shape // specification follows the "by example" principle. Plain data in // teh shape is treated as default values that also specify the @@ -2347,51 +2238,54 @@ const _validation: Modify = ( function validate( data: any, // Source data to transform into new data (original not mutated) spec: any, // Transform specification; output follows this shape - injdef?: Partial + injdef?: Partial, ) { const extra = injdef?.extra const collect = null != injdef?.errs const errs = injdef?.errs || [] - const store = merge([ - { - // Remove the transform commands. - $DELETE: null, - $COPY: null, - $KEY: null, - $META: null, - $MERGE: null, - $EACH: null, - $PACK: null, - - $STRING: validate_STRING, - $NUMBER: validate_TYPE, - $INTEGER: validate_TYPE, - $DECIMAL: validate_TYPE, - $BOOLEAN: validate_TYPE, - $NULL: validate_TYPE, - $NIL: validate_TYPE, - $MAP: validate_TYPE, - $LIST: validate_TYPE, - $FUNCTION: validate_TYPE, - $INSTANCE: validate_TYPE, - $ANY: validate_ANY, - $CHILD: validate_CHILD, - $ONE: validate_ONE, - $EXACT: validate_EXACT, - }, - - getdef(extra, {}), - - // A special top level value to collect errors. - // NOTE: collecterrs parameter always wins. - { - $ERRS: errs, - } - ], 1) - - let meta = getprop(injdef, 'meta', {}) + const store = merge( + [ + { + // Remove the transform commands. + $DELETE: null, + $COPY: null, + $KEY: null, + $META: null, + $MERGE: null, + $EACH: null, + $PACK: null, + + $STRING: validate_STRING, + $NUMBER: validate_TYPE, + $INTEGER: validate_TYPE, + $DECIMAL: validate_TYPE, + $BOOLEAN: validate_TYPE, + $NULL: validate_TYPE, + $NIL: validate_TYPE, + $MAP: validate_TYPE, + $LIST: validate_TYPE, + $FUNCTION: validate_TYPE, + $INSTANCE: validate_TYPE, + $ANY: validate_ANY, + $CHILD: validate_CHILD, + $ONE: validate_ONE, + $EXACT: validate_EXACT, + }, + + getdef(extra, {}), + + // A special top level value to collect errors. + // NOTE: collecterrs parameter always wins. + { + $ERRS: errs, + }, + ], + 1, + ) + + const meta = getprop(injdef, 'meta', {}) setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false)) const out = transform(data, spec, { @@ -2402,7 +2296,7 @@ function validate( errs, }) - const generr = (0 < size(errs) && !collect) + const generr = 0 < size(errs) && !collect if (generr) { throw new Error(join(errs, ' | ')) } @@ -2410,7 +2304,6 @@ function validate( return out } - const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { if (M_KEYPRE === inj.mode) { const terms = getprop(inj.parent, inj.key) @@ -2421,8 +2314,8 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an const vstore = merge([{}, store], 1) vstore.$TOP = point - for (let term of terms) { - let terrs: any[] = [] + for (const term of terms) { + const terrs: any[] = [] validate(point, term, { extra: vstore, @@ -2432,7 +2325,8 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an if (0 != size(terrs)) { inj.errs.push( - 'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)) + 'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms), + ) } } @@ -2442,7 +2336,6 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an } } - const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { if (M_KEYPRE === inj.mode) { const terms = getprop(inj.parent, inj.key) @@ -2453,8 +2346,8 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any const vstore = merge([{}, store], 1) vstore.$TOP = point - for (let term of terms) { - let terrs: any[] = [] + for (const term of terms) { + const terrs: any[] = [] validate(point, term, { extra: vstore, @@ -2471,12 +2364,10 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any } } - inj.errs.push( - 'OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)) + inj.errs.push('OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)) } } - const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => { if (M_KEYPRE === inj.mode) { const term = getprop(inj.parent, inj.key) @@ -2487,7 +2378,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an const vstore = merge([{}, store], 1) vstore.$TOP = point - let terrs: any[] = [] + const terrs: any[] = [] validate(point, term, { extra: vstore, @@ -2496,8 +2387,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an }) if (0 == size(terrs)) { - inj.errs.push( - 'NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term)) + inj.errs.push('NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term)) } const gkey = getelem(inj.path, -2) @@ -2506,7 +2396,6 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an } } - const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any) => { if (M_KEYPRE === inj.mode) { const term = getprop(inj.parent, inj.key) @@ -2522,17 +2411,13 @@ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any if ('$GT' === ref && point > term) { pass = true - } - else if ('$LT' === ref && point < term) { + } else if ('$LT' === ref && point < term) { pass = true - } - else if ('$GTE' === ref && point >= term) { + } else if ('$GTE' === ref && point >= term) { pass = true - } - else if ('$LTE' === ref && point <= term) { + } else if ('$LTE' === ref && point <= term) { pass = true - } - else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) { + } else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) { pass = true } @@ -2540,17 +2425,23 @@ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any // Update spec to match found value so that _validate does not complain. const gp = getelem(inj.nodes, -2) setprop(gp, gkey, point) - } - else { - inj.errs.push('CMP: ' + pathify(ppath) + S_VIZ + stringify(point) + - ' fail:' + ref + ' ' + stringify(term)) + } else { + inj.errs.push( + 'CMP: ' + + pathify(ppath) + + S_VIZ + + stringify(point) + + ' fail:' + + ref + + ' ' + + stringify(term), + ) } } return NONE } - // Select children from a top-level object that match a MongoDB-style query. // Supports $and, $or, and equality comparisons. // For arrays, children are elements; for objects, children are values. @@ -2561,12 +2452,11 @@ function select(children: any, query: any): any[] { } if (ismap(children)) { - children = items(children, n => { + children = items(children, (n) => { setprop(n[1], S_DKEY, n[0]) return n[1] }) - } - else { + } else { children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1])) } @@ -2583,7 +2473,7 @@ function select(children: any, query: any): any[] { $GTE: select_CMP, $LTE: select_CMP, $LIKE: select_CMP, - } + }, } const q = clone(query) @@ -2608,26 +2498,25 @@ function select(children: any, query: any): any[] { return results } - // Injection state used for recursive injection into JSON - like data structures. class Injection { - mode: InjectMode // Injection mode: M_KEYPRE, M_VAL, M_KEYPOST. - full: boolean // Transform escape was full key name. - keyI: number // Index of parent key in list of parent keys. - keys: string[] // List of parent keys. - key: string // Current parent key. - val: any // Current child value. - parent: any // Current parent (in transform specification). - path: string[] // Path to current node. - nodes: any[] // Stack of ancestor nodes. - handler: Injector // Custom handler for injections. - errs: any[] // Error collector. + mode: InjectMode // Injection mode: M_KEYPRE, M_VAL, M_KEYPOST. + full: boolean // Transform escape was full key name. + keyI: number // Index of parent key in list of parent keys. + keys: string[] // List of parent keys. + key: string // Current parent key. + val: any // Current child value. + parent: any // Current parent (in transform specification). + path: string[] // Path to current node. + nodes: any[] // Stack of ancestor nodes. + handler: Injector // Custom handler for injections. + errs: any[] // Error collector. meta: Record // Custom meta data. NOTE: do not merge, values must remain as-is. - dparent: any // Current data parent node (contains current data value). - dpath: string[] // Current data value path - base?: string // Base key for data in store, if any. - modify?: Modify // Modify injection output. - prior?: Injection // Parent (aka prior) injection. + dparent: any // Current data parent node (contains current data value). + dpath: string[] // Current data value path + base?: string // Base key for data in store, if any. + modify?: Modify // Modify injection output. + prior?: Injection // Parent (aka prior) injection. extra?: any constructor(val: any, parent: any) { @@ -2650,42 +2539,56 @@ class Injection { this.meta = {} } - toString(prefix?: string) { - return 'INJ' + (null == prefix ? '' : S_FS + prefix) + S_CN + + return ( + 'INJ' + + (null == prefix ? '' : S_FS + prefix) + + S_CN + pad(pathify(this.path, 1)) + - MODENAME[this.mode] + (this.full ? '/full' : '') + S_CN + - 'key=' + this.keyI + S_FS + this.key + S_FS + S_OS + this.keys + S_CS + - ' p=' + stringify(this.parent, -1, 1) + - ' m=' + stringify(this.meta, -1, 1) + - ' d/' + pathify(this.dpath, 1) + '=' + stringify(this.dparent, -1, 1) + - ' r=' + stringify(this.nodes[0]?.[S_DTOP], -1, 1) + MODENAME[this.mode] + + (this.full ? '/full' : '') + + S_CN + + 'key=' + + this.keyI + + S_FS + + this.key + + S_FS + + S_OS + + this.keys + + S_CS + + ' p=' + + stringify(this.parent, -1, 1) + + ' m=' + + stringify(this.meta, -1, 1) + + ' d/' + + pathify(this.dpath, 1) + + '=' + + stringify(this.dparent, -1, 1) + + ' r=' + + stringify(this.nodes[0]?.[S_DTOP], -1, 1) + ) } - descend() { this.meta.__d++ const parentkey = getelem(this.path, -2) // Resolve current node in store for local paths. if (NONE === this.dparent) { - // Even if there's no data, dpath should continue to match path, so that // relative paths work properly. if (1 < size(this.dpath)) { this.dpath = flatten([this.dpath, parentkey]) } - } - else { + } else { // this.dparent is the containing node of the current store value. if (null != parentkey) { this.dparent = getprop(this.dparent, parentkey) - let lastpart = getelem(this.dpath, -1) + const lastpart = getelem(this.dpath, -1) if (lastpart === '$:' + parentkey) { this.dpath = slice(this.dpath, -1) - } - else { + } else { this.dpath = flatten([this.dpath, parentkey]) } } @@ -2695,7 +2598,6 @@ class Injection { return this.dparent } - child(keyI: number, keys: string[]) { const key = strkey(keys[keyI]) const val = this.val @@ -2722,20 +2624,17 @@ class Injection { return cinj } - setval(val: any, ancestor?: number) { let parent = NONE if (null == ancestor || ancestor < 2) { - parent = NONE === val ? - this.parent = delprop(this.parent, this.key) : - setprop(this.parent, this.key, val) - } - else { + parent = + NONE === val + ? (this.parent = delprop(this.parent, this.key)) + : setprop(this.parent, this.key, val) + } else { const aval = getelem(this.nodes, 0 - ancestor) const akey = getelem(this.path, 0 - ancestor) - parent = NONE === val ? - delprop(aval, akey) : - setprop(aval, akey, val) + parent = NONE === val ? delprop(aval, akey) : setprop(aval, akey, val) } // console.log('SETVAL', val, this.key, this.parent) @@ -2743,42 +2642,36 @@ class Injection { } } - // Internal utilities // ================== - // // Update all references to target in inj.nodes. // function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) { // // SetProp is sufficient in TypeScript as target reference remains consistent even for lists. // setprop(target, tkey, tval) // } - // Build a type validation error message. function _invalidTypeMsg(path: any, needtype: string, vt: number, v: any, _whence?: string) { - let vs = null == v ? 'no value' : stringify(v) - - return 'Expected ' + - (1 < size(path) ? ('field ' + pathify(path, 1) + ' to be ') : '') + - needtype + ', but found ' + - (null != v ? typename(vt) + S_VIZ : '') + vs + - + const vs = null == v ? 'no value' : stringify(v) + + return ( + 'Expected ' + + (1 < size(path) ? 'field ' + pathify(path, 1) + ' to be ' : '') + + needtype + + ', but found ' + + (null != v ? typename(vt) + S_VIZ : '') + + vs + // Uncomment to help debug validation errors. // ' [' + _whence + ']' + '.' + ) } - // Default inject handler for transforms. If the path resolves to a function, // call the function passing the injection inj. This is how transforms operate. -const _injecthandler: Injector = ( - inj: Injection, - val: any, - ref: string, - store: any -): any => { +const _injecthandler: Injector = (inj: Injection, val: any, ref: string, store: any): any => { let out = val const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS)) @@ -2797,13 +2690,7 @@ const _injecthandler: Injector = ( return out } - -const _validatehandler: Injector = ( - inj: Injection, - val: any, - ref: string, - store: any -): any => { +const _validatehandler: Injector = (inj: Injection, val: any, ref: string, store: any): any => { let out = val const m = ref.match(R_META_PATH) @@ -2812,22 +2699,19 @@ const _validatehandler: Injector = ( if (ismetapath) { if ('=' === m[2]) { inj.setval([S_BEXACT, val]) - } - else { + } else { inj.setval(val) } inj.keyI = -1 out = SKIP - } - else { + } else { out = _injecthandler(inj, val, ref, store) } return out } - // Inject values from a data store into a string. Not a public utility - used by // `inject`. Inject are marked with `path` where path is resolved // with getpath against the store or current (if defined) @@ -2837,11 +2721,7 @@ const _validatehandler: Injector = ( // upper case letters only, and 999 is any digits, which are // discarded. This syntax specifies the name of a transform, and // optionally allows transforms to be ordered by alphanumeric sorting. -function _injectstr( - val: string, - store: any, - inj?: Injection -): any { +function _injectstr(val: string, store: any, inj?: Injection): any { // Can't inject into non-strings if (S_string !== typeof val || S_MT === val) { return S_MT @@ -2866,9 +2746,7 @@ function _injectstr( // Get the extracted path reference. out = getpath(store, pathref, inj) - } - - else { + } else { // Check for injections within the string. const partial = (_m: string, ref: string) => { // Special escapes inside injection. @@ -2900,11 +2778,9 @@ function _injectstr( return out } - // Handler Utilities // ================= - const MODENAME: any = { [M_VAL]: 'val', [M_KEYPRE]: 'key:pre', @@ -2921,28 +2797,44 @@ function checkPlacement( modes: InjectMode, ijname: string, parentTypes: number, - inj: Injection + inj: Injection, ): boolean { if (0 === (modes & inj.mode)) { - inj.errs.push('$' + ijname + ': invalid placement as ' + PLACEMENT[inj.mode] + - ', expected: ' + join(items( - [M_KEYPRE, M_KEYPOST, M_VAL].filter(m => modes & m), - (n: any) => PLACEMENT[n[1]]), ',') + '.') + inj.errs.push( + '$' + + ijname + + ': invalid placement as ' + + PLACEMENT[inj.mode] + + ', expected: ' + + join( + items( + [M_KEYPRE, M_KEYPOST, M_VAL].filter((m) => modes & m), + (n: any) => PLACEMENT[n[1]], + ), + ',', + ) + + '.', + ) return false } if (!isempty(parentTypes)) { const ptype = typify(inj.parent) if (0 === (parentTypes & ptype)) { - inj.errs.push('$' + ijname + ': invalid placement in parent ' + typename(ptype) + - ', expected: ' + typename(parentTypes) + '.') + inj.errs.push( + '$' + + ijname + + ': invalid placement in parent ' + + typename(ptype) + + ', expected: ' + + typename(parentTypes) + + '.', + ) return false - } } return true } - // function injectorArgs(argTypes: number[], inj: Injection): any { function injectorArgs(argTypes: number[], args: any[]): any { const numargs = size(argTypes) @@ -2953,9 +2845,16 @@ function injectorArgs(argTypes: number[], args: any[]): any { const arg = args[argI] const argType = typify(arg) if (0 === (argTypes[argI] & argType)) { - found[0] = 'invalid argument: ' + stringify(arg, 22) + - ' (' + typename(argType) + ' at position ' + (1 + argI) + - ') is not of type: ' + typename(argTypes[argI]) + '.' + found[0] = + 'invalid argument: ' + + stringify(arg, 22) + + ' (' + + typename(argType) + + ' at position ' + + (1 + argI) + + ') is not of type: ' + + typename(argTypes[argI]) + + '.' break } found[1 + argI] = arg @@ -2963,7 +2862,6 @@ function injectorArgs(argTypes: number[], args: any[]): any { return found } - function injectChild(child: any, store: any, inj: Injection): Injection { let cinj = inj @@ -2973,8 +2871,7 @@ function injectChild(child: any, store: any, inj: Injection): Injection { cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys) cinj.val = child setprop(cinj.parent, inj.prior.key, child) - } - else { + } else { cinj = inj.prior.child(inj.keyI, inj.keys) cinj.val = child setprop(cinj.parent, inj.key, child) @@ -2987,7 +2884,6 @@ function injectChild(child: any, store: any, inj: Injection): Injection { return cinj } - class StructUtility { clone = clone delprop = delprop @@ -3094,13 +2990,10 @@ export { typename, validate, walk, - SKIP, DELETE, - jm, jt, - T_any, T_noval, T_boolean, @@ -3116,20 +3009,13 @@ export { T_instance, T_scalar, T_node, - M_KEYPRE, M_KEYPOST, M_VAL, - MODENAME, - checkPlacement, injectorArgs, injectChild, } -export type { - Injection, - Injector, - WalkApply -} +export type { Injection, Injector, WalkApply } diff --git a/ts/test/client.test.ts b/ts/test/client.test.ts index a0859d8c..575f0670 100644 --- a/ts/test/client.test.ts +++ b/ts/test/client.test.ts @@ -1,19 +1,15 @@ - // RUN: npm test // RUN-SOME: npm run test-some --pattern=check import { test, describe } from 'node:test' -import { - makeRunner, -} from './runner' +import { makeRunner } from './runner' import { SDK } from './sdk.js' const TEST_JSON_FILE = '../../build/test/test.json' describe('client', async () => { - const runner = await makeRunner(TEST_JSON_FILE, await SDK.test()) const { spec, runset, subject } = await runner('check') @@ -21,5 +17,4 @@ describe('client', async () => { test('client-check-basic', async () => { await runset(spec.basic, subject) }) - }) diff --git a/ts/test/direct.ts b/ts/test/direct.ts index dd92851b..2fc93cbe 100644 --- a/ts/test/direct.ts +++ b/ts/test/direct.ts @@ -1,15 +1,7 @@ +import { transform, M_KEYPRE } from '..' -import { - validate, - transform, - M_KEYPRE, -} from '..' - - -let out: any let errs: any - // errs = [] // out = transform(undefined, undefined, { errs }) // console.log('transform-OUT', out, errs) @@ -26,8 +18,6 @@ let errs: any // out = transform(undefined, undefined, { errs }) // console.log('transform-OUT', out, errs) - - // errs = [] // out = validate(undefined, undefined, { errs }) // console.log('validate-OUT', out, errs) @@ -40,12 +30,10 @@ let errs: any // out = validate({ x: 2 }, undefined, { errs }) // console.log('validate-OUT', out, errs) - // errs = [] // out = validate({ x: 3 }, { y: '`dm$=a`' }, { meta: { dm: { a: 4 } }, errs }) // console.log('validate-OUT', out, errs) - // errs = [] // out = validate({ x: 4 }, { y: '`dm$=a`' }, { meta: { dm: {} }, errs }) // console.log('validate-OUT', out, errs) @@ -70,7 +58,6 @@ let errs: any // out = validate(1000, 1001, { errs }) // console.log('validate-OUT', out, errs) - const extra = { $CAPTURE: (inj: any) => { if (M_KEYPRE === inj.mode) { @@ -84,13 +71,12 @@ const extra = { }, } -let meta = { capture: {} } -out = transform( +const meta = { capture: {} } +const out = transform( { a: { b: 1, c: 2 } }, { a: { b: { '`$CAPTURE`': 'x' }, c: { '`$CAPTURE`': 'x' } } }, - { extra, errs, meta } + { extra, errs, meta }, ) console.dir(out, { depth: null }) console.dir(errs, { depth: null }) console.dir(meta, { depth: null }) - diff --git a/ts/test/runner.ts b/ts/test/runner.ts index f13a19fc..36abd9d3 100644 --- a/ts/test/runner.ts +++ b/ts/test/runner.ts @@ -10,12 +10,13 @@ const NULLMARK = '__NULL__' // Value is JSON null const UNDEFMARK = '__UNDEF__' // Value is not present (thus, undefined). const EXISTSMARK = '__EXISTS__' // Value exists (not undefined). - type Subject = (...args: any[]) => any -type RunSet = (testspec: any, testsubject: Function) => Promise -type RunSetFlags = (testspec: any, flags: Record, testsubject: Function) - => Promise - +type RunSet = (testspec: any, testsubject: Subject) => Promise +type RunSetFlags = ( + testspec: any, + flags: Record, + testsubject: Subject, +) => Promise type RunPack = { spec: Record @@ -25,7 +26,6 @@ type RunPack = { client: any } - type TestPack = { name?: string client: any @@ -35,38 +35,27 @@ type TestPack = { type Flags = Record - type Utility = { struct: any makeContext: (ctxmap: Record, basectx?: any) => any } - type Client = { utility: () => Utility } - async function makeRunner(testfile: string, client: Client) { - - return async function runner( - name: string, - store?: any, - ): Promise { + return async function runner(name: string, store?: any): Promise { store = store || {} const utility = client.utility() const structUtils = utility.struct - let spec = resolveSpec(name, testfile) - let clients = await resolveClients(client, spec, store, structUtils) + const spec = resolveSpec(name, testfile) + const clients = await resolveClients(client, spec, store, structUtils) let subject = resolveSubject(name, utility) - let runsetflags: RunSetFlags = async ( - testspec: any, - flags: Flags, - testsubject: Function - ) => { + const runsetflags: RunSetFlags = async (testspec: any, flags: Flags, testsubject: Subject) => { subject = testsubject || subject flags = resolveFlags(flags) const testspecmap = fixJSON(testspec, flags) @@ -76,16 +65,15 @@ async function makeRunner(testfile: string, client: Client) { try { entry = resolveEntry(entry, flags) - let testpack = resolveTestPack(name, entry, subject, client, clients) - let args = resolveArgs(entry, testpack, utility, structUtils) + const testpack = resolveTestPack(name, entry, subject, client, clients) + const args = resolveArgs(entry, testpack, utility, structUtils) let res = await testpack.subject(...args) res = fixJSON(res, flags) entry.res = res checkResult(entry, args, res, structUtils) - } - catch (err: any) { + } catch (err: any) { if (err instanceof AssertionError) { throw err } @@ -94,10 +82,8 @@ async function makeRunner(testfile: string, client: Client) { } } - let runset: RunSet = async ( - testspec: any, - testsubject: Function - ) => runsetflags(testspec, {}, testsubject) + const runset: RunSet = async (testspec: any, testsubject: Subject) => + runsetflags(testspec, {}, testsubject) const runpack: RunPack = { spec, @@ -112,25 +98,21 @@ async function makeRunner(testfile: string, client: Client) { } function resolveSpec(name: string, testfile: string): Record { - const alltests = - JSON.parse(readFileSync(join(__dirname, testfile), 'utf8')) + const alltests = JSON.parse(readFileSync(join(__dirname, testfile), 'utf8')) - let spec = alltests.primary?.[name] || alltests[name] || alltests + const spec = alltests.primary?.[name] || alltests[name] || alltests return spec } - async function resolveClients( client: any, spec: Record, store: any, - structUtils: Record -): - Promise> { - + structUtils: Record, +): Promise> { const clients: Record = {} if (spec.DEF && spec.DEF.client) { - for (let cn in spec.DEF.client) { + for (const cn in spec.DEF.client) { const cdef = spec.DEF.client[cn] const copts = cdef.test.options || {} if ('object' === typeof store && structUtils?.inject) { @@ -143,13 +125,11 @@ async function resolveClients( return clients } - function resolveSubject(name: string, container: any) { const subject = container[name] || container.struct[name] return subject } - function resolveFlags(flags?: Flags): Flags { if (null == flags) { flags = {} @@ -158,28 +138,23 @@ function resolveFlags(flags?: Flags): Flags { return flags } - function resolveEntry(entry: any, flags: Flags): any { entry.out = null == entry.out && flags.null ? NULLMARK : entry.out return entry } - function checkResult(entry: any, args: any[], res: any, structUtils: Record) { let matched = false if (entry.err) { - return fail('Expected error did not occur: ' + entry.err + - '\n\nENTRY: ' + JSON.stringify(entry, null, 2)) + return fail( + 'Expected error did not occur: ' + entry.err + '\n\nENTRY: ' + JSON.stringify(entry, null, 2), + ) } if (entry.match) { const result = { in: entry.in, args, out: entry.res, ctx: entry.ctx } - match( - entry.match, - result, - structUtils - ) + match(entry.match, result, structUtils) matched = true } @@ -198,7 +173,6 @@ function checkResult(entry: any, args: any[], res: any, structUtils: Record) { entry.thrown = err @@ -211,41 +185,36 @@ function handleError(entry: any, err: any, structUtils: Record) { match( entry.match, { in: entry.in, out: entry.res, ctx: entry.ctx, err: fixJSON(err, { null: true }) }, - structUtils + structUtils, ) } return } - fail('ERROR MATCH: [' + structUtils.stringify(entry_err) + - '] <=> [' + err.message + ']') + fail('ERROR MATCH: [' + structUtils.stringify(entry_err) + '] <=> [' + err.message + ']') } // Unexpected error (test didn't specify an error expectation) else if (err instanceof AssertionError) { fail(err.message + '\n\nENTRY: ' + JSON.stringify(entry, null, 2)) - } - else { + } else { fail(err.stack + '\\nnENTRY: ' + JSON.stringify(entry, null, 2)) } } - function resolveArgs( entry: any, testpack: TestPack, utility: Utility, - structUtils: Record + structUtils: Record, ): any[] { let args: any[] = [] if (entry.ctx) { args = [entry.ctx] - } - else if (entry.args) { + } else if (entry.args) { args = entry.args - } - else { + } else { args = [structUtils.clone(entry.in)] } @@ -265,13 +234,12 @@ function resolveArgs( return args } - function resolveTestPack( name: string, entry: any, subject: Subject, client: any, - clients: Record + clients: Record, ) { const testpack: TestPack = { name, @@ -289,17 +257,12 @@ function resolveTestPack( return testpack } - -function match( - check: any, - basex: any, - structUtils: Record -) { +function match(check: any, basex: any, structUtils: Record) { const cbase = structUtils.clone(basex) structUtils.walk(check, (_key: any, val: any, _parent: any, path: any) => { if (!structUtils.isnode(val)) { - let baseval = structUtils.getpath(cbase, path) + const baseval = structUtils.getpath(cbase, path) if (baseval === val) { return val @@ -316,9 +279,15 @@ function match( } if (!matchval(val, baseval, structUtils)) { - fail('MATCH: ' + path.join('.') + - ': [' + structUtils.stringify(val) + - '] <=> [' + structUtils.stringify(baseval) + ']') + fail( + 'MATCH: ' + + path.join('.') + + ': [' + + structUtils.stringify(val) + + '] <=> [' + + structUtils.stringify(baseval) + + ']', + ) } } @@ -326,28 +295,20 @@ function match( }) } - -function matchval( - check: any, - base: any, - structUtils: Record -) { +function matchval(check: any, base: any, structUtils: Record) { let pass = check === base if (!pass) { - if ('string' === typeof check) { - let basestr = structUtils.stringify(base) + const basestr = structUtils.stringify(base) - let rem = check.match(/^\/(.+)\/$/) + const rem = check.match(/^\/(.+)\/$/) if (rem) { pass = new RegExp(rem[1]).test(basestr) - } - else { + } else { pass = basestr.toLowerCase().includes(structUtils.stringify(check).toLowerCase()) } - } - else if ('function' === typeof check) { + } else if ('function' === typeof check) { pass = true } } @@ -355,7 +316,6 @@ function matchval( return pass } - function fixJSON(val: any, flags?: Flags): any { if (null == val) { return flags?.null ? NULLMARK : val @@ -380,25 +340,12 @@ function fixJSON(val: any, flags?: Flags): any { return JSON.parse(JSON.stringify(val, replacer)) } - -function nullModifier( - val: any, - key: any, - parent: any -) { - if ("__NULL__" === val) { +function nullModifier(val: any, key: any, parent: any) { + if ('__NULL__' === val) { parent[key] = null - } - else if ('string' === typeof val) { + } else if ('string' === typeof val) { parent[key] = val.replaceAll('__NULL__', 'null') } } - -export { - NULLMARK, - EXISTSMARK, - nullModifier, - makeRunner, -} - +export { NULLMARK, EXISTSMARK, nullModifier, makeRunner } diff --git a/ts/test/sdk.ts b/ts/test/sdk.ts index b421ed4b..a25c2755 100644 --- a/ts/test/sdk.ts +++ b/ts/test/sdk.ts @@ -1,8 +1,6 @@ - import { StructUtility } from '../dist/StructUtility' class SDK { - #opts: any = {} #utility: any = {} @@ -14,12 +12,13 @@ class SDK { makeContext: (ctxmap: any) => ctxmap, check: (ctx: any) => { return { - zed: 'ZED' + + zed: + 'ZED' + (null == this.#opts ? '' : null == this.#opts.foo ? '' : this.#opts.foo) + '_' + - (null == ctx.meta?.bar ? '0' : ctx.meta.bar) + (null == ctx.meta?.bar ? '0' : ctx.meta.bar), } - } + }, } } @@ -36,6 +35,4 @@ class SDK { } } -export { - SDK -} +export { SDK } diff --git a/ts/test/utility/StructUtility.test.ts b/ts/test/utility/StructUtility.test.ts index 94585228..53b08cdb 100644 --- a/ts/test/utility/StructUtility.test.ts +++ b/ts/test/utility/StructUtility.test.ts @@ -5,25 +5,14 @@ import { test, describe, before } from 'node:test' import assert from 'node:assert' -import { - makeRunner, - nullModifier, - NULLMARK, -} from '../runner' - - -import { - SDK, - TEST_JSON_FILE -} from './index' +import { makeRunner, nullModifier, NULLMARK } from '../runner' +import { SDK, TEST_JSON_FILE } from './index' const { equal, deepEqual } = assert - // NOTE: tests are (mostly) in order of increasing dependence. describe('StructUtility', async () => { - let spec: any let runset: any let runsetflags: any @@ -43,8 +32,6 @@ describe('StructUtility', async () => { struct = client.utility().struct }) - - test('exists', () => { const s = struct @@ -93,7 +80,6 @@ describe('StructUtility', async () => { equal('function', typeof s.walk) }) - // minor tests // =========== @@ -101,46 +87,43 @@ describe('StructUtility', async () => { await runset(spec.minor.isnode, struct.isnode) }) - test('minor-ismap', async () => { await runset(spec.minor.ismap, struct.ismap) }) - test('minor-islist', async () => { await runset(spec.minor.islist, struct.islist) }) - test('minor-iskey', async () => { await runsetflags(spec.minor.iskey, { null: false }, struct.iskey) }) - test('minor-strkey', async () => { await runsetflags(spec.minor.strkey, { null: false }, struct.strkey) }) - test('minor-isempty', async () => { await runsetflags(spec.minor.isempty, { null: false }, struct.isempty) }) - test('minor-isfunc', async () => { const { isfunc } = struct await runset(spec.minor.isfunc, isfunc) - function f0() { return null } + function f0() { + return null + } equal(isfunc(f0), true) - equal(isfunc(() => null), true) + equal( + isfunc(() => null), + true, + ) }) - test('minor-clone', async () => { await runsetflags(spec.minor.clone, { null: false }, struct.clone) }) - test('minor-edge-clone', async () => { const { clone } = struct @@ -148,19 +131,20 @@ describe('StructUtility', async () => { deepEqual({ a: f0 }, clone({ a: f0 })) const x = { y: 1 } - let xc = clone(x) + const xc = clone(x) deepEqual(x, xc) assert(x !== xc) - class A { x = 1 } + class A { + x = 1 + } const a = new A() - let ac = clone(a) + const ac = clone(a) deepEqual(a, ac) assert(a === ac) equal(a.constructor.name, ac.constructor.name) }) - test('minor-filter', async () => { const checkmap: any = { gt3: (n: any) => n[1] > 3, @@ -169,158 +153,151 @@ describe('StructUtility', async () => { await runset(spec.minor.filter, (vin: any) => struct.filter(vin.val, checkmap[vin.check])) }) - test('minor-flatten', async () => { await runset(spec.minor.flatten, (vin: any) => struct.flatten(vin.val, vin.depth)) }) - test('minor-escre', async () => { await runset(spec.minor.escre, struct.escre) }) - test('minor-escurl', async () => { await runset(spec.minor.escurl, struct.escurl) }) - test('minor-stringify', async () => { await runset(spec.minor.stringify, (vin: any) => - struct.stringify((NULLMARK === vin.val ? "null" : vin.val), vin.max)) + struct.stringify(NULLMARK === vin.val ? 'null' : vin.val, vin.max), + ) }) - test('minor-edge-stringify', async () => { const { stringify } = struct const a: any = {} a.a = a equal(stringify(a), '__STRINGIFY_FAILED__') - equal(stringify({ a: [9] }, -1, true), + equal( + stringify({ a: [9] }, -1, true), '\x1B[38;5;81m\x1B[38;5;118m{\x1B[38;5;118ma\x1B[38;5;118m:' + - '\x1B[38;5;213m[\x1B[38;5;213m9\x1B[38;5;213m]\x1B[38;5;118m}\x1B[0m') + '\x1B[38;5;213m[\x1B[38;5;213m9\x1B[38;5;213m]\x1B[38;5;118m}\x1B[0m', + ) }) - test('minor-jsonify', async () => { - await runsetflags(spec.minor.jsonify, { null: false }, - (vin: any) => struct.jsonify(vin.val, vin.flags)) + await runsetflags(spec.minor.jsonify, { null: false }, (vin: any) => + struct.jsonify(vin.val, vin.flags), + ) }) - test('minor-edge-jsonify', async () => { const { jsonify } = struct - equal(jsonify(() => 1), 'null') + equal( + jsonify(() => 1), + 'null', + ) }) - test('minor-pathify', async () => { - await runsetflags( - spec.minor.pathify, { null: true }, - (vin: any) => { - let path = NULLMARK == vin.path ? undefined : vin.path - let pathstr = struct.pathify(path, vin.from).replace('__NULL__.', '') - pathstr = NULLMARK === vin.path ? pathstr.replace('>', ':null>') : pathstr - return pathstr - }) + await runsetflags(spec.minor.pathify, { null: true }, (vin: any) => { + const path = NULLMARK == vin.path ? undefined : vin.path + let pathstr = struct.pathify(path, vin.from).replace('__NULL__.', '') + pathstr = NULLMARK === vin.path ? pathstr.replace(/>/g, ':null>') : pathstr + return pathstr + }) }) - test('minor-items', async () => { await runset(spec.minor.items, struct.items) }) - test('minor-edge-items', async () => { const { items } = struct const a0: any = [11, 22, 33] a0.x = 1 - deepEqual(items(a0), [['0', 11], ['1', 22], ['2', 33]]) + deepEqual(items(a0), [ + ['0', 11], + ['1', 22], + ['2', 33], + ]) }) - test('minor-getelem', async () => { const { getelem } = struct await runsetflags(spec.minor.getelem, { null: false }, (vin: any) => - null == vin.alt ? getelem(vin.val, vin.key) : getelem(vin.val, vin.key, vin.alt)) + null == vin.alt ? getelem(vin.val, vin.key) : getelem(vin.val, vin.key, vin.alt), + ) }) - test('minor-edge-getelem', async () => { const { getelem } = struct - equal(getelem([], 1, () => 2), 2) + equal( + getelem([], 1, () => 2), + 2, + ) }) - test('minor-getprop', async () => { const { getprop } = struct await runsetflags(spec.minor.getprop, { null: false }, (vin: any) => - undefined === vin.alt ? getprop(vin.val, vin.key) : getprop(vin.val, vin.key, vin.alt)) + undefined === vin.alt ? getprop(vin.val, vin.key) : getprop(vin.val, vin.key, vin.alt), + ) }) - test('minor-edge-getprop', async () => { const { getprop } = struct - let strarr = ['a', 'b', 'c', 'd', 'e'] + const strarr = ['a', 'b', 'c', 'd', 'e'] deepEqual(getprop(strarr, 2), 'c') deepEqual(getprop(strarr, '2'), 'c') - let intarr = [2, 3, 5, 7, 11] + const intarr = [2, 3, 5, 7, 11] deepEqual(getprop(intarr, 2), 5) deepEqual(getprop(intarr, '2'), 5) }) - test('minor-setprop', async () => { - await runset(spec.minor.setprop, (vin: any) => - struct.setprop(vin.parent, vin.key, vin.val)) + await runset(spec.minor.setprop, (vin: any) => struct.setprop(vin.parent, vin.key, vin.val)) }) - test('minor-edge-setprop', async () => { const { setprop } = struct - let strarr0 = ['a', 'b', 'c', 'd', 'e'] - let strarr1 = ['a', 'b', 'c', 'd', 'e'] + const strarr0 = ['a', 'b', 'c', 'd', 'e'] + const strarr1 = ['a', 'b', 'c', 'd', 'e'] deepEqual(setprop(strarr0, 2, 'C'), ['a', 'b', 'C', 'd', 'e']) deepEqual(setprop(strarr1, '2', 'CC'), ['a', 'b', 'CC', 'd', 'e']) - let intarr0 = [2, 3, 5, 7, 11] - let intarr1 = [2, 3, 5, 7, 11] + const intarr0 = [2, 3, 5, 7, 11] + const intarr1 = [2, 3, 5, 7, 11] deepEqual(setprop(intarr0, 2, 55), [2, 3, 55, 7, 11]) deepEqual(setprop(intarr1, '2', 555), [2, 3, 555, 7, 11]) }) - test('minor-delprop', async () => { - await runset(spec.minor.delprop, (vin: any) => - struct.delprop(vin.parent, vin.key)) + await runset(spec.minor.delprop, (vin: any) => struct.delprop(vin.parent, vin.key)) }) - test('minor-edge-delprop', async () => { const { delprop } = struct - let strarr0 = ['a', 'b', 'c', 'd', 'e'] - let strarr1 = ['a', 'b', 'c', 'd', 'e'] + const strarr0 = ['a', 'b', 'c', 'd', 'e'] + const strarr1 = ['a', 'b', 'c', 'd', 'e'] deepEqual(delprop(strarr0, 2), ['a', 'b', 'd', 'e']) deepEqual(delprop(strarr1, '2'), ['a', 'b', 'd', 'e']) - let intarr0 = [2, 3, 5, 7, 11] - let intarr1 = [2, 3, 5, 7, 11] + const intarr0 = [2, 3, 5, 7, 11] + const intarr1 = [2, 3, 5, 7, 11] deepEqual(delprop(intarr0, 2), [2, 3, 7, 11]) deepEqual(delprop(intarr1, '2'), [2, 3, 7, 11]) }) - test('minor-haskey', async () => { await runsetflags(spec.minor.haskey, { null: false }, (vin: any) => - struct.haskey(vin.src, vin.key)) + struct.haskey(vin.src, vin.key), + ) }) - test('minor-keysof', async () => { await runset(spec.minor.keysof, struct.keysof) }) @@ -332,64 +309,60 @@ describe('StructUtility', async () => { deepEqual(keysof(a0), [0, 1, 2]) }) - - test('minor-join', async () => { - await runsetflags(spec.minor.join, { null: false }, - (vin: any) => struct.join(vin.val, vin.sep, vin.url)) + await runsetflags(spec.minor.join, { null: false }, (vin: any) => + struct.join(vin.val, vin.sep, vin.url), + ) }) - test('minor-typename', async () => { await runset(spec.minor.typename, struct.typename) }) - test('minor-typify', async () => { await runsetflags(spec.minor.typify, { null: false }, struct.typify) }) - test('minor-edge-typify', async () => { - const { - typify, T_noval, T_scalar, T_function, T_symbol, T_any, T_node, T_instance, T_null - } = struct - class X { } + const { typify, T_noval, T_scalar, T_function, T_symbol, T_any, T_node, T_instance, T_null } = + struct + class X {} const x = new X() equal(typify(), T_noval) equal(typify(undefined), T_noval) equal(typify(NaN), T_noval) equal(typify(null), T_scalar | T_null) - equal(typify(() => null), T_scalar | T_function) + equal( + typify(() => null), + T_scalar | T_function, + ) equal(typify(Symbol('S')), T_scalar | T_symbol) equal(typify(BigInt(1)), T_any) equal(typify(x), T_node | T_instance) }) - test('minor-size', async () => { await runsetflags(spec.minor.size, { null: false }, struct.size) }) - test('minor-slice', async () => { - await runsetflags(spec.minor.slice, { null: false }, - (vin: any) => struct.slice(vin.val, vin.start, vin.end)) + await runsetflags(spec.minor.slice, { null: false }, (vin: any) => + struct.slice(vin.val, vin.start, vin.end), + ) }) - test('minor-pad', async () => { - await runsetflags(spec.minor.pad, { null: false }, - (vin: any) => struct.pad(vin.val, vin.pad, vin.char)) + await runsetflags(spec.minor.pad, { null: false }, (vin: any) => + struct.pad(vin.val, vin.pad, vin.char), + ) }) - test('minor-setpath', async () => { - await runsetflags(spec.minor.setpath, { null: false }, - (vin: any) => struct.setpath(vin.store, vin.path, vin.val)) + await runsetflags(spec.minor.setpath, { null: false }, (vin: any) => + struct.setpath(vin.store, vin.path, vin.val), + ) }) - test('minor-edge-setpath', async () => { const { setpath, DELETE } = struct const x = { y: { z: 1, q: 2 } } @@ -397,8 +370,6 @@ describe('StructUtility', async () => { deepEqual(x, { y: { z: 1 } }) }) - - // walk tests // ========== @@ -410,10 +381,16 @@ describe('StructUtility', async () => { let log: string[] = [] function walklog(key: any, val: any, parent: any, path: any) { - log.push('k=' + stringify(key) + - ', v=' + stringify(val) + - ', p=' + stringify(parent) + - ', t=' + pathify(path)) + log.push( + 'k=' + + stringify(key) + + ', v=' + + stringify(val) + + ', p=' + + stringify(parent) + + ', t=' + + pathify(path), + ) return val } @@ -429,7 +406,6 @@ describe('StructUtility', async () => { deepEqual(log, test.out.both) }) - test('walk-basic', async () => { function walkpath(_key: any, val: any, _parent: any, path: any) { return 'string' === typeof val ? val + '~' + path.join('.') : val @@ -438,34 +414,28 @@ describe('StructUtility', async () => { await runset(spec.walk.basic, (vin: any) => struct.walk(vin, walkpath)) }) - test('walk-depth', async () => { - - await runsetflags(spec.walk.depth, { null: false }, - (vin: any) => { - let top: any = undefined - let cur: any = undefined - function copy(key: any, val: any, _parent: any, _path: any) { - if (undefined === key || struct.isnode(val)) { - let child = struct.islist(val) ? [] : {} - if (undefined === key) { - top = cur = child - } - else { - cur = cur[key] = child - } - } - else { - cur[key] = val + await runsetflags(spec.walk.depth, { null: false }, (vin: any) => { + let top: any = undefined + let cur: any = undefined + function copy(key: any, val: any, _parent: any, _path: any) { + if (undefined === key || struct.isnode(val)) { + const child = struct.islist(val) ? [] : {} + if (undefined === key) { + top = cur = child + } else { + cur = cur[key] = child } - return val + } else { + cur[key] = val } - struct.walk(vin.src, copy, undefined, vin.maxdepth) - return top - }) + return val + } + struct.walk(vin.src, copy, undefined, vin.maxdepth) + return top + }) }) - test('walk-copy', async () => { const { walk, isnode, ismap, islist, size, setprop } = struct @@ -478,7 +448,7 @@ describe('StructUtility', async () => { } let v = val - let i = size(path) + const i = size(path) if (isnode(v)) { v = cur[i] = ismap(v) ? {} : [] @@ -492,8 +462,6 @@ describe('StructUtility', async () => { await runset(spec.walk.copy, (vin: any) => (walk(vin, walkcopy), cur[0])) }) - - // merge tests // =========== @@ -503,27 +471,22 @@ describe('StructUtility', async () => { deepEqual(merge(test.in), test.out) }) - test('merge-cases', async () => { await runset(spec.merge.cases, struct.merge) }) - test('merge-array', async () => { await runset(spec.merge.array, struct.merge) }) - test('merge-integrity', async () => { await runset(spec.merge.integrity, struct.merge) }) - test('merge-depth', async () => { await runset(spec.merge.depth, (vin: any) => struct.merge(vin.val, vin.depth)) }) - test('merge-special', async () => { const { merge } = struct const f0 = () => null @@ -538,7 +501,9 @@ describe('StructUtility', async () => { deepEqual(merge([[global.fetch]]), [global.fetch]) deepEqual(merge([{ a: { b: global.fetch } }]), { a: { b: global.fetch } }) - class Bar { x = 1 } + class Bar { + x = 1 + } const b0 = new Bar() let out @@ -567,7 +532,6 @@ describe('StructUtility', async () => { equal(b0 instanceof Bar, true) }) - // getpath tests // ============= @@ -575,20 +539,16 @@ describe('StructUtility', async () => { await runset(spec.getpath.basic, (vin: any) => struct.getpath(vin.store, vin.path)) }) - test('getpath-relative', async () => { await runset(spec.getpath.relative, (vin: any) => - struct.getpath(vin.store, vin.path, - { dparent: vin.dparent, dpath: vin.dpath?.split('.') })) + struct.getpath(vin.store, vin.path, { dparent: vin.dparent, dpath: vin.dpath?.split('.') }), + ) }) - test('getpath-special', async () => { - await runset(spec.getpath.special, (vin: any) => - struct.getpath(vin.store, vin.path, vin.inj)) + await runset(spec.getpath.special, (vin: any) => struct.getpath(vin.store, vin.path, vin.inj)) }) - test('getpath-handler', async () => { await runset(spec.getpath.handler, (vin: any) => struct.getpath( @@ -600,12 +560,12 @@ describe('StructUtility', async () => { { handler: (_inj: any, val: any, _cur: any, _ref: any) => { return val() - } - } - )) + }, + }, + ), + ) }) - // inject tests // ============ @@ -615,18 +575,16 @@ describe('StructUtility', async () => { deepEqual(inject(test.in.val, test.in.store), test.out) }) - test('inject-string', async () => { await runset(spec.inject.string, (vin: any) => - struct.inject(vin.val, vin.store, { modify: nullModifier })) + struct.inject(vin.val, vin.store, { modify: nullModifier }), + ) }) - test('inject-deep', async () => { await runset(spec.inject.deep, (vin: any) => struct.inject(vin.val, vin.store)) }) - // transform tests // =============== @@ -636,46 +594,34 @@ describe('StructUtility', async () => { deepEqual(transform(test.in.data, test.in.spec), test.out) }) - test('transform-paths', async () => { - await runset(spec.transform.paths, (vin: any) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.paths, (vin: any) => struct.transform(vin.data, vin.spec)) }) - test('transform-cmds', async () => { - await runset(spec.transform.cmds, (vin: any) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.cmds, (vin: any) => struct.transform(vin.data, vin.spec)) }) - test('transform-each', async () => { - await runset(spec.transform.each, (vin: any) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.each, (vin: any) => struct.transform(vin.data, vin.spec)) }) - test('transform-pack', async () => { - await runset(spec.transform.pack, (vin: any) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.pack, (vin: any) => struct.transform(vin.data, vin.spec)) }) - test('transform-ref', async () => { - await runset(spec.transform.ref, (vin: any) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.ref, (vin: any) => struct.transform(vin.data, vin.spec)) }) - test('transform-format', async () => { await runsetflags(spec.transform.format, { null: false }, (vin: any) => - struct.transform(vin.data, vin.spec)) + struct.transform(vin.data, vin.spec), + ) }) - test('transform-apply', async () => { - await runset(spec.transform.apply, (vin: any) => - struct.transform(vin.data, vin.spec)) + await runset(spec.transform.apply, (vin: any) => struct.transform(vin.data, vin.spec)) }) test('transform-edge-apply', async () => { @@ -683,44 +629,41 @@ describe('StructUtility', async () => { equal(2, transform({}, ['`$APPLY`', (v: any) => 1 + v, 1])) }) - - test('transform-modify', async () => { await runset(spec.transform.modify, (vin: any) => - struct.transform( - vin.data, - vin.spec, - { - modify: (val: any, key: any, parent: any) => { - if (null != key && null != parent && 'string' === typeof val) { - val = parent[key] = '@' + val - } + struct.transform(vin.data, vin.spec, { + modify: (val: any, key: any, parent: any) => { + if (null != key && null != parent && 'string' === typeof val) { + val = parent[key] = '@' + val } - } - )) + }, + }), + ) }) - test('transform-extra', async () => { - deepEqual(struct.transform( - { a: 1 }, - { x: '`a`', b: '`$COPY`', c: '`$UPPER`' }, + deepEqual( + struct.transform( + { a: 1 }, + { x: '`a`', b: '`$COPY`', c: '`$UPPER`' }, + { + extra: { + b: 2, + $UPPER: (state: any) => { + const { path } = state + return ('' + struct.getprop(path, path.length - 1)).toUpperCase() + }, + }, + }, + ), { - extra: { - b: 2, $UPPER: (state: any) => { - const { path } = state - return ('' + struct.getprop(path, path.length - 1)).toUpperCase() - } - } - } - ), { - x: 1, - b: 2, - c: 'C' - }) + x: 1, + b: 2, + c: 'C', + }, + ) }) - test('transform-funcval', async () => { const { transform } = struct // f0 should never be called (no $ prefix). @@ -731,43 +674,37 @@ describe('StructUtility', async () => { deepEqual(transform({ f0 }, { x: '`f0`' }), { x: f0 }) }) - // validate tests // =============== test('validate-basic', async () => { - await runsetflags(spec.validate.basic, { null: false }, - (vin: any) => struct.validate(vin.data, vin.spec)) + await runsetflags(spec.validate.basic, { null: false }, (vin: any) => + struct.validate(vin.data, vin.spec), + ) }) - test('validate-child', async () => { await runset(spec.validate.child, (vin: any) => struct.validate(vin.data, vin.spec)) }) - test('validate-one', async () => { await runset(spec.validate.one, (vin: any) => struct.validate(vin.data, vin.spec)) }) - test('validate-exact', async () => { await runset(spec.validate.exact, (vin: any) => struct.validate(vin.data, vin.spec)) }) - test('validate-invalid', async () => { - await runsetflags(spec.validate.invalid, { null: false }, - (vin: any) => struct.validate(vin.data, vin.spec)) + await runsetflags(spec.validate.invalid, { null: false }, (vin: any) => + struct.validate(vin.data, vin.spec), + ) }) - test('validate-special', async () => { - await runset(spec.validate.special, (vin: any) => - struct.validate(vin.data, vin.spec, vin.inj)) + await runset(spec.validate.special, (vin: any) => struct.validate(vin.data, vin.spec, vin.inj)) }) - test('validate-edge', async () => { const { validate } = struct let errs: any[] = [] @@ -782,23 +719,22 @@ describe('StructUtility', async () => { validate({ x: [] }, { x: '`$INSTANCE`' }, { errs }) equal(errs[0], 'Expected field x to be instance, but found list: [].') - class C { } + class C {} const c = new C() errs = [] validate({ x: c }, { x: '`$INSTANCE`' }, { errs }) equal(errs.length, 0) }) - test('validate-custom', async () => { const errs: any[] = [] const extra = { $INTEGER: (inj: any) => { const { key } = inj // let out = getprop(current, key) - let out = struct.getprop(inj.dparent, key) + const out = struct.getprop(inj.dparent, key) - let t = typeof out + const t = typeof out if ('number' !== t && !Number.isInteger(out)) { inj.errs.push('Not an integer at ' + inj.path.slice(1).join('.') + ': ' + out) return @@ -819,7 +755,6 @@ describe('StructUtility', async () => { deepEqual(errs, ['Not an integer at a: A']) }) - // select tests // ============ @@ -827,45 +762,41 @@ describe('StructUtility', async () => { await runset(spec.select.basic, (vin: any) => struct.select(vin.obj, vin.query)) }) - test('select-operators', async () => { await runset(spec.select.operators, (vin: any) => struct.select(vin.obj, vin.query)) }) - test('select-edge', async () => { await runset(spec.select.edge, (vin: any) => struct.select(vin.obj, vin.query)) }) - test('select-alts', async () => { await runset(spec.select.alts, (vin: any) => struct.select(vin.obj, vin.query)) }) - // JSON Builder // ============ test('json-builder', async () => { const { jsonify, jm, jt } = struct - equal(jsonify(jm( - 'a', 1 - )), `{ + equal( + jsonify(jm('a', 1)), + `{ "a": 1 -}`) +}`, + ) - equal(jsonify(jt( - 'b', 2 - )), `[ + equal( + jsonify(jt('b', 2)), + `[ "b", 2 -]`) +]`, + ) - equal(jsonify(jm( - 'c', 'C', - 'd', jm('x', true), - 'e', jt(null, false) - )), `{ + equal( + jsonify(jm('c', 'C', 'd', jm('x', true), 'e', jt(null, false))), + `{ "c": "C", "d": { "x": true @@ -874,17 +805,14 @@ describe('StructUtility', async () => { null, false ] -}`) - - equal(jsonify(jt( - 3.3, jm( - 'f', true, - 'g', false, - 'h', null, - 'i', jt('y', 0), - 'j', jm('z', -1), - 'k') - )), `[ +}`, + ) + + equal( + jsonify( + jt(3.3, jm('f', true, 'g', false, 'h', null, 'i', jt('y', 0), 'j', jm('z', -1), 'k')), + ), + `[ 3.3, { "f": true, @@ -899,24 +827,18 @@ describe('StructUtility', async () => { }, "k": null } -]`) - - equal(jsonify(jm( - true, 1, - false, 2, - null, 3, - ['a'], 4, - { 'b': 0 }, 5 - )), `{ +]`, + ) + + equal( + jsonify(jm(true, 1, false, 2, null, 3, ['a'], 4, { b: 0 }, 5)), + `{ "true": 1, "false": 2, "null": 3, "[a]": 4, "{b:0}": 5 -}`) - +}`, + ) }) - - }) - diff --git a/ts/test/utility/index.ts b/ts/test/utility/index.ts index d022eb8c..8a274478 100644 --- a/ts/test/utility/index.ts +++ b/ts/test/utility/index.ts @@ -1,10 +1,5 @@ - import { SDK } from '../sdk' const TEST_JSON_FILE = '../../build/test/test.json' - -export { - SDK, - TEST_JSON_FILE, -} +export { SDK, TEST_JSON_FILE } diff --git a/ts/test/walk-bench.test.ts b/ts/test/walk-bench.test.ts index 3552d318..490194cc 100644 --- a/ts/test/walk-bench.test.ts +++ b/ts/test/walk-bench.test.ts @@ -8,10 +8,8 @@ import { test, describe } from 'node:test' import { walk } from '../dist/StructUtility' - const BENCH = '1' === process.env.WALK_BENCH - // Build a balanced tree of maps with given width and depth. // Total nodes: (width^(depth+1) - 1) / (width - 1). function buildTree(width: number, depth: number): any { @@ -25,7 +23,6 @@ function buildTree(width: number, depth: number): any { return out } - function countNodes(val: any): number { if (null == val || 'object' !== typeof val) { return 1 @@ -37,7 +34,6 @@ function countNodes(val: any): number { return n } - function measure(label: string, tree: any, runs: number) { // Touch path to simulate a minimal consumer. Using path.length keeps the // work O(1) so we measure walk overhead rather than callback overhead. @@ -70,15 +66,13 @@ function measure(label: string, tree: any, runs: number) { console.log( `[walk-bench] ${label}: nodes=${nodes} runs=${runs} ` + - `min=${min.toFixed(2)}ms median=${median.toFixed(2)}ms ` + - `mean=${mean.toFixed(2)}ms max=${max.toFixed(2)}ms ` + - `ns/node=${nsPerNode.toFixed(1)} sink=${sink}` + `min=${min.toFixed(2)}ms median=${median.toFixed(2)}ms ` + + `mean=${mean.toFixed(2)}ms max=${max.toFixed(2)}ms ` + + `ns/node=${nsPerNode.toFixed(1)} sink=${sink}`, ) } - describe('walk-bench', () => { - test('walk-bench-wide-and-deep', { skip: !BENCH }, () => { // ~299k nodes: width=8, depth=6. const wideDeep = buildTree(8, 6) @@ -100,5 +94,4 @@ describe('walk-bench', () => { const deep = buildTree(2, 20) measure('deep (w=2,d=20)', deep, 5) }) - }) diff --git a/zig/Makefile b/zig/Makefile index 9d9fba28..d9ec6583 100644 --- a/zig/Makefile +++ b/zig/Makefile @@ -1,4 +1,4 @@ -.PHONY: inspect build test clean reset +.PHONY: inspect build test lint fmt-check clean reset # Inspect: show language and project version info inspect: @@ -12,6 +12,13 @@ inspect: build: zig build +# Code quality: `zig fmt` formatting check. The compiler itself (zig build) +# is the static analyser for Zig; there is no separate community linter. +lint: fmt-check + +fmt-check: + zig fmt --check src test build.zig + # Run all tests. # The zig test framework sometimes crashes with signal 11 during cleanup # after all tests pass, due to *MapRef/*ListRef cross-references in arena diff --git a/zig/README.md b/zig/README.md index 6bc45063..f3fe854e 100644 --- a/zig/README.md +++ b/zig/README.md @@ -1,7 +1,7 @@ # Struct for Zig > Zig port of the canonical TypeScript implementation. -> Status: in progress. See [`../REPORT.md`](../REPORT.md) for parity. +> Status: complete. See [`../REPORT.md`](../REPORT.md) for parity. For motivation, language-neutral concepts, and the cross-language parity matrix, see the [top-level README](../README.md). diff --git a/zig/src/struct.zig b/zig/src/struct.zig index 10455633..aff413dd 100644 --- a/zig/src/struct.zig +++ b/zig/src/struct.zig @@ -229,12 +229,20 @@ pub const TYPENAME = [_][]const u8{ S_function, S_symbol, S_null, - "", "", "", - "", "", "", "", + "", + "", + "", + "", + "", + "", + "", S_list, S_map, S_instance, - "", "", "", "", + "", + "", + "", + "", S_scalar, S_node, }; @@ -439,7 +447,9 @@ pub fn keysof(allocator: Allocator, val: JsonValue) !JsonValue { } std.mem.sort([]const u8, key_strs.items, {}, stringLessThan); - const arr_lr2 = try allocator.create(ListRef); arr_lr2.* = .{ .data = try ListData.initCapacity(allocator, obj.count()) }; const arr = arr_lr2; + const arr_lr2 = try allocator.create(ListRef); + arr_lr2.* = .{ .data = try ListData.initCapacity(allocator, obj.count()) }; + const arr = arr_lr2; for (key_strs.items) |k| { try arr.append(JsonValue{ .string = k }); } @@ -484,11 +494,13 @@ pub fn items(allocator: Allocator, val: JsonValue) !JsonValue { } std.mem.sort([]const u8, key_strs.items, {}, stringLessThan); - const arr_lr2 = try allocator.create(ListRef); arr_lr2.* = .{ .data = try ListData.initCapacity(allocator, obj.count()) }; const arr = arr_lr2; + const arr_lr2 = try allocator.create(ListRef); + arr_lr2.* = .{ .data = try ListData.initCapacity(allocator, obj.count()) }; + const arr = arr_lr2; for (key_strs.items) |k| { const pair_lr = try allocator.create(ListRef); - pair_lr.* = .{ .data = try ListData.initCapacity(allocator, 2) }; - const pair = pair_lr; + pair_lr.* = .{ .data = try ListData.initCapacity(allocator, 2) }; + const pair = pair_lr; try pair.append(JsonValue{ .string = k }); try pair.append(obj.get(k).?); try arr.append(JsonValue{ .array = pair }); @@ -503,8 +515,8 @@ pub fn items(allocator: Allocator, val: JsonValue) !JsonValue { const arr = arr_lr; for (list, 0..) |v, i| { const pair_lr = try allocator.create(ListRef); - pair_lr.* = .{ .data = try ListData.initCapacity(allocator, 2) }; - const pair = pair_lr; + pair_lr.* = .{ .data = try ListData.initCapacity(allocator, 2) }; + const pair = pair_lr; const idx_str = try std.fmt.allocPrint(allocator, "{d}", .{i}); try pair.append(JsonValue{ .string = idx_str }); try pair.append(v); @@ -525,8 +537,8 @@ pub fn flatten(allocator: Allocator, val: JsonValue, depth: i64) !JsonValue { fn flattenDepth(allocator: Allocator, arr: []const JsonValue, depth: i64) !*ListRef { const result_lr = try allocator.create(ListRef); - result_lr.* = .{ .data = ListData.init(allocator) }; - const result = result_lr; + result_lr.* = .{ .data = ListData.init(allocator) }; + const result = result_lr; for (arr) |item| { if (depth > 0 and item == .array) { const sub = try flattenDepth(allocator, item.array.data.items, depth - 1); @@ -540,12 +552,34 @@ fn flattenDepth(allocator: Allocator, arr: []const JsonValue, depth: i64) !*List return result; } +// Predicate for `filter`: receives a [key, value] pair (a 2-element list). +pub const FilterFn = *const fn (item: JsonValue) bool; + +// Keep entries for which `check([key, value])` is truthy; returns a list of the +// matching values. Mirrors TS `filter`. +pub fn filter(allocator: Allocator, val: JsonValue, check: FilterFn) !JsonValue { + const all = try items(allocator, val); + const out = try JsonValue.makeList(allocator); + if (all == .array) { + for (all.array.data.items) |pair| { + if (check(pair)) { + const v: JsonValue = if (pair == .array and pair.array.data.items.len > 1) + pair.array.data.items[1] + else + .null; + try out.array.append(v); + } + } + } + return out; +} + // Deep clone a JSON value. pub fn clone(allocator: Allocator, val: JsonValue) !JsonValue { return switch (val) { .object => |obj| { const new_obj = try allocator.create(MapRef); - new_obj.* = .{ .data = MapData.init(allocator) }; + new_obj.* = .{ .data = MapData.init(allocator) }; try new_obj.data.ensureTotalCapacity(@intCast(obj.count())); var it = obj.iterator(); while (it.next()) |kv| { @@ -556,8 +590,8 @@ pub fn clone(allocator: Allocator, val: JsonValue) !JsonValue { }, .array => |arr| { const new_arr_lr = try allocator.create(ListRef); - new_arr_lr.* = .{ .data = try ListData.initCapacity(allocator, arr.data.items.len) }; - const new_arr = new_arr_lr; + new_arr_lr.* = .{ .data = try ListData.initCapacity(allocator, arr.data.items.len) }; + const new_arr = new_arr_lr; for (arr.data.items) |item| { const cloned_item = try clone(allocator, item); try new_arr.append(cloned_item); @@ -568,6 +602,34 @@ pub fn clone(allocator: Allocator, val: JsonValue) !JsonValue { }; } +// Define a JSON object from alternating key/value arguments. +// jm(alloc, &.{ str("a"), int(1), str("b"), int(2) }) => { "a": 1, "b": 2 }. +// A missing trailing value becomes JSON null; a non-string key is stringified. +pub fn jm(allocator: Allocator, kv: []const JsonValue) !JsonValue { + const o = try JsonValue.makeMap(allocator); + var i: usize = 0; + while (i < kv.len) : (i += 2) { + const k = kv[i]; + const ks = switch (k) { + .string => |s| s, + else => try stringify(allocator, k, null), + }; + const v: JsonValue = if (i + 1 < kv.len) kv[i + 1] else .null; + try o.object.put(ks, v); + } + return o; +} + +// Define a JSON array (tuple) from positional arguments. +// jt(alloc, &.{ int(1), str("x"), .{ .bool = true } }) => [1, "x", true]. +pub fn jt(allocator: Allocator, v: []const JsonValue) !JsonValue { + const a = try JsonValue.makeList(allocator); + for (v) |item| { + try a.array.append(item); + } + return a; +} + // Delete a property from a map or remove an element from a list. pub fn delprop(allocator: Allocator, parent: JsonValue, key: JsonValue) !JsonValue { _ = allocator; @@ -1241,8 +1303,8 @@ pub fn sliceMut(allocator: Allocator, val: JsonValue, start_in: ?i64, end_in: ?i const e_usize: usize = @intCast(end_val); const src = val.array.data.items[s_usize..e_usize]; const new_arr_lr = try allocator.create(ListRef); - new_arr_lr.* = .{ .data = try ListData.initCapacity(allocator, src.len) }; - const new_arr = new_arr_lr; + new_arr_lr.* = .{ .data = try ListData.initCapacity(allocator, src.len) }; + const new_arr = new_arr_lr; for (src) |item| { try new_arr.append(item); } @@ -1256,8 +1318,8 @@ pub fn sliceMut(allocator: Allocator, val: JsonValue, start_in: ?i64, end_in: ?i } else { if (val == .array) { const empty_arr_lr = try allocator.create(ListRef); - empty_arr_lr.* = .{ .data = ListData.init(allocator) }; - const empty_arr = empty_arr_lr; + empty_arr_lr.* = .{ .data = ListData.init(allocator) }; + const empty_arr = empty_arr_lr; _ = &empty_arr; return JsonValue{ .array = empty_arr }; } @@ -1455,7 +1517,7 @@ pub fn merge(allocator: Allocator, val: JsonValue, maxdepth: i32) !JsonValue { if (islist(last)) return try JsonValue.makeList(allocator); if (ismap(last)) { const obj = try allocator.create(MapRef); - obj.* = .{ .data = MapData.init(allocator) }; + obj.* = .{ .data = MapData.init(allocator) }; _ = &obj; return JsonValue{ .object = obj }; } @@ -2023,7 +2085,7 @@ fn appendSlice(allocator: Allocator, comptime T: type, existing: []const T, item // Inject — core injection function with three-phase key processing. // ============================================================================ -pub fn injectVal(allocator: Allocator, val: JsonValue, store: JsonValue, inj_opt: ?*Injection) anyerror!JsonValue { +pub fn inject(allocator: Allocator, val: JsonValue, store: JsonValue, inj_opt: ?*Injection) anyerror!JsonValue { var inj: *Injection = undefined; if (inj_opt == null or (inj_opt != null and inj_opt.?.mode == 0)) { @@ -2130,7 +2192,7 @@ pub fn injectVal(allocator: Allocator, val: JsonValue, store: JsonValue, inj_opt childinj.mode = M_VAL; // Phase 2: VAL — inject the child value. - _ = try injectVal(allocator, childval, store, childinj); + _ = try inject(allocator, childval, store, childinj); nkI = childinj.key_i; node_keys = blk: { @@ -2836,7 +2898,7 @@ fn cmdEach(allocator: Allocator, inj: *Injection, store: JsonValue) !JsonValue { } try each_store.put(S_DTOP, src_item); - const injected = try injectVal(allocator, child_clone, JsonValue{ .object = each_store }, null); + const injected = try inject(allocator, child_clone, JsonValue{ .object = each_store }, null); try result_lr.append(injected); } @@ -2920,7 +2982,7 @@ fn cmdPack(allocator: Allocator, inj: *Injection, store: JsonValue) !JsonValue { // Build the output map. const result_obj = try allocator.create(MapRef); - result_obj.* = .{ .data = MapData.init(allocator) }; + result_obj.* = .{ .data = MapData.init(allocator) }; for (src_list.items, 0..) |src_item, idx| { // Resolve the key for this item. var item_key: []const u8 = ""; @@ -2929,13 +2991,13 @@ fn cmdPack(allocator: Allocator, inj: *Injection, store: JsonValue) !JsonValue { if (std.mem.startsWith(u8, kp, "`")) { // Backtick path: inject to resolve. const key_store = try allocator.create(MapRef); - key_store.* = .{ .data = MapData.init(allocator) }; + key_store.* = .{ .data = MapData.init(allocator) }; if (store == .object) { var sit = store.object.iterator(); while (sit.next()) |kv| try key_store.put(kv.key_ptr.*, kv.value_ptr.*); } try key_store.put(S_DTOP, src_item); - const key_result = try injectVal(allocator, JsonValue{ .string = kp }, JsonValue{ .object = key_store }, null); + const key_result = try inject(allocator, JsonValue{ .string = kp }, JsonValue{ .object = key_store }, null); if (key_result == .string) item_key = key_result.string; } else { // Direct property path. @@ -3017,7 +3079,7 @@ fn cmdPack(allocator: Allocator, inj: *Injection, store: JsonValue) !JsonValue { } try pack_store.put(S_DTOP, src_item); - const injected = try injectVal(allocator, child_clone, JsonValue{ .object = pack_store }, null); + const injected = try inject(allocator, child_clone, JsonValue{ .object = pack_store }, null); try result_obj.put(item_key, injected); } @@ -3102,7 +3164,7 @@ fn cmdRef(allocator: Allocator, inj: *Injection, store: JsonValue) !JsonValue { }; tinj.keys[0] = lastPath; - _ = try injectVal(allocator, tref, store, tinj); + _ = try inject(allocator, tref, store, tinj); const rval = tinj.val; // Set the result on the grandparent. @@ -3161,6 +3223,72 @@ fn cmdApply(allocator: Allocator, inj: *Injection) !JsonValue { return .null; } +// ============================================================================ +// Injection helpers — exposed for callers writing custom injectors. +// ============================================================================ + +// Human placement name for an injection mode (mirrors TS PLACEMENT). +fn placementName(mode: i32) []const u8 { + return if (mode == M_VAL) "value" else "key"; +} + +// Validate where an injection result may be placed: the current mode must be +// one of `modes`, and (if `parent_types` is non-zero) the parent's type must +// match it. Pushes a message to `inj.errs` and returns false on a violation. +// Mirrors TS `checkPlacement`. +pub fn checkPlacement( + allocator: Allocator, + modes: i32, + ijname: []const u8, + parent_types: i64, + inj: *Injection, +) !bool { + if ((modes & inj.mode) == 0) { + var expected = std.ArrayList(u8).init(allocator); + var first = true; + for ([_]i32{ M_KEYPRE, M_KEYPOST, M_VAL }) |m| { + if ((modes & m) != 0) { + if (!first) try expected.append(','); + try expected.appendSlice(placementName(m)); + first = false; + } + } + const msg = try std.fmt.allocPrint(allocator, "${s}: invalid placement as {s}, expected: {s}.", .{ ijname, placementName(inj.mode), expected.items }); + try inj.errs.append(msg); + return false; + } + if (parent_types != 0) { + const ptype = typify(inj.parent); + if ((parent_types & ptype) == 0) { + const msg = try std.fmt.allocPrint(allocator, "${s}: invalid placement in parent {s}, expected: {s}.", .{ ijname, typename(ptype), typename(parent_types) }); + try inj.errs.append(msg); + return false; + } + } + return true; +} + +// Type-check the argument list passed to a transform-command injector. +// Returns a list whose element 0 is either JSON null (all args OK) or an error +// string, followed by the args that passed (up to the first mismatch). +// Mirrors TS `injectorArgs`. +pub fn injectorArgs(allocator: Allocator, arg_types: []const i64, args: []const JsonValue) !JsonValue { + const found = try JsonValue.makeList(allocator); + try found.array.append(.null); + for (arg_types, 0..) |at, arg_i| { + const arg: JsonValue = if (arg_i < args.len) args[arg_i] else .null; + const arg_type = typify(arg); + if ((at & arg_type) == 0) { + const arg_str = try stringify(allocator, arg, 22); + const msg = try std.fmt.allocPrint(allocator, "invalid argument: {s} ({s} at position {d}) is not of type: {s}.", .{ arg_str, typename(arg_type), 1 + arg_i, typename(at) }); + found.array.data.items[0] = JsonValue{ .string = msg }; + break; + } + try found.array.append(arg); + } + return found; +} + // ============================================================================ // injectChild — inject a child value using the parent injection context. // ============================================================================ @@ -3171,7 +3299,7 @@ fn injectChild(allocator: Allocator, child_raw: JsonValue, store: JsonValue, inj // Build store with correct data context. const child_store = try allocator.create(MapRef); - child_store.* = .{ .data = MapData.init(allocator) }; + child_store.* = .{ .data = MapData.init(allocator) }; if (store == .object) { var sit = store.object.iterator(); while (sit.next()) |kv| try child_store.put(kv.key_ptr.*, kv.value_ptr.*); @@ -3179,7 +3307,7 @@ fn injectChild(allocator: Allocator, child_raw: JsonValue, store: JsonValue, inj // Set $TOP to the data parent so $COPY works. child_store.put(S_DTOP, inj.dparent) catch {}; - child_clone = try injectVal(allocator, child_clone, JsonValue{ .object = child_store }, null); + child_clone = try inject(allocator, child_clone, JsonValue{ .object = child_store }, null); return child_clone; } @@ -3197,12 +3325,12 @@ pub fn transform(allocator: Allocator, data: JsonValue, spec: JsonValue) !JsonVa const orig_spec = try clone(allocator, spec); const store = try allocator.create(MapRef); - store.* = .{ .data = MapData.init(allocator) }; + store.* = .{ .data = MapData.init(allocator) }; try store.put(S_DTOP, data_clone); try store.put(S_DSPEC, orig_spec); const store_val = JsonValue{ .object = store }; - return try injectVal(allocator, spec_clone, store_val, null); + return try inject(allocator, spec_clone, store_val, null); } // Transform with a modify callback applied after each injection step. @@ -3229,7 +3357,7 @@ pub fn transformModify(allocator: Allocator, data: JsonValue, spec: JsonValue, m errs.* = std.ArrayList([]const u8).init(allocator); inj_init.* = Injection{ .allocator = allocator, - .mode = 0, // triggers root initialization in injectVal + .mode = 0, // triggers root initialization in inject .modify = modify, .keys = empty_keys, .path = empty_path, @@ -3238,7 +3366,7 @@ pub fn transformModify(allocator: Allocator, data: JsonValue, spec: JsonValue, m .errs = errs, }; - return try injectVal(allocator, spec_clone, store_val, inj_init); + return try inject(allocator, spec_clone, store_val, inj_init); } // ============================================================================ @@ -3310,7 +3438,7 @@ pub fn validate(allocator: Allocator, data: JsonValue, spec: JsonValue) anyerror .errs = errs, }; - const result = try injectVal(allocator, spec_clone, store_val, inj_init); + const result = try inject(allocator, spec_clone, store_val, inj_init); if (errs.items.len > 0) { var msg = std.ArrayList(u8).init(allocator); @@ -3736,7 +3864,7 @@ fn stdJsonEqual(a: StdJsonValue, b: StdJsonValue) bool { // Select — filter children matching a query. // ============================================================================ -pub fn selectFn(allocator: Allocator, children: JsonValue, query: JsonValue) anyerror!JsonValue { +pub fn select(allocator: Allocator, children: JsonValue, query: JsonValue) anyerror!JsonValue { if (!isnode(children)) return try JsonValue.makeList(allocator); // Normalize children: add $KEY for map/list items. diff --git a/zig/test/struct_test.zig b/zig/test/struct_test.zig index 15820aed..7cc15c5e 100644 --- a/zig/test/struct_test.zig +++ b/zig/test/struct_test.zig @@ -200,7 +200,7 @@ fn wrap_filter(allocator: Allocator, val: JsonValue) JsonValue { const list = v.array.data.items; const result_lr = allocator.create(voxgig_struct.ListRef) catch return .null; - result_lr.* = .{ .data = voxgig_struct.ListData.init(allocator) }; + result_lr.* = .{ .data = voxgig_struct.ListData.init(allocator) }; for (list) |item| { const num: f64 = switch (item) { .integer => |i| @floatFromInt(i), @@ -626,7 +626,8 @@ fn cloneWithDepth(allocator: Allocator, val: JsonValue, maxdepth: i32, depth: i3 return JsonValue.makeMap(allocator) catch return .null; } if (voxgig_struct.ismap(val)) { - const new_obj_ref = allocator.create(voxgig_struct.MapRef) catch return .null; new_obj_ref.* = .{ .data = voxgig_struct.MapData.init(allocator) }; + const new_obj_ref = allocator.create(voxgig_struct.MapRef) catch return .null; + new_obj_ref.* = .{ .data = voxgig_struct.MapData.init(allocator) }; var it = val.object.iterator(); while (it.next()) |kv| { try new_obj_ref.put(kv.key_ptr.*, try cloneWithDepth(allocator, kv.value_ptr.*, maxdepth, depth + 1)); @@ -918,7 +919,7 @@ fn wrap_inject(allocator: Allocator, val: JsonValue) JsonValue { const m = val.object; const inject_val = m.get("val") orelse return .null; const store = m.get("store") orelse JsonValue.makeMap(allocator) catch .null; - return voxgig_struct.injectVal(allocator, inject_val, store, null) catch return .null; + return voxgig_struct.inject(allocator, inject_val, store, null) catch return .null; } test "inject-string" { @@ -1044,7 +1045,7 @@ fn wrap_select(allocator: Allocator, val: JsonValue) JsonValue { const m = val.object; const obj = m.get("obj") orelse return .null; const query = m.get("query") orelse return .null; - return voxgig_struct.selectFn(allocator, obj, query) catch return .null; + return voxgig_struct.select(allocator, obj, query) catch return .null; } test "select-basic" {