Skip to content

Implementation Comparison

The shared comparison runner lives in scripts/compare-impls.mjs because this repo owns the corpus. It compares sibling implementation checkouts against the same .crv / .html pairs and reports default conformance, optional Tier-2 adapter coverage, rough CLI timing, and the extension hook surface each implementation exposes.

Snapshot (2026-05-31)

Historical run from 2026-05-31, when the corpus held 154 pairs. The corpus has since grown (now ~200 pairs); regenerate with npm run compare:impls for current figures. The numbers below are from that dated run.

154 / 154Rust corpus pass
154 / 154JS corpus pass
154 / 154PHP corpus pass
0cross-implementation diffs
ImplementationCommitCorpusMismatchesErrorsAvg CLI ms/file
Rust07ea8ea154 / 1540033.83
JS48c45e0154 / 1540058.92
PHPb1fa677154 / 1540073.96

Spec commit: 840b64f

Optional Tier-2 Profile

The optional profile enables a shared adapter per feature where each implementation exposes one. Unsupported feature/implementation combinations are reported as skipped, not failures.

FeatureRustJSPHP
Social link templatespasspasspass
Emoji mappasspassskipped
German smart quotesskippedskippedpass
Bare URL autolinkskippedskippedpass
ImplementationOptional passSkippedMismatchesErrorsAvg CLI ms/file
Rust2 / 220063.93
JS2 / 220094.26
PHP3 / 3100103.30

Optional cross-implementation diffs: 0

CLI Timing

These timings include process startup and should be read as smoke-level CLI performance, not parser microbenchmarks.

Rust
33.83 ms
JS
58.92 ms
PHP
73.96 ms

Extension Surface

The comparison run is default/no-opt-in, so extension behavior is not yet exercised across every min/max profile. This matrix records the hook surface available in each implementation today.

CapabilityRustJSPHP
Inline matcheryesnoyes
Block matcheryesnoyes
After-parse transformyesyesyes
Before-render transformyesyesyes
Inline extension rendereryesyesyes
Block extension renderer / render listeneryesnoyes
Converter-level registrationnonoyes

Running It

bash
npm run compare:impls
npm run compare:impls -- --corpus=optional
npm run compare:impls -- --limit=20 --bench

By default the script expects sibling checkouts:

  • ../carve-rs
  • ../carve-js
  • ../carve-php

Override those paths with CARVE_RS_DIR, CARVE_JS_DIR, and CARVE_PHP_DIR.

The documented snapshot used:

bash
CARVE_RS_DIR=/media/mark/data/work/git/carve-rs \
CARVE_JS_DIR=/media/mark/data/work/git/carve-js \
CARVE_PHP_DIR=/media/mark/data/work/git/carve-php \
node scripts/compare-impls.mjs

Default raw output:

text
Implementation summary
profile=default/no-opt-in corpus=core corpus_pairs=154
rust: pass=154/154 mismatch=0 error=0 skipped=0 avg_ms=33.83
js: pass=154/154 mismatch=0 error=0 skipped=0 avg_ms=58.92
php: pass=154/154 mismatch=0 error=0 skipped=0 avg_ms=73.96
cross_impl_diffs=0

Extension capability matrix
rust: inline matcher, block matcher, after_parse, before_render, inline extension renderer, block extension renderer
js: afterParse, beforeRender, inline extension renderer
php: inline matcher, block matcher, parsed-document hook, before-render hook, render listeners, converter registration
extension_profile_note=this run compares default/no-opt-in output. Use --corpus=optional for Tier-2 opt-in adapters.

Optional raw output:

text
Implementation summary
profile=optional/opt-in corpus=optional corpus_pairs=4
rust: pass=2/2 mismatch=0 error=0 skipped=2 avg_ms=63.93
js: pass=2/2 mismatch=0 error=0 skipped=2 avg_ms=94.26
php: pass=3/3 mismatch=0 error=0 skipped=1 avg_ms=103.30
cross_impl_diffs=0

Optional feature coverage
social-link-templates: rust, js, php
emoji-map: rust, js
smart-quotes-locale-de: php
bare-url-autolink: php

Scope

The tool has two profiles:

  • It runs the mandatory Tier-1 corpus in tests/corpus.
  • It runs optional Tier-2 adapters in tests/corpus-optional with --corpus=optional.
  • It compares byte-identical output after trimming.
  • It reports CLI-level average time per corpus file.
  • It reports extension system surface area.

Tier-3 app-extension max profiles still need language-specific adapter fixtures. That means a small runner per implementation that enables the same test extension in each language, then feeds those through the same comparison loop.

Released under the MIT License.